类加载,再来一发。

研究完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. 【Hexo】(一)使用HEXO配置环境,创建Hello World

    现场直播,呵呵,就是我完成一步,就记录一下: 一.配置环境 1.安装 Node 下载地址:Node.js 2.安装 Git(win环境下) 下载地址:Git Git 绑定 GitHub账户: ①打开G ...

  2. 对象的创建过程(chapter5.7.3)

    总结一下对象的创建过程,假设有一个名为Dog的类: 1. 即使没有显示地使用static关键字,构造器实际上也是静态的方法,因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类 ...

  3. [翻译] 使用ElasticSearch,Kibana,ASP.NET Core和Docker可视化数据

    原文地址:http://www.dotnetcurry.com/aspnet/1354/elastic-search-kibana-in-docker-dotnet-core-app 想要轻松地通过许 ...

  4. 用java写的一个程序,可以调用windows系统中自带的各种工具,例如截图,便签等

    由于图片资源没有上传,所以运行后不会有图片,感兴趣的同学可以@我,我打包上传, package SmallPrograme; import java.awt.*; import java.awt.ev ...

  5. stm32串口通讯问题

    stm32串口通讯问题 在串口试验中,串口通讯不正常,则可能会出现以下问题: 1. 配置完成后,串口没有任何消息打印. 原因:1,端口配置有问题,需要重新检查I/O口的配置 2,接线有问题,检查接线是 ...

  6. Ackerman 函数 (双递归函数)

    public static int ackerman(int n,int m){  if(n==1&&m==0){return 2;}  else if(n==0&&m ...

  7. Timer,TimerTask通过程序计数器实现的定时任务

    1.程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看 做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型, 各种虚 ...

  8. [UWP]了解模板化控件(5):VisualState

    1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透 ...

  9. Notepad++ 7.3.2 Download 64-bit x64 / 32-bit x86

    Notepad++ 7.3.2 Download 32-bit x86 Notepad++ Installer 32-bit x86: Take this one if you have no ide ...

  10. fuse on TDH4.8

    一.安装依赖包 yum install autoconf.noarch yum install automake yum install libtool* yum install m4 yum ins ...