Java如何隔离Jar包引入的间接依赖
基础组件/中间件开发中经常以Jar包的形式提供给业务开发人员用。如何避免组件依赖的Jar和业务依赖的Jar包造成冲突呢?Java中的Jar hell是一个伤心的话题。
一 重新打包依赖包,修改包结构名
有工具可以帮助修改包名,参考 jarjar:https://github.com/shevek/jarjar。比如:
1
| java -jar jarjar-1.3.jar process ./rule.txt asm-commons-5.2.jar rasp-asm-commons-5.2.jar
|
1
| java -jar jarjar-1.3.jar process ./rule.txt asm-commons-5.2.jar rasp-asm-commons-5.2.jar
|
1
| rule org.objectweb.asm.** org.objectweb.myasm.@1
|
1
| rule org.objectweb.asm.** org.objectweb.myasm.@1
|
需要注意的是所有相互引用或者依赖的类都要做替换;另外需要注意验证配置文件中类/包路径也做了相应的更新。比如MATE-INF/MAINFEST.MF,META-INF/services等配置。
MATE-INF/MAINFEST.MF****META-INF/services二 maven-shade-plugin
这是一个maven插件(参考http://maven.apache.org/plugins/maven-shade-plugin/),maven项目代码编译打包阶段会自动进行包路径和引用的替换。
maven pom.xml文件中这个插件的配置参考如下
1
| --- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <exclude>org.abego.treelayout:org.abego.treelayout.core</exclude> </excludes> </artifactSet> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Project-Version>${project.version}</Project-Version> <Build-Time>${maven.build.timestamp}</Build-Time> <Git-Commit>${git-commit-id.commit.id.abbrev}</Git-Commit> <Git-Branch>${git-commit-id.branch}</Git-Branch> <Last-Commit-User-Name>${git-commit-id.commit.user.name}</Last-Commit-User-Name> <Last-Commit-User-Email>${git-commit-id.commit.user.email} </Last-Commit-User-Email> </manifestEntries> </transformer> </transformers> <relocations> <relocation> <pattern>org.apache.commons.lang3</pattern> <shadedPattern>org.apache.my-commons.lang3</shadedPattern> </relocation> <relocation> <pattern>org.apache.commons.io</pattern> <shadedPattern>org.apache.my-commons.io</shadedPattern> </relocation> </relocations> </configuration> </execution> </executions> </plugin>
|
1
| <groupId>org.apache.maven.plugins</groupId>
|
1
| <artifactId>maven-shade-plugin</artifactId>
|
1
| <exclude>org.abego.treelayout:org.abego.treelayout.core</exclude>
|
1
| implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
1
| <Project-Version>${project.version}</Project-Version>
|
1
| <Build-Time>${maven.build.timestamp}</Build-Time>
|
1
| <Git-Commit>${git-commit-id.commit.id.abbrev}</Git-Commit>
|
1
| <Git-Branch>${git-commit-id.branch}</Git-Branch>
|
1
| <Last-Commit-User-Name>${git-commit-id.commit.user.name}</Last-Commit-User-Name>
|
1
| <Last-Commit-User-Email>${git-commit-id.commit.user.email}
|
1
| </Last-Commit-User-Email>
|
1
| <pattern>org.apache.commons.lang3</pattern>
|
1
| <shadedPattern>org.apache.my-commons.lang3</shadedPattern>
|
1
| <pattern>org.apache.commons.io</pattern>
|
1
| <shadedPattern>org.apache.my-commons.io</shadedPattern>
|
三 自定义ClassLoader, 打破父委托的类加载方式
比较彻底的方式是自定义ClassLoader,并打破默认的双亲委托类加载机制。也就是自定义ClassLoader优先加载我们Jar包中的类。一个简单参考实现如下。
阿里Arthas中的自定义ClassLoader实现,其他如Greys,JVM-Sandbox也是类似的机制)。
1
| package com.taobao.arthas.agent;import java.net.URL;import java.net.URLClassLoader;/** * @author beiwei30 on 09/12/2016. */public class ArthasClassloader extends URLClassLoader { public ArthasClassloader(URL[] urls) { super(urls, ClassLoader.getSystemClassLoader().getParent()); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { final Class<?> loadedClass = findLoadedClass(name); if (loadedClass != null) { return loadedClass; } // 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) { return super.loadClass(name, resolve); } try { Class<?> aClass = findClass(name); if (resolve) { resolveClass(aClass); } return aClass; } catch (Exception e) { // ignore } return super.loadClass(name, resolve); }}
|
1
| package com.taobao.arthas.agent;
|
1
| import java.net.URLClassLoader;
|
1
| * @author beiwei30 on 09/12/2016.
|
1
| public class ArthasClassloader extends URLClassLoader {
|
1
| public ArthasClassloader(URL[] urls) {
|
1
| super(urls, ClassLoader.getSystemClassLoader().getParent());
|
1
| protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
1
| final Class<?> loadedClass = findLoadedClass(name);
|
1
| if (loadedClass != null) {
|
1
| // 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException
|
1
| if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
|
1
| return super.loadClass(name, resolve);
|
1
| Class<?> aClass = findClass(name);
|
1
| return super.loadClass(name, resolve);
|