一.使用方法及示例
简介:当引入二方依赖包或三方依赖包时,可能出现外部依赖jar包与自己的工程需要依赖的冲突,或者多个二方三方依赖包互相冲突。这时候就需要一个隔离容器对他们进行隔离,其依赖的原理就是jvm认为不同classloader加载的类即使包名类名相同,也认为他们是不同的。sofa-ark将需要隔离的jar包打成plugin,对每个plugin都用独立的classloader去加载。
(温馨提示:若对sofa-ark不太了解的,最好先去看看官方文档,简单了解下)
使用的基本步骤:
- 在会发生冲突的jar包的POM文件加入sofa-ark提供的maven插件,将其打成特定格式的jar包(plugin)。
- 在外部工程按照约定引入jar包。如果外部工程想打包成可执行的jar(fat-jar),还需要加入特定的maven插件。
- 直接运行。
名词解释:
- Ark Container: Ark 容器,是组件 SOFAArk 的核心,运行 Ark 包时,Ark 容器会最先启动,负责应用运行时的管理,主要包括构建 Ark Plugin 和 Ark Biz 的类导入导出关系表、启动并初始化 Ark Plugin 和 Ark Biz、管理 Ark Plugin 服务的发布和引用等等。
- Biz:即业务工程,该工程引用一个或多个外部jar包。
- plugin:会发生冲突的外部依赖jar包经过提供的Maven插件打成的fat-jar包。运行时,由独立的类加载器加载,因此有隔离需求的 Jar 包建议打包成 Ark Plugin 供应用依赖。
1.0 sofa-ark原理
在sofa-ark中,使用container容器启动外部工程(Biz)和冲突jar包(plugin),sofa-ark的plugin maven插件将冲突的jar包打包成为plugin,外部工程只能引用plugin exported出来的类,并且这个类是由独立的PluginClassLoader加载的,从而解决了jar包冲突的问题。

1.1 冲突示例

1.2 安装sofa-ark
从sofa-ark官网 将整个工程下载下来,在最外层pom.xml所在路径执行mvn package和mvn install 将sofa-ark依赖jar包安装到本地。
1.3 创建基础依赖Jar包(冲突的包)
自己创建myjar工程,先后正常打包两个版本安装到本地maven仓库。
如:v1版本

v2版本

1.4 创建service包(plugin)
创建如图所示两个工程:

在myJarservice-v1和myJarservice-v2的pom中分别引用之前写的两个基础依赖jar包。然后在MyJarService1和MyJarService2中分别引用对应版本的myjar包里的方法。
如MyJarService1.java:

MyJarService2.java

注意:这里两个Service引用的是不同版本的myjar。
接下来需要对两个工程打包,和平常打包不一样的是需要加入sofa-ark-plugin的maven插件。两个工程下的pom.xml都要加入:
1 | <build> |
在exported标签里写出要对外提供的方法,外部要引用的所有方法都必须写在这里,可以以类(<classes>)为单位和包(packages)为单位导出。
到parent工程路径下mvn package和mvn install 即可。
1.5 外部工程引用(Biz)
新建一个工程,在pom.xml引入以下依赖:
1 | <dependencies> |
注意要添加<classifier>标签,因为IDE识别不了ark-plugin的jar包,所以需要再引入一个范围为provided的jar包。
然后pom.xml还要添加maven插件:
1 | <build> |
然后再随便写个类引入两个版本service包的类使用即可,在main函数入口处需要加上一句:
SofaArkBootstrap.launch(args);

可以看到打印结果:

二.sofa-ark-plugin-maven-plugin插件原理分析
2.1 查看plugin jar包内容
使用sofa-ark-plugin-maven-plugin Maven插件即可将jar包打包成可在container中隔离加载的jar包(如myjarservice-v1-1.0-ark-plugin.jarr和myjarservice-v2-1.0-ark-plugin.jar)。进入本地maven仓库myJarservice-v1工程所在位置,可以看到maven打了两个包:一个是maven自带的插件打的普通的包:myjarservice-v1-1.0.jar ,另一个是sofa-ark提供的maven插件打的包myjarservice-v1-1.0-ark-plugin.jar,打开myjarservice-v1-1.0-ark-plugin.jar 可以看到如下目录:

相关目录说明:
com/alipay/sofa/ark/plugin/mark:标记文件,标记该 Jar 包是sofa-ark-plugin-maven-plugin打包生成的Ark Plugin文件。META-INF/MANIFEST.MF:记录插件元信息,其中包含要导出的和导入的类。
1 | Manifest-Version: 1.0 |
conf/export.index:插件导出类索引文件;为了避免在运行时计算MANIFEST.MF中export-packages下面具体的导出类,在打包生成Ark Plugin时,会生成插件所有导出类的索引文件,缩短Ark Container解析配置时间。lib/: lib 目录存放插件工程依赖的普通 Jar 包,一般包含插件需要和其他插件或者业务有隔离需求的 Jar 包;插件配置的导出类都包含在这些 Jar 包中。
2.2 分析sofa-ark-plugin-maven-plugin源码
进入下载的sofa-ark源码中的ark-plugin-maven-plugin工程,可以看到ArkPluginMojo继承了
1 | (name = "ark-plugin", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME) |
@Mojo 就是一个 goal,可以绑定到某个 phase (这里是package)执行,@Parameter 是从外面传进来的参数,可以直接获取xml中配置的参数。Maven插件标准要求必须重写execute()方法,插件执行时主要就是执行execute()。
1 |
|
这里的将execute()方法进行了精简,这个方法主要做了一下几件事情:
- 建立一个zip格式的归档,用来保存引入的jar包和其他文件,建立输出路径。
- 获取引入的所有依赖(Artifacts),并且将需要exclude的包排除出去。
- 将所有依赖写入zip归档中的lib目录下

- 将配置信息写入zip归档中,包括之前提到的
export.index,MANIFEST.MF,mark
经过上述步骤后即把依赖的dependence和配置文件都写入zip中了,然后将其转换为jar后缀即可。
三. Sofa-ark原理分析
3.1 初始化ArkContainer
前面讲解了plugin插件如何工作,这节讲述外部工程是如何引用运用plugin插件打包而成的plugin jar包来解决隔离冲突的。可以从main方法加入的SofaArkBootstrap.launch(args)进行单步跟踪,这句代码主要是将Container启动起来,然后让Container去加载Plugin和Biz。在launch方法里通过反射调用了SofaArkBootstrap 的remain方法,在remain方法里主要干了两件事:
1 | private static void remain(String[] args) throws Exception {// NOPMD |
- 获取classpath下的所有jar包,包括jdk自己的jar包和maven引入的jar包。
- 将所有依赖jar包和自己写的启动类及其main函数以url的形式传入
ClasspathLauncher,ClasspathLauncher反射调用ArkContainer的main方法,并且使用ContainerClassLoader加载ArkContainer。至此,就开始启动ArkContainer了。
3.2 启动ArkContainer
接着就运行到了ArkContainer中的main方法,传入的参数args即之前ClasspathLauncher传入的url
1 | public static Object main(String[] args) throws ArkException |
这个方法主要做了一下几件事:
- 使用
LaunchCommand将传入的参数分类,将classpath的url和自己写的启动类的main方法提取出来
- 将
LaunchCommand传入ArkContainer并启动:
在ArkContainer.start()中:
1 | public Object start() throws ArkException |
arkServiceContainer中包含了一些Container启动前需要运行的Service,这些Service被封装到一个个的PipelineStage中,这些PipelineStage又被封装成List到一个pipeline中。主要包含这么几个PipelineStage,依次执行:
HandleArchiveStage筛选所有第三方jar包中含有mark标记的plugin jar,说明这些jar是sofa ark maven插件打包成的需要隔离的jar。从jar中的export.index中提取需要隔离的类,把他们加入一个PluginList中,并给每个plugin,分配一个独立的PluginClassLoader。同时以同样的操作给Biz也分配一个BizClassLoaderDeployPluginStage创建一个map,key是需要隔离的类,value是这个加载这个类使用的PluginClassLoader实例。DeployBizStage使用BizClassLoader反射调用Biz的main方法。
至此,Container就启动完了。后面再调用需要隔离的类时,由于启动Biz的线程已经被换成了BizClassLoader,在loadClass时BizClassLoader会首先看看在DeployPluginStage创建的Map中是否有PluginClassLoader能加载这个类,如果能就委托PluginClassLoader加载。就实现了不同类使用不同的类加载器加载。