sofastack / sofa-ark

SOFAArk is a light-weight,java based classloader isolation framework.
https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/
Apache License 2.0
1.57k stars 500 forks source link

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

Closed cngddflzw closed 5 years ago

cngddflzw commented 5 years ago

Your question

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

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

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

之前我看过 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

QilongZhang commented 5 years ago

先回答 Spring 相关的问题,目前采用两种方案:

QilongZhang commented 5 years ago

除了 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 的导出类最好是面向接口的,而且只导出和本插件自身类,不到处插件依赖的间接二方包类。

cngddflzw commented 5 years ago

除了 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 来避免这个问题, 你觉得合理吗

QilongZhang commented 5 years ago

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

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

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

cngddflzw commented 5 years ago

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

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

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

QilongZhang commented 5 years ago

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

nArKr commented 5 years ago

这个后来怎么解决的

cngddflzw commented 5 years ago

这个后来怎么解决的

手动设置上下文 classloader 吧