背景

在菊花云上进行开发,使用它们的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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐