簡介:當引入二方依賴包或三方依賴包時,可能出現外部依賴jar包與本身的工程須要依賴的衝突,或者多個二方三方依賴包互相沖突。這時候就須要一個隔離容器對他們進行隔離,其依賴的原理就是jvm認爲不一樣classloader加載的類即便包名類名相同,也認爲他們是不一樣的。sofa-ark將須要隔離的jar包打成plugin,對每一個plugin都用獨立的classloader去加載。java
(舒適提示:若對sofa-ark不太瞭解的,最好先去看看官方文檔,簡單瞭解下)git
使用的基本步驟:github
名詞解釋:jvm
在sofa-ark中,使用container容器啓動外部工程(Biz)和衝突jar包(plugin),sofa-ark的plugin maven插件將衝突的jar包打包成爲plugin,外部工程只能引用plugin exported出來的類,而且這個類是由獨立的PluginClassLoader
加載的,從而解決了jar包衝突的問題。maven
從sofa-ark官網 將整個工程下載下來,在最外層pom.xml所在路徑執行mvn package
和mvn install
將sofa-ark依賴jar包安裝到本地。ide
本身建立myjar工程,前後正常打包兩個版本安裝到本地maven倉庫。函數
如:v1版本ui
v2版本this
建立如圖所示兩個工程:url
在myJarservice-v1
和myJarservice-v2
的pom中分別引用以前寫的兩個基礎依賴jar包。而後在MyJarService1
和MyJarService2
中分別引用對應版本的myjar包裏的方法。
如MyJarService1.java:
MyJarService2.java
注意:這裏兩個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 package
和mvn install
便可。
新建一個工程,在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);
能夠看到打印結果:
使用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 包中。進入下載的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()
方法進行了精簡,這個方法主要作了一下幾件事情:
export.index
,MANIFEST.MF
,mark
通過上述步驟後即把依賴的dependence和配置文件都寫入zip中了,而後將其轉換爲jar後綴便可。
前面講解了plugin插件如何工做,這節講述外部工程是如何引用運用plugin插件打包而成的plugin jar包來解決隔離衝突的。能夠從main方法加入的SofaArkBootstrap.launch(args)
進行單步跟蹤,這句代碼主要是將Container啓動起來,而後讓Container去加載Plugin和Biz。在launch
方法裏經過反射調用了SofaArkBootstrap
的remain
方法,在remain
方法裏主要乾了兩件事:
private static void remain(String[] args) throws Exception {// NOPMD
URL[] urls = getURLClassPath();
new ClasspathLauncher(new ClassPathArchive(urls)).launch(args, getClasspath(urls),
entryMethod.getMethod());
}
複製代碼
ClasspathLauncher
,ClasspathLauncher
反射調用ArkContainer
的main
方法,而且使用ContainerClassLoader
加載ArkContainer
。至此,就開始啓動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();
}
}
複製代碼
這個方法主要作了一下幾件事:
LaunchCommand
將傳入的參數分類,將classpath的url和本身寫的啓動類的main方法提取出來
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
,依次執行:
HandleArchiveStage
篩選全部第三方jar包中含有mark標記的plugin jar,說明這些jar是sofa ark maven插件打包成的須要隔離的jar。從jar中的export.index中提取須要隔離的類,把他們加入一個PluginList
中,並給每一個plugin,分配一個獨立的PluginClassLoader
。同時以一樣的操做給Biz也分配一個BizClassLoader
DeployPluginStage
建立一個map,key是須要隔離的類,value是這個加載這個類使用的PluginClassLoader實例。DeployBizStage
使用BizClassLoader反射調用Biz的main方法。至此,Container就啓動完了。後面再調用須要隔離的類時,因爲啓動Biz的線程已經被換成了BizClassLoader,在loadClass時BizClassLoader會首先看看在DeployPluginStage
建立的Map中是否有PluginClassLoader能加載這個類,若是能就委託PluginClassLoader加載。就實現了不一樣類使用不一樣的類加載器加載。