原文链接:http://xtuhcy.iteye.com/blog/2305410
从双亲委派说起
- 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 系统(System)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。 除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
双亲委派机制描述
- 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
Tomcat的类加载机制
本文主要以tomcat7为例说明类加载机制,大家也可以参考tomcat7的类加载机制的官方文档。Tomcat7总的ClassLoader结构如下图:
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ...
各个类加载器加载类的范围:
- Bootstrap:包括java虚机机的基本类,以及$JAVA_HOME/jre/lib/ext下的类。
- System:包括CLASSPATH环境变量的类,主要是$CATALINA_HOME/bin/bootstrap.jar和$CATALINA_BASE/bin/tomcat-juli.jar两个jar包
- Common:包括tomcat的基本类,主要是$CATALINA_BASE/lib下的所有jar包
- WebappX:应用相关类,保证应用之间的类隔离。先加载/WEB-INF/classes,再加载/WEB-INF/lib/*.jar
加载顺序默认如下
- Bootstrap classes of your JVM
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
- System class loader classes (described above)
- Common class loader classes (described above)
如果设置了<Loader delegate="true"/>,加载顺序如下:
- Bootstrap classes of your JVM
- System class loader classes (described above)
- Common class loader classes (described above)
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
JavaEE的 委派模型
每个方块都是一个类加载器,JavaEE规范推荐每个模块的类加载器先加载本类加载的内容,如果加载不到才回到parent类加载器中尝试加载。
反转委派原则的原因是应用服务器中所携带的类库并不是应用所期待的,也许不适合应用开发者,一个常见的例子就是log4j的依赖在容器和不同的应用中都存在,但是它们的版本大都不同。
Tomcat的 类加载顺序
在Tomcat中,默认的行为是先尝试在Bootstrap和Extension中进行类型加载,如果加载不到则在WebappClassLoader中进行加载,如果还是找不到则在Common中进行查找。
setContextClassLoader的理解
JDK的解释是这样的:
Sets the context ClassLoader for this Thread. The context ClassLoader can be set when a thread is created, and allows the creator of the thread to provide the appropriate class loader, through , to code running in the thread when loading classes and resources.
并不是给线程设置了ContextClassLoader,这个线程下加载的类就都使用该ContextClassLoader。ContextClassLoader的用途是提供一个途径,使得线程运行时可以随时获得指定的ContextClassLoader进行类的加载,通过这个这种方式可以避开双亲委派模型,最典型的应用是JDBC、JNDI。
自定义的ClassLoader
自定义的ClassLoader通过继承ClassLoader来实现,也可以使用URLClassLoader更简单。如果需要改写类的加载过程最好覆盖findClass()而不是loadClass(),loadClass()是为了保持jdk1.2之前的兼容。使用findClass()能保证不会违背双亲委派模式。
如何使用自定义的ClassLoader new出对象呢?前面说过的setContextClassLoader是不对的,必须用自定义的ClassLoader,通过反射实例化一个初始类,由该初始类加载的其他类就都会使用自定义的ClassLoader了。可以分析一下tomcat的代码:
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
通过反射机制调用startupInstance的方法,之后的类就会都有自定义的ClassLoader加载,当然有个前提就是不能违背双亲委派模型——自定义的ClassLoader加载的类在父ClassLoader加载的类中不存在。为什么要这么做?类加载的时候有一个规律,被加载类使用调用者所用的ClassLoader进行类的加载。可以通过Class.forName()的代码得到这个结论:
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}