蚂蚁金服SOFAArk框架解决spring boot多模块包冲突
背景
在菊花云上进行开发,使用它们的Hbase服务过程中,由于其版本国语古老,所使用的依赖包也是非常早期的版本,导致和目前整体程序开发框架有较大的冲突,一个个排除冲突包工作量大,效率低。灵鸡一动,考虑到国内大厂复杂的业务场景,铁定有类似的情况并且开发了相应的开源解决方案,所以搜了一下,找到了蚂蚁金服开发的SOFAArk。
SOFAArk
官网地址:https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/
github地址: https://github.com/sofastack/sofa-ark
SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,主要提供类隔离和应用(模块)合并部署能力,由蚂蚁金服公司开源贡献;
在大型软件开发过程中,通常会推荐底层功能插件化,业务功能模块化的开发模式,以期达到低耦合、高内聚、功能复用的优点。基于此,SOFAArk 提供了一套较为规范化的插件化、模块化的开发方案,产品能力主要包括:
- 定义类加载模型,运行时底层插件、业务应用(模块)之间均相互隔离,单一插件和应用(模块)由不同的 ClassLoader 加载,可以有效避免相互之间的包冲突,提升插件和模块功能复用能力;
- 定义插件开发规范,提供 maven 打包工具,简单快速将多个二方包打包成插件(Ark Plugin,以下简称 Plugin)
- 定义模块开发规范,提供 maven 打包工具,简单快速将应用打包成模块 (Ark Biz,以下简称 Biz)
- 针对 Plugin、Biz 提供标准的编程界面,包括服务、事件、扩展点等机制
支持多 Biz 的合并部署,开发阶段将多个 Biz 打包成可执行 Fat Jar,或者运行时使用 API 或配置中心(Zookeeper)动态地安装卸载 Biz
基于以上能力,SOFAArk 可以帮助解决依赖包冲突、多应用(模块)合并部署等场景问题。
假设如下场景,如果工程需要引入两个三方包:A 和 B,但是 A 需要依赖版本号为 0.1 的 C 包,而恰好 B 需要依赖版本号为 0.2 的 C 包,且 C 包的这两个版本无法兼容:

应用SOFAArk解决包冲突
先说下我的spring boot工程结构:
parent-proj 父工程
│
└───hbase-module hbase交互模块
│
└───app-module-1 应用模块1,依赖于hbase-module
│
└───app-module-2 应用模块2,依赖于hbase-module
│
└───app-module-3 应用模块3,依赖于hbase-module
由于hbase-module版本较早,导致其与其他模块存在冲突。分三步构建Ark应用:
打包Ark Plugin
在hbase-module模块的pom文件中加入:
<build>
<plugins>
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-plugin-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>ark-plugin</goal>
</goals>
<configuration>
<classifier>ark-plugin</classifier>
<exported>
<classes>
<class>com.demo.SDKTestV1</class>
</classes>
</exported>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
现实开发中,常常会遇到依赖包冲突的情况;假设我们开发了一个类库 sample-lib , 业务应用在引入使用时,可能存在跟已有的依赖发生冲突的情况;通常这个时候,我们会希望自己的类库能够和业务其他依赖进行隔离,互不协商双方依赖包版本。 Ark Plugin 正是基于这种需求背景下的实践产物; Ark Plugin 运行在 Ark Container 之上,由容器负责加载启动,任何一个 Ark Plugin 由独立的 ClassLoader 加载,从而做到相互隔离。Ark Plugin 存在四个概念: * 导入类:插件启动时,优先委托给导出该类的插件负责加载,如果加载不到,才会尝试从本插件内部加载;
打包 Ark 包
在各app-module的pom文件中加入依赖,引入hbase-module:
<dependency>
<groupId>com.demo</groupId>
<artifactId>hbase-module</artifactId>
<version>1.0.0.RELEASE</version>
<classifier>ark-plugin</classifier>
</dependency>
注意这里classifier即在应用工程识别为ark-plugin的标志.
由于IDE是无法识别classifier,因此为了可调试需要再次引入plugin的依赖,并且定义scope为provided
<!--注意正式生产环境中一定要去掉,否则会报类重复加载错误-->
<dependency>
<groupId>com.demo</groupId>
<artifactId>hbase-module</artifactId>
<version>1.0.0.RELEASE</version>
<scope>provided</scope>
</dependency>
springboot工程还需引入:
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-springboot-starter</artifactId>
<version>1.1.1</version>
</dependency>
最后添加打包插件:
<build>
<plugins>
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<id>default-cli</id>
<!--goal executed to generate executable-ark-jar -->
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<!--specify destination where executable-ark-jar will be saved, default
saved to ${project.build.directory} -->
<outputDirectory>./target</outputDirectory>
<!--default none, 可执行fat jar -->
<arkClassifier>executable-ark</arkClassifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
其他配置
官网说如果是普通java工程,需要在工程 main 方法最开始处,执行容器启动,如下:但是我的springboot工程没加也会报错,加了没事,这里有点疑问。
另外如果要在app-module里面使用自动注入@Autowired hbase-module里面的东西,需要加上@ComponentScan扫描对应的包路径。
@ComponentScan({"com.demo.hbase"})
public class Application{
public static void main(String[] args) {
SofaArkBootstrap.launch(args);
...
}
}
最后执行mvn clean package进行打包的时候,会生成以下3个jar包
ark-platform-2.0.1.RELEASE-ark-biz.jar : biz包 有合并部署的时候使用
ark-platform-2.0.1.RELEASE-executable-ark.jar:ark可执行应用工程
ark-platform-2.0.1.RELEASE.jar:普通工程
使用ark-platform-2.0.1.RELEASE-executable-ark.jar运行即可.
jvm类加载双亲委派机制
从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。
从开发者的角度,类加载器可以细分为:
- 启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。
除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
参考文章:https://blog.csdn.net/qq_28540443/article/details/106399367
官网地址:https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/
github地址: https://github.com/sofastack/sofa-ark
更多推荐
所有评论(0)