Skip to content

DeclaredMode: bizClassLoader delegate to Master ClassLoader only declared in biz module

Past due by about 1 year 88% complete

类委托加载

注:这里委托加载不止包含类委托,还包含资源委托加载。

SOFAArk 通过多 ClassLoader 的方式提供类隔离的能力,并在此基础上具备多版本隔离和热部署的能力。当然类如果纯粹隔离还不够,还需要考虑ClassLoader之间的类共享。SOFAArk 通过类委托机制,提供了一套丰富的委托加载能力。定义了PluginClassLoader 和 BizClassLoader,不同类型具备不同的类查找逻辑,同时提供ClassLoaderHook能力,允许自定义模块间的类委托加载。SOFAArk 框架本身的类委托加载是非常灵活的,但是在实际落地时,需要有一套类委托加载的最佳实践。

SOFAArk 支持的场景主要分为两类:

  1. 多Plugin的类隔离:考虑如何相同类不同版本不会相互冲突
  2. 合并部署/热部…

类委托加载

注:这里委托加载不止包含类委托,还包含资源委托加载。

SOFAArk 通过多 ClassLoader 的方式提供类隔离的能力,并在此基础上具备多版本隔离和热部署的能力。当然类如果纯粹隔离还不够,还需要考虑ClassLoader之间的类共享。SOFAArk 通过类委托机制,提供了一套丰富的委托加载能力。定义了PluginClassLoader 和 BizClassLoader,不同类型具备不同的类查找逻辑,同时提供ClassLoaderHook能力,允许自定义模块间的类委托加载。SOFAArk 框架本身的类委托加载是非常灵活的,但是在实际落地时,需要有一套类委托加载的最佳实践。

SOFAArk 支持的场景主要分为两类:

  1. 多Plugin的类隔离:考虑如何相同类不同版本不会相互冲突
  2. 合并部署/热部署:原应用如何拆分,或多个应用如何合并

这里我们主要考虑第2种,多 ClassLoader 如何布局,在内部探索出的最佳实践多 ClassLoader 布局为
image

模块里的类和资源查找优先从模块里查找列,查找不到从再基座里查找。

最佳实践
类委托加载的准则是中间件相关的依赖需要放在同一个的 ClassLoader 里进行加载执行,达到这种方式的最佳实践有两种:

强制委托加载

由于中间件相关的依赖一般需要在同一个ClassLoader里加载运行,所以可以制定一个中间件依赖的白名单,强制这些依赖委托给基座加载。SOFAArk 已经提供白名单能力,详细查看与 sofa.ark.plugin.export.class.enable 有关源码。

强制加载优缺点

  • 优点

模块开发者不需要感知哪些依赖属于需要强制加载由同一个 ClassLoader 加载的依赖

  • 缺点

白名单里要强制加载的依赖列表需要维护,列表的缺失需要更新基座,较为重要的升级需要推所有的基座升级。

自定义委托加载

模块里pom通过设置依赖的 scope 为 provided主动指定哪些要委托给基座加载。通过模块瘦身工具自动把与基座重复的依赖委托给基座加载,基座通过预置中间件的依赖(可选,虽然模块暂时不会用到,但可以提前引入,以备后续模块需要引入的时候不需再发布基座即可引入)

1. 基座预置依赖

基座可以提前预置一些依赖

2. 通过两种方式委托给基座加载

  • 模块pom里依赖 scope 设置为 provided
  • biz 打包插件sofa-ark-maven-plugin里设置 excludeGroupIds 或 excludeArtifactIds

3. 只有模块声明过的依赖才可以委托给基座加载

模块启动的时候,框架会有一些扫描逻辑,这些扫描如果不做限制会查找到模块和基座的所有类、资源,导致一些模块明明不需要的功能尝试去初始化,从而报错。SOFAArk 2.0.3之后新增了模块的 declaredMode, 来限制只有模块里声明过的依赖才可以委托给基座加载。只需在模块的 sofa-ark-maven-plugin 打包插件的 Configurations 里增加 true即可。

自定义委托加载优缺点

  • 优点

不需要维护强制加载列表,当部分需要由同一 ClassLoader 加载的依赖没有设置为统一加载时,可以修改模块就可以修复,不需要发布基座(除非基座确实依赖)。

  • 缺点

对模块自动瘦身的依赖较强

对比与总结

依赖缺失排查成本 修复成本 模块改造成本 维护成本
强制加载 类转换失败或类查找失败,成本中 更新 plugin,发布基座,高
自定义委托加载 类转换失败或类查找失败,成本中 更新模块依赖,如果基座依赖不足,需要更新基座并发布,中
自定义委托加载 + 基座预置依赖 + 模块自动瘦身 类转换失败或类查找失败,成本中 更新模块依赖,设置为provided,低

推荐自定义委托加载方式

  1. 模块自定义委托加载 + 模块自动瘦身
  2. 模块开启 declaredMode
  3. 基座预置依赖

关于 declaredMode 的逻辑介绍:

模块打包插件,需要通过执行 mvn dependency:tree 获取模块的所有依赖列表
模块启动或运行时查找类和资源时,找到对应基座的jar,提取jar里的artifactId,跟打包时获取到的依赖进行对比,如果模块里有声明,则委托加载成功,如果模块里没声明,则委托加载失败。
这段逻辑有两个关键地方,一是模块打包扫描的依赖是否完整,而是从基座jar里提取 artifactId 是否正确,这两处如果有任何一处有疏漏都会出现你这种情况。这段逻辑当前是不够完善的,希望一起把它完善起来。

开启后的副作用

  1. 由于基座提前启动,模块再次启动的时候,如果依赖了基座的依赖里有发布服务,那么模块在启动的时候还会再次发布,如果该问题有一定影响可以模块pom里exclude掉对应的依赖。
  2. 模块启动会出现 Unable to instantiate org.springframework.boot.env.EnvironmentPostProcessor, 详见 #611
  3. 从基座里查找出来的依赖,需要判断是否是模块里声明的逻辑在 isDeclared() 内,需要准确提取出 jar 里的 groupdId + artifactId, 但当前只提取了 artifactId, 如果基座和模块里有 groupId 不同 artifactId 相同的依赖,会被误认为也是允许委托加载的。