类加载,再来一发。

研究完java提供的类加载机制,再来看看tomcat开出了那些花。

最近开始读tomcat的源码,主线路当然是类加载机制,在这个过程中有豁然开朗的感觉。这一篇主要是自己的体会,而不是从头到尾的详细解读。很显然,是因为我懒。有多懒呢,懒到把女朋友都弄丢了,哎。

言归正传,从tomcat的启动类Bootstrap开始叙述。

1、加载Bootstrap的类加载器是哪个呢?APPClassLoader,因为tomcat还是要依赖Java的基础,包括类加载器和双亲委派模型。

2、Bootstrap类启动的时候,会初始化类加载器。

    ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null; // -------------------------------------------------------- Private Methods private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}

这里有3个类加载器,catalinaLoader 和 sharedLoader 以 commonLoader 为父类加载器,那commonLoader的父类加载器呢???一路跟代码,发现最终是AppClassLoader,具体实现在Java.lang.ClassLoader类。

common加载的类:tomcat和web应用都可以访问;

catalina加载的类:tomcat内部实现用到的类;

shared加载的类:所有web应用可以共用。

与每个web应用单独对应的类加载器是WebappClassLoader,后面会具体分析。

3、common、catalina以及shared三个类加载器都是URLClassLoader

    if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);

这是tomcat7的代码,6包括之前的版本还在用StandardClassLoader

4、类加载器生成后,Thread.currentThread().setContextClassLoader(catalinaLoader); 将当前线程的上下文类加载器设置为catalinaLoader,在后面的逻辑里面会经常用到。

5、再之后的一段代码需要好好分析一下

        // Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);

这里通过catalinaLoader去加载了Catalina类,生成了一个实例,并且通过反射调用了这个实例的setParentClassLoader方法,将父类加载器设置为sharedLoader,后面也会经常用到。我刚看到这段代码时,有一个巨大的疑问,为什么要用反射呢?

说的这里,我想再延伸一下。使用不同的类加载器还有一个好处:类之间的相互隔离。再深入一点,初始加载器和定义加载器。比如:有A、B、C 3个类加载器,根据双亲委派,通过 A --> B --> C 这条路径去加载了一个类 X, 那么A、B、C都是X的初始加载器,只有C是X的定义加载器。有一个原则:X类型在A、B、C 的命名空间中共享,在别的类加载器的命名空间中不能访问,也就是隔离。这里要注意,A、B独自加载的类型,对C不共享,也就是说子共享父,反过来不行。

所以,现在能解释两个事情:

a)、我们平常的代码里处处是jdk的类,它们的类加载器是启动类加载器,为什么。因为我们的代码是在classpath里面,类加载器是AppClassLoader,可以共享jdk的类。

b)、我们这里必须用反射!!!路演一下,启动类Bootstrap的类加载器是AppClassLoader,然后Bootstrap类里面用catalinaLoader去加载Catalina类。catalina --> common --> AppClassLoader,所以顺序很重要,反过来不行。既然不能共享,但是必须要访问,那怎么办呢,还好有反射。

其实在Tomcat的源码里面,Bootstrap类和Catalina类是在同一个package里。那,问题又来了。class文件既然在同一个路径下,为什么一个用AppClassLoader来加载,而另一个却用CatalinaLoader呢???

应该是出于安全的考虑,不信就去看看Bootstrap类的注释(重点是最后一句。翻译,我就不献丑了):

 * Bootstrap loader for Catalina.  This application constructs a class loader
* for use in loading the Catalina internal classes (by accumulating all of the
* JAR files found in the "server" directory under "catalina.home"), and
* starts the regular execution of the container. The purpose of this
* roundabout approach is to keep the Catalina internal classes (and any
* other classes they depend on, such as an XML parser) out of the system
* class path and therefore not visible to application level classes.

6、最后来解刨WebappClassLoader

先看它重写的loadClass方法,实现在父类WebappClassLoaderBase里面

public Class<?> loadClass(String name, boolean resolve) throws         ClassNotFoundException {

        synchronized (getClassLoadingLockInternal(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null; // Log access to stopped classloader
if (!started) {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.info(sm.getString("webappClassLoader.stopped", name), e);
}
} // (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
} // (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
} // (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding J2SE classes
try {
clazz = j2seClassLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
} // (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
if (name.endsWith("BeanInfo")) {
// BZ 57906: suppress logging for calls from
// java.beans.Introspector.findExplicitBeanInfo()
log.debug(error, se);
} else {
log.info(error, se);
}
throw new ClassNotFoundException(error, se);
}
}
} boolean delegateLoad = delegate || filter(name); // (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
} // (2) Search local repositories
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
} // (3) Delegate to parent unconditionally
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
} throw new ClassNotFoundException(name);
}

梳理一下流程,其实英文的注释已经很明白了。

(1)首先从Tomcat自己持有的缓存中去查找

(2)从JVM的缓存中找

(3)先用j2seClassLoader去加载,就是怕你把jdk的jar包放到了WEB-INF下面

        ClassLoader j = String.class.getClassLoader();
if (j == null) {
j = getSystemClassLoader();
while (j.getParent() != null) {
j = j.getParent();
}
}
this.j2seClassLoader = j;

从这段代码中,你应该知道j2seClassLoader到底是哪个类加载器了。只是我不明白为什么要用这么复杂的方式得到,getSystemClassLoader().getParent() 不行吗,希望明白的读者告知。

(4)如果设置了代理,就代理给父类加载器去加载,父亲是谁呢?后面再说。

(5)终于轮到自己去加载了

(6)如果还是不好使,无条件的交给父亲去搞定。

现在看看,这个WebappClassLoader是怎么生成的:

启动StandardContext的时候会创建WebappLoader,WebappLoader持有WebappClassLoderBase,WebappLoader里面初生成它的代码

    private String loaderClass =

"org.apache.catalina.loader.WebappClassLoader";


  classLoader = createClassLoader();

    private WebappClassLoaderBase createClassLoader()
throws Exception { Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null; if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args); return classLoader; }

这里通过反射调用构造方法,生成一个WebappClassLoader,并强转成父类。

其中container.getParentClassLoader() 得到的类加载器,就是sharedLoader,去看Tomcat的源码。

至于这里为什么也要用反射,不明白,请读者指教。

tomcat类加载体系的更多相关文章

  1. Tomcat源码分析——类加载体系

    前言 Tomcat遵循J2EE规范,实现了Web容器.很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解.此外,Tomcat还根据Java虚 ...

  2. Tomcat系列(7)——Tomcat类加载机制

    1. 核心部分 1. 类加载器: 通过一个类的全限定名来获取描述此类的二进制字节流. 对于任意一个类,都需要由加载他的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一 ...

  3. Java 类加载体系之 ClassLoader 双亲委托机制

    Java 类加载体系之 ClassLoader 双亲委托机制 java 是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件 ...

  4. Tomcat源码分析 (五)----- Tomcat 类加载器

    在研究tomcat 类加载之前,我们复习一下或者说巩固一下java 默认的类加载器.楼主以前对类加载也是懵懵懂懂,借此机会,也好好复习一下. 楼主翻开了神书<深入理解Java虚拟机>第二版 ...

  5. Tomcat 类加载器打破双亲委派模型

    我们分为4个部分来探讨: 1. 什么是类加载机制? 2. 什么是双亲委任模型? 3. 如何破坏双亲委任模型? 4. Tomcat 的类加载器是怎么设计的? 我想,在研究tomcat 类加载之前,我们复 ...

  6. java类加载器-Tomcat类加载器

    在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式.接下来就以tomcat6为例看看tomat是如何使用自定制类加载器的.(本介绍是基于tomcat6.0.41,不同版本可 ...

  7. 图解Tomcat类加载机制

    说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷. 之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试.后来同 ...

  8. Tomcat类加载器机制

    Tomcat为什么需要定制自己的ClassLoader: 1.定制特定的规则:隔离webapp,安全考虑,reload热插拔 2.缓存类 3.事先加载 要说Tomcat的Classloader机制,我 ...

  9. Tomcat类加载器

    1JVM类加载机制   JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构.其中引导类.扩展类.系统类三个加载器是JVM内置的. 它们的作用分别是: 1)引导类加载器:使 ...

随机推荐

  1. Nagios邮件报警

    p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; line-height: 150%; fon ...

  2. WebStorm里使用注意点

    归纳一些我在使用webstorm里遇到的问题: 1.问题:文件出现红线,如图 解决方案:可能是由于语言版本的问题,改一下版本试试 2.快捷键:http://www.cnblogs.com/yeming ...

  3. 什么是测试开发工程师-google的解释

    什么是测试开发工程师-google的解释 “ 软件测试开发工程师[SET or Software Engineer in Test],和软件开发工程师一样是开发工程师,主要负责软件的可测试性.他们参与 ...

  4. Java第二章 变量

    1.什么是变量? 存储数据的基本单位. 2.数据类型分为: 基本类型和引用数据 3.基本数据类型和引用数据类型的区别: 基础数据:不同的变量会分配不同的存储空间,改变一个变量不会影响另一个变量 引用数 ...

  5. MongoDB基础教程系列--第六篇 MongoDB 索引

    使用索引可以大大提高文档的查询效率.如果没有索引,会遍历集合中所有文档,才能找到匹配查询语句的文档.这样遍历集合中整个文档的方式是非常耗时的,特别是处理大数据时,耗时几十秒甚至几分钟都是有可能的. 创 ...

  6. C++命名空间【转】

    本讲基本要求 * 掌握:命名空间的作用及定义:如何使用命名空间.     * 了解:使用早期的函数库 重点.难点     ◆命名空间的作用及定义:如何使用命名空间.     在学习本书前面各章时,读者 ...

  7. z-index用法总结

    一.定义: z-index 只适用于元素有定位的情况,表示层级 数值越大 层级越高 展示的位置越靠前. 二.用法: 1.同级关系: z-index值较大的元素将叠加在z-index值较小的元素之上 ( ...

  8. Spring事务管理的实现方式:编程式事务与声明式事务

    1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖 ...

  9. 封装Web Uploader 上传插件、My97DatePicker、百度 编辑器 的使用 (ASP.NET MVC)

    Web Uploader: WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件.在现代的浏览器里面能充分发挥HTML5的优 ...

  10. 网站启用gzip压缩

    gzip压缩启用不启用还是要看实际情况的,启用gzip后可以相应的减轻带宽压力但是同时也会增加cpu的压力(压缩解压),相反的如果不启用那么cpu压力也会相应的减少,具体情况具体分析. Linux开启 ...