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中,不同类加载器加载的类属于不同类型,导致实现类无法转换为主程序中的接口实例
具体诱因:
- Gradle打包时将
bundles.service包的接口类也打进了mathBundle,同时主程序本身也包含该接口 - 主程序直接使用本地的
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 集成实践建议
- 模块拆分规范:始终将接口与实现分离,API模块独立,业务模块依赖API,从根源避免类重复加载问题
- 服务操作规范:
- 注册服务时优先传入接口类对象(
MathOperationsService::class.java),减少字符串拼写错误 - 查找服务使用框架级BundleContext,确保跨bundle服务可见
- 注册服务时优先传入接口类对象(
- 打包工具优化:使用OSGi专用Gradle插件(如
org.osgi)自动生成Manifest属性,避免手动配置出错 - Kotlin特性适配:确保Kotlin类编译为Java兼容字节码(默认支持),激活器类保持无参构造函数,符合OSGi规范
内容的提问来源于stack exchange,提问作者NirYudo
相关产品推荐
相关产品推荐

