You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin使用Apache Felix+OSGi动态加载模块时遇类型转换错误

Kotlin + Apache Felix OSGi 类转换异常问题解决

问题描述

开发Kotlin服务器项目时,采用OSGi(Apache Felix)实现动态模块加载,运行主程序时抛出类转换异常:

Error: bundles.service.MathOperationsAddImpl cannot be cast to bundles.service.MathOperationsService
java.lang.ClassCastException: bundles.service.MathOperationsAddImpl cannot be cast to bundles.service.MathOperationsService
    at MathOperationProgram$Companion.main(MathOperationProgram.kt:23)
    at MathOperationProgram.main(MathOperationProgram.kt)

相关代码片段如下:

激活器代码

import bundles.service.MathOperationsService
import bundles.service.MathOperationsAddImpl
import org.osgi.framework.BundleActivator
import org.osgi.framework.BundleContext

class MathOperationsActivator: BundleActivator {
    override fun start(context: BundleContext?) {
        context?.registerService(MathOperationsService::class.java.name, MathOperationsAddImpl(), null)
    }

    override fun stop(context: BundleContext?) {
       // NOTE: The service is automatically unregistered.
    }
}

服务接口

interface MathOperationsService {
    fun calculate(a: Int, b: Int): Int
}

服务实现类

class MathOperationsAddImpl: MathOperationsService {
    override fun calculate(a: Int, b: Int): Int {
        return a + b
    }
}

主程序代码

class MathOperationProgram {

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val frameworkFactory = FrameworkFactory()
            val framework = frameworkFactory.newFramework(null)
            framework.init()
            try {
                framework.start()
                val bundleContext = framework.bundleContext

                val mathBundle: Bundle =
                    bundleContext.installBundle("file:/path/to/mathBundle.jar")
                mathBundle.start()

                val mathOperationsService = mathBundle.bundleContext.getService(
                    mathBundle.bundleContext.getServiceReference(MathOperationsService::class.java)
                )

                if (mathOperationsService != null) {
                    val result = mathOperationsService.calculate(3, 4)
                    println("Result: $result")
                } else {
                    println("Failed to retrieve MathOperationsService")
                }
            } catch (e: Exception) {
                println("Error: ${e.message}")
                e.printStackTrace()
            } finally {
                framework.stop()
                framework.waitForStop(0)
            }
        }
    }
}

Gradle打包任务

tasks.register("mathBundleJar", Jar::class) {
    archiveFileName.set("mathBundle.jar")
    manifest {
        attributes(
            "Bundle-Name" to "Math Operations Bundle",
            "Bundle-Description" to "Bundle for Math Operations",
            "Bundle-SymbolicName" to "math-bundle",
            "Bundle-Vendor" to "Apache Felix",
            "Bundle-Version" to "1.0.0",
            "Bundle-Activator" to "bundles.MathOperationsActivator",
            "Export-Package" to "bundles.service",
            "Import-Package" to "org.osgi.framework, bundles.service",
        )
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    from(sourceSets.main.get().output)
    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
    })
}

错误原因

核心问题是同一接口被不同类加载器重复加载:

  • 主程序类加载器加载了MathOperationsService接口
  • mathBundle的类加载器也加载了一份完全相同的MathOperationsService接口
  • OSGi中,不同类加载器加载的类属于不同类型,导致实现类无法转换为主程序中的接口实例

具体诱因:

  1. Gradle打包时将bundles.service包的接口类也打进了mathBundle,同时主程序本身也包含该接口
  2. 主程序直接使用本地的MathOperationsService::class.java获取服务引用,没有遵循OSGi跨bundle服务查找规范

解决步骤

1. 拆分模块,独立API包

创建独立的math-api模块,仅包含MathOperationsService接口,让主程序和mathBundle都依赖该模块,确保接口只被类加载器加载一次。

2. 修正Gradle打包任务

修改mathBundle的打包配置,不再将API类打进bundle(通过OSGi的Import-Package导入接口):

tasks.register("mathBundleJar", Jar::class) {
    archiveFileName.set("mathBundle.jar")
    manifest {
        attributes(
            "Bundle-Name" to "Math Operations Bundle",
            "Bundle-Description" to "Bundle for Math Operations",
            "Bundle-SymbolicName" to "math-bundle",
            "Bundle-Vendor" to "Apache Felix",
            "Bundle-Version" to "1.0.0",
            "Bundle-Activator" to "bundles.MathOperationsActivator",
            "Export-Package" to "", // 无需导出接口,接口在独立API模块中
            "Import-Package" to "org.osgi.framework, bundles.service", // 导入API模块的接口
        )
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    from(sourceSets.main.get().output)
    // 移除runtimeClasspath的打包逻辑,避免将API类打进bundle
    // dependsOn(configurations.runtimeClasspath)
    // from({
    //     configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
    // })
}

3. 修正主程序服务查找逻辑

使用框架级别的BundleContext,通过接口全限定名查找服务,避免类加载器冲突:

class MathOperationProgram {

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val frameworkFactory = FrameworkFactory()
            val framework = frameworkFactory.newFramework(null)
            framework.init()
            try {
                framework.start()
                val bundleContext = framework.bundleContext

                // 若API模块也是OSGi bundle,先安装启动(否则确保主程序已依赖API模块)
                // bundleContext.installBundle("file:/path/to/math-api.jar").start()
                
                val mathBundle: Bundle =
                    bundleContext.installBundle("file:/path/to/mathBundle.jar")
                mathBundle.start()

                // 用接口全限定名获取服务引用,使用框架的BundleContext
                val serviceRef = bundleContext.getServiceReference("bundles.service.MathOperationsService")
                val mathOperationsService = bundleContext.getService(serviceRef) as? MathOperationsService

                if (mathOperationsService != null) {
                    val result = mathOperationsService.calculate(3, 4)
                    println("Result: $result")
                } else {
                    println("Failed to retrieve MathOperationsService")
                }
            } catch (e: Exception) {
                println("Error: ${e.message}")
                e.printStackTrace()
            } finally {
                framework.stop()
                framework.waitForStop(0)
            }
        }
    }
}

4. 确保类加载一致性

  • 主程序仅依赖math-api模块,不包含服务实现类
  • mathBundle依赖math-api模块,仅包含服务实现和激活器代码

Kotlin/OSGi 集成实践建议

  1. 模块拆分规范:始终将接口与实现分离,API模块独立,业务模块依赖API,从根源避免类重复加载问题
  2. 服务操作规范:
    • 注册服务时优先传入接口类对象(MathOperationsService::class.java),减少字符串拼写错误
    • 查找服务使用框架级BundleContext,确保跨bundle服务可见
  3. 打包工具优化:使用OSGi专用Gradle插件(如org.osgi)自动生成Manifest属性,避免手动配置出错
  4. Kotlin特性适配:确保Kotlin类编译为Java兼容字节码(默认支持),激活器类保持无参构造函数,符合OSGi规范

内容的提问来源于stack exchange,提问作者NirYudo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 01:20:54