weixin_39526546
weixin_39526546
2020-11-23 10:17

基础组件的 ark 化改造中, 依赖的第三方库应该如何正确配置 export 属性

Your question

我的场景是希望使用 ark 来改造基础组件客户端, 隔离基础组件与业务代码之间的第三方依赖冲突, 以此推动基础组件的自动升级.

举个例子, 我的配置中心的客户端, 我们设计很多自定义的 annotation 用来注入远程配置, 例如 ("${k1}") 这样的使用方式, 即可将远程配置注入变量中. 而这些 annotation 的生效, 则依赖于 spring 的一些开关, 例如 以及 ImportAware 等等

假如我直接将整个 client jar 包的类做 ark 导出处理, 例如我在 export 配种配置 package 为 com.test.configcenter.client.* 这样, 则会导致这些 失效

之前我看过 issue 中有提到关于处理 spring 的问题, 作者给出的两种选择是 1. 去 spring 化 (显然这里不合适, 我们就是要用 spring 的 annotation scan 功能) 2. 将 spring 下沉为另一个 ark plugin, 让业务和 plugin 的 spring 类都从该 plugin 中导出

在此我有几个问题, 希望可以帮忙看看

  1. ark 化以后的注解失效, 应该是因为业务项目启动的 spring context 相关的类都是由 BizClassLoader 加载, 而 Plugin 中的 spring 相关的类都由 PluginClassLoader 加载, 导致认不出来这些注解?

  2. spring 下沉为另一个 ark plugin 的做法, 是否就是将依赖的 spring 包都放到一个项目的 pom 的 dependency 中, 然后在 ark 的配置中配置 exported 的 package 为 org.springframework.* ?

  3. 下沉 spring 这种处理与业务有交互的第三方库, 感觉也比较难完备的解决业务与插件出现同一个类型的不同 classLoader 交互问题, 除了 spring 之外, 我还遇到一些第三方库, 里面出现了 Class.forName("MyClass", true, Thread.currentThread().getContextClassLoader()).asSubclass(type) 这样的代码, 其中 MyClass 是基础组件 plugin 中的某个类, 而 type 是 MyClass 的一个 Class 类型引用 (Class type = MyClass.class) 由于上下文的 ClassLoader 是 BizClassLoader, type 的 ClassLoader 是 PluginClassLoader, 会导致 asSubclass() 失败, 类型不匹配 我想问的是第三方库中可能会存在很多这种隐式交互, 并且第一次发布的时候可能发现不了 (因为代码可能没跑到那个分支, 就不会触发异常), 有没有比较好的方法来比较全面的规避这种 BizClassLoader 和 PluginClassLoader 出现交互, 导致类型不匹配的问题?

Environment

  • SOFAArk version: 0.3.0
  • JVM version (e.g. java -version): 1.8
  • OS version (e.g. uname -a): MacOS 10.13.6
  • Maven version: 3.5.2
  • IDE version: Intellij Idea 2018.3

该提问来源于开源项目:sofastack/sofa-ark

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

8条回答

  • weixin_39997310 weixin_39997310 5月前

    先回答 Spring 相关的问题,目前采用两种方案: + 1、ark plugin 的去 spring 化,参考 SOFARPC, sofarpc 核心包是和spring无关的,但是为了支持注解及xml配置引用服务的形式,有单独的 rpc-sofa-boot-starter 工程,所有涉及spring的处理放在这个包中。在制作ark插件化时,rpc-sofa-boot-starter 是和 ark biz 依赖引入,共同有bizClassloader加载,而sofarpc 核心包及其间接依赖单独成为 ark plugin.

    • 2、扩展 pluginclassloader 逻辑,遇到spring相关类,委托给 biz classloader. 这样就统一了biz和plugin共同的spring,这种方案社区已经有公司反馈内部是这样改造的。
    点赞 评论 复制链接分享
  • weixin_39997310 weixin_39997310 5月前

    除了 spring 之外, 我还遇到一些第三方库, 里面出现了 Class.forName("MyClass", true, Thread.currentThread().getContextClassLoader()).asSubclass(type) 这样的代码, 其中 MyClass 是基础组件 plugin 中的某个类, 而 type 是 MyClass 的一个 Class 类型引用 (Class type = MyClass.class) 由于上下文的 ClassLoader 是 BizClassLoader, type 的 ClassLoader 是 PluginClassLoader, 会导致 asSubclass() 失败, 类型不匹配

    目前plugin 和 biz 启动时,thread context classloader 都会设置成pluginclassloader 或者 bizclassloader, 你上面说的问题应该是对应的父类没有导出所致,通常来说,我们建议每个 Plugin 的导出类最好是面向接口的,而且只导出和本插件自身类,不到处插件依赖的间接二方包类。

    点赞 评论 复制链接分享
  • weixin_39526546 weixin_39526546 5月前

    除了 spring 之外, 我还遇到一些第三方库, 里面出现了 Class.forName("MyClass", true, Thread.currentThread().getContextClassLoader()).asSubclass(type) 这样的代码, 其中 MyClass 是基础组件 plugin 中的某个类, 而 type 是 MyClass 的一个 Class 类型引用 (Class type = MyClass.class) 由于上下文的 ClassLoader 是 BizClassLoader, type 的 ClassLoader 是 PluginClassLoader, 会导致 asSubclass() 失败, 类型不匹配

    目前plugin 和 biz 启动时,thread context classloader 都会设置成pluginclassloader 或者 bizclassloader, 你上面说的问题应该是对应的父类没有导出所致,通常来说,我们建议每个 Plugin 的导出类最好是面向接口的,而且只导出和本插件自身类,不到处插件依赖的间接二方包类。

    你说的这点我没太明白, 我确实是没有导出间接依赖的包, 例如我就写了一个这样的配置

    
    <exported>
        <packages>
            <package>com.test.remoteconfig.client.*</package>
        </packages>
    </exported>
    

    将客户端的代码都导出了, 然而在执行到某个 Plugin 内部的导出类 com.test.remoteconfig.client.ConfigInitializer 的逻辑时候, 例如

    
    public void ConfigInitializer.init() {
                System.out.println(
                        Class.forName("com.google.common.collect.Lists", true, Thread.currentThread().getContextClassLoader())
                                .equals(com.google.common.collect.Lists.class));
    }
    

    如果 com.google.common.collect.Lists.class 不是导出类的话, 那么这个 equals() 应该是 false 吧, 如果遇到使用这种方法做的类型校验, 就会校验失败导致异常 这样会导致一些问题, 我的想法是给所有 plugin 导出类的 api 做一层代理, 在执行方法之前都先设置 Current Thread ClassLoader 为 PluginClassLoader 来避免这个问题, 你觉得合理吗

    点赞 评论 复制链接分享
  • weixin_39997310 weixin_39997310 5月前

    ConfigInitalizer.init 方法如果不是在插件启动执行,而是在启动 Biz 的时候触发的,这个时候 ContextClassLoader 是 BizClassLoader,是会存在问题。这种代码很难避免。 目前遇到的这种问题不多,唯一遇到的是 Log4j 配置,不过官方通过配置 ignoreTCL=true 避免,目前ark自动会配置这一项。

    这样会导致一些问题, 我的想法是给所有 plugin 导出类的 api 做一层代理, 在执行方法之前都先设置 Current Thread ClassLoader 为 PluginClassLoader 来避免这个问题, 你觉得合理吗

    额,这样的话你代码会很丑吧,建议在插件逻辑中较少使用 context loader 去处理

    点赞 评论 复制链接分享
  • weixin_39526546 weixin_39526546 5月前

    额,这样的话你代码会很丑吧,建议在插件逻辑中较少使用 context loader 去处理

    哈哈. 谢谢. 就是因为无法预知哪里会出现类似的问题, 为了一劳永逸, 我打算使用类似于 Javassist 之类的做个 ark 化的中间层, 让客户端 api 层 ark 化以后 client 实现自动包一层代理. 应该也不会很丑, 不需要修改原来的客户端.

    总之感谢你的回答, 谢谢~!

    点赞 评论 复制链接分享
  • weixin_39997310 weixin_39997310 5月前

    好的, 如果方案简单通用,可以贡献哈。 另外方便透露下你们公司吗?

    点赞 评论 复制链接分享
  • weixin_39895977 weixin_39895977 5月前

    这个后来怎么解决的

    点赞 评论 复制链接分享
  • weixin_39526546 weixin_39526546 5月前

    这个后来怎么解决的

    手动设置上下文 classloader 吧

    点赞 评论 复制链接分享

相关推荐