基于sofaark轻松实现不同版本jar包类隔离机制

背景

我们在开发java应用工程的时候,随着技术的迭代升级,免不得升级/添加我们的工程依赖也就是jar包,但是升级/添加依赖jar包随之而来的是包冲突原有业务代码的大量修改,应用工程变得臃肿等令开发人员头大的问题.
基于sofaark轻松实现不同版本jar包类隔离机制_第1张图片
所以每次去做升级依赖,那个依赖不向下兼容的的时候,老子慌一批!

作为一个称职的开发,肯定是挠秃头也要想个办法解决问题的

问题分析

首先基于背景分析现状,现状就是在对应用的某些功能进行升级的时候,会出现包冲突等问题,导致开发以及维护的复杂度增加.

在解决升级问题的时候,主要有两种方案:

  • 当我们直接升级包依赖版本的时候,会导致如下状况:
    基于sofaark轻松实现不同版本jar包类隔离机制_第2张图片
    很明显直接升级的成本是很高的,于是在考虑复杂度跟成本的情况下是尽量不使用这种方案.
  • 微服务化,一个版本一个服务
     不是不可以,就是有点杀鸡用牛刀的感觉,除非您的版本影响的业务功能极其庞大
     在这里插入图片描述在这里插入图片描述
  • 让sdk-1.0与sdk-2.0并存项目,会导致如下状况:

基于sofaark轻松实现不同版本jar包类隔离机制_第3张图片
在选择并存的方案的时候,最棘手的就是包冲突的问题,最好的情况就是只需要排除冲突就可以了,但是最坏的情况是因为是同类jar包但不同版本,在引入同一个类的时候,很容易出现java.lang.NoClassDefFoundErrorClassNotFoundException问题.

主要是因为从类加载机制分析,在 Java 中,所有的类默认通过 ClassLoader 加载,而 Java 默认提供了三层的 ClassLoader,并通过双亲委托模型的原则进行加载.

双亲委托模型如下:
基于sofaark轻松实现不同版本jar包类隔离机制_第4张图片

双亲委派模型的工作流程是:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此。因此,所有的加载请求都应当传送到顶层的BootStrap加载器中,只有当父加载器反馈无法完成这个加载请求时(在自己搜索范围中没有找到此类),子加载器才会尝试自己去加载

在我们没有对jar包的类加载器进行自定义的时候,我们对jar的类进行调用的时候,会去到默认的父类加载器Bootstrap ClassLoader,加载我们调用的类,由于是同类jar包不同版本,Bootstrap ClassLoader未必会加载到我们想要的类,因此会出现java.lang.NoClassDefFoundErrorClassNotFoundException问题.

针对这个问题,我们需要做的是基于双亲委派模型,自定义classloader使得两个版本的jar包是使用两个不同的classloader,我们调用的时候使用自定义的classloader进行调用就可以实现多个同类但不同版本的jar包并存.

基于sofaark轻松实现不同版本jar包类隔离机制_第5张图片

类隔离实现方案

自定义classloader实现类隔离

自定义classloader就是上面说的实现方向,此处不做展开,需要的朋友可以参考:

https://www.cnblogs.com/silyvin/p/12174761.html

https://blog.csdn.net/t894690230/article/details/73252331

基于sofaark实现隔离

造轮子不是不可以,但是如果有现有的轮子,确实可以一试.

SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要提供类隔离和应用(模块)动态部署能力;基于 Fat Jar 技术,可以将多个应用(模块)打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot/SOFABoot 应用.

sofaark类隔离原理

SOFAArk 框架包含有三个概念,Ark Container, Ark PluginArk Biz; 运行时逻辑结构图如下:

基于sofaark轻松实现不同版本jar包类隔离机制_第6张图片
以下我们针对这三个概念简单做下名词解释:

Ark Container: Ark 容器,负责整个运行时的管理;Ark Plugin 和 Ark Biz 运行在 Ark 容器之上;容器具备管理多插件、多应用的功能;容器启动成功后,会自动解析 classpath 包含的 Ark Plugin 和 Ark Biz 依赖,完成隔离加载并按优先级依次启动之;

Ark Plugin: Ark 插件,满足特定目录格式要求的 Fat Jar,使用官方提供的 Maven 插件 sofa-ark-plugin-maven-plugin 可以将一个或多个普通的 Java Jar 包打包成一个标准格式的 Ark Plugin; Ark Plugin 会包含一份配置文件,通常包括插件类导入导出配置、插件启动优先级等;运行时,Ark 容器会使用独立的PluginClassLoader加载插件,并根据插件配置构建类加载索引表,从而使插件与插件、插件与应用之间相互隔离

Ark Biz: Ark 业务模块,满足特定目录格式要求的 Fat Jar ,使用官方提供的 Maven 插件 sofa-ark-maven-plugin 可以将工程应用打包成一个标准格式的 Ark-Biz 包;是工程应用模块及其依赖包的组织单元,包含应用启动所需的所有依赖和配置

在运行时,Ark Container 优先启动,自动解析 classpath 包含的 Ark Plugin 和 Ark Biz,并读取他们的配置,构建类加载索引关系;然后使用独立的 ClassLoader 加载他们并按优先级配置依次启动;需要指出的是,Ark Plugin 优先 Ark Biz 被加载启动;Ark Plugin 之间是双向类索引关系,即可以相互委托对方加载所需的类;Ark Plugin 和 Ark Biz 是单向类索引关系,即只允许 Ark Biz 索引 Ark Plugin 加载的类,反之则不允许。

详细解释可以参考:https://gitee.com/sofastack/sofa-ark

具体实现

我们将sdk的两个版本分别封装成两个ark-plugin工程,通过导出类/包的方式,让顶层应用程序识别为来自不同plugin,实现两个sdk.jar的隔离.

基于sofaark轻松实现不同版本jar包类隔离机制_第7张图片

工程结构

ark-demo
│   README.md
│   pom.xml
│
└───ark-pluginV1
│
└───ark-pluginV2
│
└───ark-facade
│
└───ark-plarform

ark-demo: 总工程
ark-pluginV1: 封装v1版本的ark-plugin,依赖sdk-1.0
ark-pluginV2: 封装v2版本的ark-plugin,依赖sdk-2.0
ark-facade: 项目接口工程,封装接口与bean
ark-plarform: 原应用工程

构建ark-plugin

1.编辑pom.xml添加1.0依赖

<dependencies>
		<dependency>
			<groupId>org.hyperledger.fabric-sdk-javagroupId>
			<artifactId>fabric-sdk-javaartifactId>
			<version>1.0.0version>
		dependency>
dependencies>

2.编辑pom.xml添加ark-plugin插件

<build>
		<plugins>
			 <plugin>
                <groupId>com.alipay.sofagroupId>
                <artifactId>sofa-ark-plugin-maven-pluginartifactId>
                <version>1.1.1version>
                <executions>
                    <execution>
                        <id>default-cliid>
                        <goals>
                            <goal>ark-plugingoal>
                        goals>
                        <configuration>
                            <classifier>ark-pluginclassifier>
                            <exported>
                                <classes>
                                    <class>com.demo.SDKTestV1class>
                                classes>
                            exported>
                        configuration>
                    execution>
                executions>
            plugin>
		plugins>
	build>

com.demo.SDKTestV1为调用sdk-1.0.0/sdk-1.0.0的相关类.

V2的构建同理.

应用工程引入ark-plugin

1.编辑pom.xml添加1.0依赖

		<dependency>
			<groupId>com.demogroupId>
			<artifactId>ark-pluginV1artifactId>
			<version>1.0.0.RELEASEversion>
			<classifier>ark-pluginclassifier>
		dependency>

注意这里classifier即在应用工程识别为ark-plugin的标志.
由于IDE是无法识别classifier,因此为了可调试需要再次引入plugin的依赖,并且定义scope为provided

<dependency>
			<groupId>com.demogroupId>
			<artifactId>ark-pluginV1artifactId>
			<version>1.0.0.RELEASEversion>
			<scope>providedscope>
		dependency>

V2的引入同理.

原springboot应用工程转ark工程

依赖包版本说明:

依赖包 版本
springboot 2.1.0.RELEASE
sofa-ark 1.1.1

原springboot应用工程转ark工程只需要在工程pom.xml引入以下依赖即可:

		<dependency>
			<groupId>com.alipay.sofagroupId>
			<artifactId>sofa-ark-springboot-starterartifactId>
			<version>1.1.1version>
		dependency>

添加打包插件


<build>
		<plugins>
			<plugin>
				<groupId>com.alipay.sofagroupId>
				<artifactId>sofa-ark-maven-pluginartifactId>
				<version>1.1.1version>
				<executions>
					<execution>
						<id>default-cliid>
						
						<goals>
							<goal>repackagegoal>
						goals>
						<configuration>
							
							<outputDirectory>./targetoutputDirectory>
							
							<arkClassifier>executable-arkarkClassifier>
						configuration>
					execution>
				executions>
			plugin>
		plugins>
	build>

打包运行工程说明

最后执行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运行即可.

我们在宿主应用即platform 以及两个plugin 里面输出他们的classloader

platform:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
		System.out.println("DemoApplication:"+DemoApplication.class.getClassLoader());
        new SDKTestV1().test();
		new SDKTestV2().test();
    }

}

plugin:

public class SDKTestV1 {
	public  void test() {
		System.out.println("i am version 1.0.0 plugin");
		System.out.println(this.getClass().getClassLoader());

	}
}

ark-platform-2.0.1.RELEASE-executable-ark.jar运行结果:

i am platform
DemoApplication:com.alipay.sofa.ark.container.service.classloader.BizClassLoader@36aa7bc2
i am version 1.0.0 plugin
com.alipay.sofa.ark.container.service.classloader.PluginClassLoader@518caac3
i am version 2.0.0 plugin
com.alipay.sofa.ark.container.service.classloader.PluginClassLoader@4f74980d

可以看到宿主应用platform的classloader是BizClassLoader,plugin的classlader是PluginClassLoader,不同的plugin他们的类加载器也不一样.

运行原理参考: https://juejin.im/post/5b6b982451882569fd2898d7

测试

@RunWith(ArkBootRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class TestSDK {
    public static final String TEST_CLASSLOADER = "com.alipay.sofa.ark.container.test.TestClassLoader";

	@Test
	public void test(){
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        ClassLoader loader = this.getClass().getClassLoader();
        Assert.assertTrue(tccl.equals(loader));
        Assert.assertTrue(tccl.getClass().getCanonicalName().equals(TEST_CLASSLOADER));
	}
}

你可能感兴趣的:(Java)