Tomcat类加载机制之隔离
对于很多非框架的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操作并没有影响另外一个。
以上就实现了简单容器的封装。
网友评论