Java解决相同依赖包不同版本的兼容问题

/ 经历分享Java / 0 条评论 / 900浏览

Java程序的依赖包都是以jar文件的形式引入并加载的,而jar包又是由若干个class文件、资源文件、包描述文件经过zip格式压缩而成的,依赖jar包主要是class文件。

如果你对类加载流程不是很熟悉,可以前置阅读:详谈类加载的全过程

众多class文件在JVM中,在载入的过程都是动态的,只有在程序执行到某个段代码需要import这个class时才会被载入,并且载入的流程默认遵循双亲委派模型,这一委派模型被定义在ClassLoader中。双亲委派的好处主要有:

  1. 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
  2. 避免了多份同样字节码的加载。 双亲委派模型

由上面的图可以看到依赖的三方jar包都是由AppClassLoader载入的,要想同一个Java程序兼容不同版本的jar包就需要对不同版本的类加载器作用空间进行隔离,即破坏其双亲委派模型。

破坏双亲委派模型

自定义ClassLoader

public class ServiceAgentClassLoader extends URLClassLoader {

    public ServiceAgentClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public ServiceAgentClassLoader(URL[] urls) {
        super(urls, ClassLoader.getSystemClassLoader());
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        final Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) {
            return loadedClass;
        }

        if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
            return super.loadClass(name, resolve);
        }
        try {
            Class<?> clazz = findClass(name);
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        } catch (Exception ignored) {
        }
        return super.loadClass(name, resolve);
    }
}

然后在项目启动时加载jar包

public synchronized static void load(String jarName) throws Exception {
    if (classLoader != null) {
        throw new IllegalStateException("Class loader existed");
    }
    String jarPath = AppUtil.getHomePath() + "/agent/" + jarName;
    File jar = new File(jarPath);
    if (!jar.exists()) {
        throw new FileNotFoundException(jarPath);
    }
    classLoader = new ServiceAgentClassLoader(new URL[]{jar.toURI().toURL()});
    Class<?> clazz;
    try {
        clazz = classLoader.loadClass("com.leocool.px.agent.EtcdServiceDiscover", true);
    } catch (ClassNotFoundException e) {
        throw new Exception("Load jar failed: " + jarPath);
    }
    Method method = clazz.getDeclaredMethod("getInstance");
    method.setAccessible(true);
    method.invoke(null);

    ServiceDiscover discoverer = ServiceDiscoverFactory.getDiscoverer();
    Preconditions.checkNotNull(discoverer);
    logger.info("Load service agent successful, target: '{}'", discoverer.getName());
}

微信公众号浏览体验更佳,在这里还有更多优秀文章为你奉上,快来关注吧!

北风IT之路