Java隔离容器之sofa-ark使用说明及源码解析

一.使用方法及示例

简介:当引入二方依赖包或三方依赖包时,可能出现外部依赖jar包与本身的工程须要依赖的冲突,或者多个二方三方依赖包互相冲突。这时候就须要一个隔离容器对他们进行隔离,其依赖的原理就是jvm认为不一样classloader加载的类即便包名类名相同,也认为他们是不一样的。sofa-ark将须要隔离的jar包打成plugin,对每一个plugin都用独立的classloader去加载。java

(舒适提示:若对sofa-ark不太了解的,最好先去看看官方文档,简单了解下)git

使用的基本步骤:github

  1. 在会发生冲突的jar包的POM文件加入sofa-ark提供的maven插件,将其打成特定格式的jar包(plugin)。
  2. 在外部工程按照约定引入jar包。若是外部工程想打包成可执行的jar(fat-jar),还须要加入特定的maven插件。
  3. 直接运行。

名词解释:jvm

  • 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包冲突的问题。maven

image-20180724204016919

1.1 冲突示例

jar冲突

1.2 安装sofa-ark

sofa-ark官网 将整个工程下载下来,在最外层pom.xml所在路径执行mvn packagemvn install 将sofa-ark依赖jar包安装到本地。ide

1.3 建立基础依赖Jar包(冲突的包)

本身建立myjar工程,前后正常打包两个版本安装到本地maven仓库。函数

如:v1版本ui

image-20180724185922039

v2版本this

image-20180724190102751

1.4 建立service包(plugin)

建立如图所示两个工程:url

myJarservice-v1myJarservice-v2的pom中分别引用以前写的两个基础依赖jar包。而后在MyJarService1MyJarService2中分别引用对应版本的myjar包里的方法。

如MyJarService1.java:

image-20180724190836319

MyJarService2.java

image-20180724191026890

注意:这里两个Service引用的是不一样版本的myjar。

接下来须要对两个工程打包,和日常打包不同的是须要加入sofa-ark-plugin的maven插件。两个工程下的pom.xml都要加入:

<build>
        <plugins>
            <plugin>
                <groupId>com.alipay.sofa</groupId>
                <artifactId>sofa-ark-plugin-maven-plugin</artifactId>
                <version>0.4.0-SNAPSHOT</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <goal>ark-plugin</goal>
                        </goals>

                        <configuration>
                            <!-- configure exported class -->
                            <exported>
                                <!-- configure class-level exported class -->
                                <classes>
                                 
           <class>com.netease.sofaservice.MyJar1Service</class>
                                </classes>
                            </exported>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
复制代码

exported标签里写出要对外提供的方法,外部要引用的全部方法都必须写在这里,能够以类(<classes>)为单位和包(packages)为单位导出。

到parent工程路径下mvn packagemvn install 便可。

1.5 外部工程引用(Biz)

新建一个工程,在pom.xml引入如下依赖:

<dependencies>
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>sofa-ark-support-starter</artifactId>
            <version>0.4.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v1</artifactId>
            <classifier>ark-plugin</classifier>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v2</artifactId>
            <classifier>ark-plugin</classifier>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v1</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>myjarservice-v2</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

复制代码

注意要添加<classifier>标签,由于IDE识别不了ark-plugin的jar包,因此须要再引入一个范围为provided的jar包。

而后pom.xml还要添加maven插件:

<build>
        <plugins>
            <plugin>
                <groupId>com.alipay.sofa</groupId>
                <artifactId>sofa-ark-maven-plugin</artifactId>
                <version>0.4.0-SNAPSHOT</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>./</outputDirectory>

                            <!--default none-->
                            <arkClassifier>executable-ark</arkClassifier>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
复制代码

而后再随便写个类引入两个版本service包的类使用便可,在main函数入口处须要加上一句:

SofaArkBootstrap.launch(args);

image-20180724203423465

能够看到打印结果:

image-20180724203546696

二.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 :记录插件元信息,其中包含要导出的和导入的类
Manifest-Version: 1.0
groupId: com.netease
artifactId: myjarservice-v1
version: 1.0
priority: 100
pluginName: myjarservice-v1
activator: 
import-packages: 
import-classes: 
import-resources: 
export-packages: 
export-classes: com.netease.sofaservice.MyJar1Service
export-resources: 
复制代码
  • 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继承了

@Mojo(name = "ark-plugin", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME)
public class ArkPluginMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.artifactId}")
    public String                   pluginName;

    @Parameter(defaultValue = "100", property = "sofa.ark.plugin.priority")
    protected Integer               priority;

    @Parameter
    protected String                activator;

    @Parameter
    protected ExportConfig          exported;

    @Parameter
    protected ImportConfig          imported;
...
复制代码

@Mojo 就是一个 goal,能够绑定到某个 phase (这里是package)执行,@Parameter 是从外面传进来的参数,能够直接获取xml中配置的参数。Maven插件标准要求必须重写execute()方法,插件执行时主要就是执行execute()

@Override
    public void execute() throws MojoExecutionException {
        Archiver archiver;//zip归档
        archiver = getArchiver();
        outputDirectory.mkdirs();
        String fileName = getFileName();
        File destination = new File(outputDirectory, fileName);
        archiver.setDestFile(destination);
        Set<Artifact> artifacts = project.getArtifacts();
        artifacts = filterExcludeArtifacts(artifacts);
        Set<Artifact> conflictArtifacts = filterConflictArtifacts(artifacts);
        addArkPluginArtifact(archiver, artifacts, conflictArtifacts);
        addArkPluginConfig(archiver);
      	archiver.createArchive();
        projectHelper.attachArtifact(project, destination, getClassifier());
    }
复制代码

这里的将execute()方法进行了精简,这个方法主要作了一下几件事情:

  1. 创建一个zip格式的归档,用来保存引入的jar包和其余文件,创建输出路径。
  2. 获取引入的全部依赖(Artifacts),而且将须要exclude的包排除出去。
  3. 将全部依赖写入zip归档中的lib目录下

image-20180725153520102

  1. 将配置信息写入zip归档中,包括以前提到的export.indexMANIFEST.MFmark
    image-20180725153659204

通过上述步骤后即把依赖的dependence和配置文件都写入zip中了,而后将其转换为jar后缀便可。

三. Sofa-ark原理分析

3.1 初始化ArkContainer

前面讲解了plugin插件如何工做,这节讲述外部工程是如何引用运用plugin插件打包而成的plugin jar包来解决隔离冲突的。能够从main方法加入的SofaArkBootstrap.launch(args)进行单步跟踪,这句代码主要是将Container启动起来,而后让Container去加载Plugin和Biz。在launch方法里经过反射调用了SofaArkBootstrapremain方法,在remain方法里主要干了两件事:

private static void remain(String[] args) throws Exception {// NOPMD
        URL[] urls = getURLClassPath();
        new ClasspathLauncher(new ClassPathArchive(urls)).launch(args, getClasspath(urls),
            entryMethod.getMethod());
    }
复制代码
  1. 获取classpath下的全部jar包,包括jdk本身的jar包和maven引入的jar包。
  2. 将全部依赖jar包和本身写的启动类及其main函数以url的形式传入ClasspathLauncherClasspathLauncher反射调用ArkContainermain方法,而且使用ContainerClassLoader加载ArkContainer。至此,就开始启动ArkContainer了。

3.2 启动ArkContainer

接着就运行到了ArkContainer中的main方法,传入的参数args即以前ClasspathLauncher传入的url

public static Object main(String[] args) throws ArkException {
            //使用LaunchCommand将传入的参数按类型分类
            LaunchCommand launchCommand = LaunchCommand.parse(args[ARK_COMMAND_ARG_INDEX], Arrays.copyOfRange(args, MINIMUM_ARGS_SIZE, args.length));
            //ClassPathArchive将传入依赖的Jar包分类,并提供得到plugin和biz的filter方法
            ClassPathArchive classPathArchive = new ClassPathArchive(launchCommand.getEntryClassName(), launchCommand.getEntryMethodName(), launchCommand.getEntryMethodDescriptor(), launchCommand.getClasspath());
            return new ArkContainer(classPathArchive, launchCommand).start();
            }
    }
复制代码

这个方法主要作了一下几件事:

  1. 使用LaunchCommand将传入的参数分类,将classpath的url和本身写的启动类的main方法提取出来
    image-20180726151249913
  2. LaunchCommand传入ArkContainer并启动:

ArkContainer.start()中:

public Object start() throws ArkException {
        if (started.compareAndSet(false, true))
        {
            arkServiceContainer.start();
            Pipeline pipeline = arkServiceContainer.getService(Pipeline.class);
            pipeline.process(pipelineContext);
        }
        return this;
    }
复制代码

arkServiceContainer中包含了一些Container启动前须要运行的Service,这些Service被封装到一个个的PipelineStage中,这些PipelineStage又被封装成List到一个pipeline中。主要包含这么几个PipelineStage,依次执行:

  1. HandleArchiveStage筛选全部第三方jar包中含有mark标记的plugin jar,说明这些jar是sofa ark maven插件打包成的须要隔离的jar。从jar中的export.index中提取须要隔离的类,把他们加入一个PluginList中,并给每一个plugin,分配一个独立的PluginClassLoader。同时以一样的操做给Biz也分配一个BizClassLoader
  2. DeployPluginStage 建立一个map,key是须要隔离的类,value是这个加载这个类使用的PluginClassLoader实例。
  3. DeployBizStage 使用BizClassLoader反射调用Biz的main方法。

至此,Container就启动完了。后面再调用须要隔离的类时,因为启动Biz的线程已经被换成了BizClassLoader,在loadClass时BizClassLoader会首先看看在DeployPluginStage建立的Map中是否有PluginClassLoader能加载这个类,若是能就委托PluginClassLoader加载。就实现了不一样类使用不一样的类加载器加载。

相关文章
相关标签/搜索