近期对于classloader的一些了解和解读

近期对于classloader的一些了解和解读

对于初学者来说这个概念也许很陌生,其实对于很多非框架的java开发者来说,classloader确实很少用到。这里提一下我最近开到的两篇优秀博文深入浅出classLoader深入理解Tomcat(五)类加载机制世界杯外围投注规则。他们给了我很大的启发以及思考。

  • 为什么classLoader在一些场合是必须的?

世界杯外围投注规则对于一般的开发者来说main函数即可运行一个完整的java程序(其实启动一个java进程也是用到了java自带的classLoader)。但考虑到tomcat这种容器类的程序,上面两篇博文中讲到

世界杯外围投注规则一个Tomcat容器允许同时运行多个Web程序,每个Web程序依赖的类又必须是相互隔离的

世界杯外围投注规则假如我们有两个Web程序,一个依赖A库的1.0版本,另一个依赖A库的2.0版本,他们都使用了类xxx.xx.Clazz,其实现的逻辑因类库版本的不同而结构完全不同。那么这两个Web程序的其中一个必然因为加载的Clazz不是所使用的Clazz而出现问题!而这对于开发来说是非常致命的!

看到这个我才有一种恍然大悟的感觉,原来我从来没考虑过这样的需求和环境,所以说框架的开发者们真的考虑了很多。膜拜!

  • 那么Tomcat是如何做到的呢?

Tomcat的 类加载顺序(开启了delegate模式)

delegate模式

在Tomcat中,默认的行为是先尝试在Bootstrap和Extension中进行类型加载,如果加载不到则在WebappClassLoader中进行加载,如果还是找不到则在Common中进行查找。在Alibaba使用的Tomcat开启了delegate模式,因此加载类型时会以parent类加载器优先。

关于这点官网也有文档说明

我的理解是只要用两个不同的类加载器,加载同一个类即可实现相互隔离,但是要注意双亲委派模型,不要让父类加载器加载到你要加载的类。

  • 我按照这一原理简单实现的demo

世界杯外围投注规则这个类有两个classloader加载两次classloader.Container类

package classloader;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 这几天在看了 tomcat的 类加载器 产生了1个疑问
 * 如果一个tomcat内有了两个webApp, 那么启动的jvm进程应该1个
 * 那如果保证webApp1 和webApp2之间内存数据互不影响?
 */
 
public class ClassLoaderDemo {
    public static void main(String... args) throws Exception {
        URL url = new URL("file:E:\\testContainer.jar");
        URLClassLoader classloader1 = new URLClassLoader(new URL[]{url});
        URLClassLoader classloader2 = new URLClassLoader(new URL[]{url});
        Class clazz1 = Class.forName("classloader.Container", true, classloader1);
        Class clazz2 = Class.forName("classloader.Container", true, classloader2);
        System.out.println(clazz1);
        System.out.println(clazz2);
        System.out.println(clazz1==clazz2);
        System.out.println(clazz1.equals(clazz2));
        clazz1.getMethod("addEntry").invoke(clazz1.newInstance());
        clazz1.getMethod("printEntries").invoke(clazz1.newInstance());
        clazz2.getMethod("printEntries").invoke(clazz2.newInstance());
    }
}

这个类是一个容器类,把他打包在testContainer.jar中供上一个类调用

package classloader;

import java.util.HashMap;
import java.util.Map;

/**
 * 虚拟一个容器 假设是一个 webApp容器,注意这个类不要让上述的main类的类加载器加载到。
 * 最简单的方式就是把他们放在不同的项目中,并把这个类打成jar包 然后让URLClassLoader去加载jar包中的这个类
 *
 * @author xuecm
 */
public class Container {

    public static Map<String, String> map = new HashMap<>();

    public void addEntry() {
        map.put("key", "value");
    }

    public void printEntries() {
        System.out.println("start printEntries ===========");

        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "_" + entry.getValue());
        }
        System.out.println("end printEntries ============");

    }
}

最后的输出结果

class classloader.Container
class classloader.Container
false
false
start printEntries ===========
key_value
end printEntries ============
start printEntries ===========
end printEntries ============

可以看到输出的结果中两个classLoader加载的classloader.Container 并不是同一个,而且对一个Container的addEntry操作并没有影响另外一个。
以上就实现了简单容器的封装。