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
---
1
<plugin>
1
<groupId>org.apache.maven.plugins</groupId>
1
<artifactId>maven-shade-plugin</artifactId>
1
<version>2.4</version>
1
<executions>
1
<execution>
1
<phase>package</phase>
1
<goals>
1
<goal>shade</goal>
1
</goals>
1
<configuration>
1
<artifactSet>
1
<excludes>
1
<exclude>org.abego.treelayout:org.abego.treelayout.core</exclude>
1
</excludes>
1
</artifactSet>
1
<transformers>
1
<transformer
1
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
1
<manifestEntries>
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
</manifestEntries>
1
</transformer>
1
</transformers>
1
<relocations>
1
<relocation>
1
<pattern>org.apache.commons.lang3</pattern>
1
<shadedPattern>org.apache.my-commons.lang3</shadedPattern>
1
</relocation>
1
<relocation>
1
<pattern>org.apache.commons.io</pattern>
1
<shadedPattern>org.apache.my-commons.io</shadedPattern>
1
</relocation>
1
</relocations>
1
</configuration>
1
</execution>
1
</executions>
1
</plugin>

三 自定义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.URL;
1
import java.net.URLClassLoader;
1
/**
1
* @author beiwei30 on 09/12/2016.
1
*/
1
public class ArthasClassloader extends URLClassLoader {
1
public ArthasClassloader(URL[] urls) {
1
super(urls, ClassLoader.getSystemClassLoader().getParent());
1
}
1
@Override
1
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
1
final Class<?> loadedClass = findLoadedClass(name);
1
if (loadedClass != null) {
1
return loadedClass;
1
}
1
// 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException
1
if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
1
return super.loadClass(name, resolve);
1
}
1
try {
1
Class<?> aClass = findClass(name);
1
if (resolve) {
1
resolveClass(aClass);
1
}
1
return aClass;
1
} catch (Exception e) {
1
// ignore
1
}
1
return super.loadClass(name, resolve);
1
}
1
}