Tomcat为什么需要定制自己的ClassLoader:

1、定制特定的规则:隔离webapp,安全考虑,reload热插拔

2、缓存类

3、事先加载

要说Tomcat的Classloader机制,我们还得从Bootstrap开始。在BootStrap初始化的时候,调用了org.apache.catalina.startup.Bootstrap#initClassLoaders方法,这个方法里面创建了3个ClassLoader,它们分别是commonLoader,catalinaLoader,sharedLoader,其中catalinaLoader,sharedLoader的父亲加载器是commonLoader,initClassLoaders执行的过程中会执行createClassLoader,而createClassLoader是根据conf/catalina.properties文件中common.loader,server.loader,shared.loader的值来初始化,它的代码如下:

默认情况下,这3个ClassLoader是同一个实例变量。

private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {String value = CatalinaProperties.getProperty(name + ".loader");// 1 if ((value == null) || (value.equals("")))return parent;// 2value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore
        }// Local repositoryif (repository.endsWith("*.jar")) {repository = repository.substring(0, repository.length() - "*.jar".length());repositories.add(new Repository(repository, RepositoryType.GLOB));} else if (repository.endsWith(".jar")) {repositories.add(new Repository(repository, RepositoryType.JAR));} else {repositories.add(new Repository(repository, RepositoryType.DIR));}}// 3ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);//这里返回的是一个UrlClassLoader实例return classLoader;}

以上代码删除了与本篇无关的代码,下面我们分别来分析一下标注的地方:

  1. 标注1的代码(第5行)判断如果catalina.properties中没有配置对应的loader属性的话,直接返回父加载器,而默认情况下,server.loader,shared.loader为空,那么此时的catalinaLoader,sharedLoader其实是同一个ClassLoader.
  2. 标注2(第9行)的地方根据环境变量的配置替换字符串中的值.默认情况下,common.loader的值为common.loader=${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar,这里会将catalina.base和catalina.home用环境变量的值替换。
  3. 标注3(第46行)的代码最终调用org.apache.catalina.startup.ClassLoaderFactory#createClassLoader静态工厂方法创建了URLClassloader的实例,而具体的URL其实就是*.loader属性配置的内容,此外如果parent为null的话,则直接用系统类加载器。

上面分析了Tomcat在启动的时候,初始化的几个ClassLoader,接下来我们再来继续看看,这些ClassLoader具体都用在什么地方。

我们接着来看org.apache.catalina.startup.Bootstrap#init方法,在初始化完3个classLoader以后,接下来首先通过catalinaLoader加载了org.apache.catalina.startup.Catalinal类,然后通过放射调用了org.apache.catalina.startup.Catalina#setParentClassLoader,具体代码片段如下:

Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();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);

通过上面的代码,我们可以清楚的看到调用了Catalina的setParentClassLoader放,那么到这里我们可能又要想知道,设置了parentClassLoader以后,sharedLoader又是在哪里使用的呢?这就需要我们接着来分析容器启动的代码。我们通过查看org.apache.catalina.startup.Catalina#getParentClassLoader调用栈,我们看到在StandardContext的startInternal方法中调用了它,那么我们查看一下它的代码,包含了如下代码片段:

if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);
}
try {if (ok) {// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();//other code
    }
catch(Exception e){
}

通过查看上面的代码,我们看到在StandardContext启动的时候,会创建webapploader,创建webapploader的时候会将getParentClassLoader方法返回的结果(这里返回的其实就是sharedLoader)赋值给自己的parentClassLoader变量,接着又会调用到Webapploader的start方法,因为WebappLoader符合Tomcat组件生命周期管理的模板方法模式,因此会调用到它的startInternal方法。我们接下来就来看看WebappLoader的startInternal,我们摘取一部分与本篇相关的代码片段如下:

classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);

从上的代码可以看到调用了createClassLoader方法创建一个classLoader,那么我们再看来看看createClassLoader的代码:

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

在上面的代码里面,loaderClass是WebappLoader的实例变量,其值为org.apache.catalina.loader.WebappClassLoader,那么上面的代码其实就是通过反射调用了WebappClassLoader的构造函数,然后传递了sharedLoader作为其父亲加载器。

代码阅读到这里,我们已经基本清楚了Tomcat中ClassLoader的总体结构,总结如下: 在Tomcat存在common,cataina,shared三个公共的classloader,默认情况下,这三个classloader其实是同一个,都是common classloader,而针对每个webapp,也就是context(对应代码中的StandardContext类),都有自己的WebappClassLoader来加载每个应用自己的类。上面的描述,我们可以通过下图形象化的描述:

清楚了Tomcat总体的ClassLoader结构以后,咋们就来进一步来分析一下WebAppClassLoader的代码,我们知道Java的ClassLoader机制有parent-first的机制,而这种机制是在loadClass方法保证的,一般情况下,我们只需要重写findClass方法就好了,而对于WebAppClassLoader,通过查看源代码,我们发现loadClass和findClass方法都进行了重写,那么我们首先就来看看它的loadClass方法,它的代码如下:

  1 public synchronized Class<?> loadClass(String name, boolean resolve)
  2     throws ClassNotFoundException {
  3
  4     if (log.isDebugEnabled())
  5         log.debug("loadClass(" + name + ", " + resolve + ")");
  6     Class<?> clazz = null;
  7
  8     // Log access to stopped classloader
  9     if (!started) {
 10         try {
 11             throw new IllegalStateException();
 12         } catch (IllegalStateException e) {
 13             log.info(sm.getString("webappClassLoader.stopped", name), e);
 14         }
 15     }
 16
 17     // (0) Check our previously loaded local class cache
 18     // 1
 19     clazz = findLoadedClass0(name);
 20     if (clazz != null) {
 21         if (log.isDebugEnabled())
 22             log.debug("  Returning class from cache");
 23         if (resolve)
 24             resolveClass(clazz);
 25         return (clazz);
 26     }
 27
 28     // (0.1) Check our previously loaded class cache
 29     // 2
 30     clazz = findLoadedClass(name);
 31     if (clazz != null) {
 32         if (log.isDebugEnabled())
 33             log.debug("  Returning class from cache");
 34         if (resolve)
 35             resolveClass(clazz);
 36         return (clazz);
 37     }
 38
 39     // (0.2) Try loading the class with the system class loader, to prevent
 40     //       the webapp from overriding J2SE classes
 41     // 3
 42     try {
 43         clazz = system.loadClass(name);
 44         if (clazz != null) {
 45             if (resolve)
 46                 resolveClass(clazz);
 47             return (clazz);
 48         }
 49     } catch (ClassNotFoundException e) {
 50         // Ignore
 51     }
 52
 53     // (0.5) Permission to access this class when using a SecurityManager
 54     if (securityManager != null) {
 55         int i = name.lastIndexOf('.');
 56         if (i >= 0) {
 57             try {
 58                 securityManager.checkPackageAccess(name.substring(0,i));
 59             } catch (SecurityException se) {
 60                 String error = "Security Violation, attempt to use " +
 61                     "Restricted Class: " + name;
 62                 log.info(error, se);
 63                 throw new ClassNotFoundException(error, se);
 64             }
 65         }
 66     }
 67
 68     //4
 69     boolean delegateLoad = delegate || filter(name);
 70
 71     // (1) Delegate to our parent if requested
 72     // 5
 73     if (delegateLoad) {
 74         if (log.isDebugEnabled())
 75             log.debug("  Delegating to parent classloader1 " + parent);
 76         ClassLoader loader = parent;
 77         if (loader == null)
 78             loader = system;
 79         try {
 80             clazz = Class.forName(name, false, loader);
 81             if (clazz != null) {
 82                 if (log.isDebugEnabled())
 83                     log.debug("  Loading class from parent");
 84                 if (resolve)
 85                     resolveClass(clazz);
 86                 return (clazz);
 87             }
 88         } catch (ClassNotFoundException e) {
 89             // Ignore
 90         }
 91     }
 92
 93     // (2) Search local repositories
 94     if (log.isDebugEnabled())
 95         log.debug("  Searching local repositories");
 96     // 6
 97     try {
 98         clazz = findClass(name);
 99         if (clazz != null) {
100             if (log.isDebugEnabled())
101                 log.debug("  Loading class from local repository");
102             if (resolve)
103                 resolveClass(clazz);
104             return (clazz);
105         }
106     } catch (ClassNotFoundException e) {
107         // Ignore
108     }
109
110     // (3) Delegate to parent unconditionally
111     // 7
112     if (!delegateLoad) {
113         if (log.isDebugEnabled())
114             log.debug("  Delegating to parent classloader at end: " + parent);
115         ClassLoader loader = parent;
116         if (loader == null)
117             loader = system;
118         try {
119             clazz = Class.forName(name, false, loader);
120             if (clazz != null) {
121                 if (log.isDebugEnabled())
122                     log.debug("  Loading class from parent");
123                 if (resolve)
124                     resolveClass(clazz);
125                 return (clazz);
126             }
127         } catch (ClassNotFoundException e) {
128             // Ignore
129         }
130     }
131
132     throw new ClassNotFoundException(name);
133
134 }

我们一步步的来分析一下上面的代码做了什么事情。

  1. 标注1(第18行)代码,首先从当前ClassLoader的本地缓存中加载类,如果找到则返回。
  2. 标注2(第29行)代码,在本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。
  3. 标注3(第41行)代码,通过系统的来加载器加载此类,这里防止应用写的类覆盖了J2SE的类,这句代码非常关键,如果不写的话,就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写了一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中,如果没有此句代码的保证,那么你自己写的类就会替换到Tomcat容器Lib中包含的类。
  4. 标注4(第68行)代码,判断是否需要委托给父类加载器进行加载,delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了,filter方法中根据包名来判断是否需要进行委托加载,默认情况下会返回false.因此delegatedLoad为false
  5. 标注5(第72行)代码,因为delegatedLoad为false,那么此时不会委托父加载器去加载,这里其实是没有遵循parent-first的加载机制。
  6. 标注6(第96行)调用findClass方法在webapp级别进行加载
  7. 标注7(第111行)如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。

通过上面的描述,我们可以知道Tomcat在加载webapp级别的类的时候,默认是不遵守parent-first的,这样做的好处是更好的实现了应用的隔离,但是坏处就是加大了内存浪费,同样的类库要在不同的app中都要加载一份。

上面分析完了loadClass,我们接着在来分析一下findClass,通过分析findClass的代码,最终会调用org.apache.catalina.loader.WebappClassLoader#findClassInternal方法,那我们就来分析一下它的代码:

 1 protected Class<?> findClassInternal(String name)
 2     throws ClassNotFoundException {
 3
 4     //
 5     if (!validate(name))
 6         throw new ClassNotFoundException(name);
 7
 8     String tempPath = name.replace('.', '/');
 9     String classPath = tempPath + ".class";
10
11     ResourceEntry entry = null;
12
13     if (securityManager != null) {
14         PrivilegedAction<ResourceEntry> dp =
15             new PrivilegedFindResourceByName(name, classPath);
16         entry = AccessController.doPrivileged(dp);
17     } else {
18         // 1
19         entry = findResourceInternal(name, classPath);
20     }
21
22     if (entry == null)
23         throw new ClassNotFoundException(name);
24
25     Class<?> clazz = entry.loadedClass;
26     if (clazz != null)
27         return clazz;
28
29     synchronized (this) {
30         clazz = entry.loadedClass;
31         if (clazz != null)
32             return clazz;
33
34         if (entry.binaryContent == null)
35             throw new ClassNotFoundException(name);
36
37         try {
38             // 2
39             clazz = defineClass(name, entry.binaryContent, 0,
40                     entry.binaryContent.length,
41                     new CodeSource(entry.codeBase, entry.certificates));
42         } catch (UnsupportedClassVersionError ucve) {
43             throw new UnsupportedClassVersionError(
44                     ucve.getLocalizedMessage() + " " +
45                     sm.getString("webappClassLoader.wrongVersion",
46                             name));
47         }
48         entry.loadedClass = clazz;
49         entry.binaryContent = null;
50         entry.source = null;
51         entry.codeBase = null;
52         entry.manifest = null;
53         entry.certificates = null;
54     }
55
56     return clazz;
57
58 }

上面的代码标注1(第19行)的地方通过名称去当前webappClassLoader的仓库中查找对应的类文件,标注2(第38行)的代码,将找到的类文件通过defineClass转变为Jvm可以识别的Class对象返回。

Reference

Tomcat类加载器机制(Tomcat源代码阅读系列之六)

Tomcat学习之ClassLoader

tomcat的classloader机制

Tomcat类加载器机制相关推荐

  1. Tomcat源码解析六:Tomcat类加载器机制

    要说Tomcat的Classloader机制,我们还得从Bootstrap开始.在BootStrap初始化的时候,调用了org.apache.catalina.startup.Bootstrap#in ...

  2. 类加载机制:双亲委任模型和tomcat类加载器

    简介 类是如何加载的,那么必须要面对的几个问题如下 什么是类加载机制? 什么是双亲委任模型? 如何破坏双亲委任模型? Tomcat 的类加载器是怎么设计的? 类加载机制 Java 中的类加载器大致可以 ...

  3. JVM类加载理解(线程上下文类加载器、Tomcat类加载器)

    类加载机制概念 Java虚拟机把描述类的class文件加载到内存,对其进行校验.转换解析.初始化等操作,最终得到可以被虚拟机直接使用的java类型,这就是虚拟机的加载机制. 主要有五个步骤: 加载 将 ...

  4. Tomcat类加载器为何违背双亲委派模型

    本文来说下Tomcat类加载器为何违背双亲委派模型 文章目录 什么是类加载机制 什么是双亲委派模型 如何破坏双亲委任模型 Tomcat的类加载器是怎么设计的 本文小结 什么是类加载机制 代码编译的结果 ...

  5. 接口多个实现类加载哪个_深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  6. OSGi 规范和框架 OSGi框架类加载机制 Java默认类加载器机制和OSGI类加载器机制比较

    一.OSGi 规范 OSGi(Open Service Gateway Initiative) 技术是 Java 动态化模块化系统的一系列规范.OSGi 一方面指维护 OSGi 规范的 OSGi Al ...

  7. 《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程

    参考网址:http://blog.csdn.net/luanlouis/article/details/50529868 0.前言 读完本文,你将了解到: 一.为什么说Jabalpur语言是跨平台的 ...

  8. 利用反射模拟Tomcat类加载器的toString方法

    一直认为tomcat5的类加载器的toString方法很酷,为什么呢?因为它的toString方法列出了所有的父类加载器以及类加载器加载的资源(即classpath).可能有些朋友还没有注意过,如果想 ...

  9. java类加载器、双亲委派、沙箱安全机制全都让你整明白(三万字,收藏慢慢啃)

    目录 一.概述 1.类加载的分类 2.类加载器的必要性 3.命名空间 4.类加载机制的基本特征 二.类加载器的分类 1.引导类加载器 2.扩展类加载器 3.系统类加载器 4.用户自定义类加载器 三.测 ...

最新文章

  1. 十个最常用的JVM 配置参数
  2. android stub.asinterface是什么意思
  3. Java泛型面试问题
  4. oracle 分页查询
  5. 【EOJ Monthly 2019.02 - D】进制转换(思维,取模,高精度大数)
  6. android微信支付服务端,Android 微信支付返回-1
  7. python-面向对象名词解析(类、实例、属性、方法、对象)
  8. HDU-6341 Problem J. Let Sudoku Rotate(dfs 剪枝)
  9. 利用监听器实现在线人数统计
  10. Andrew Ng 如何重拾梦想
  11. 抓取Js动态生成数据且以滚动页面方式分页的网页
  12. wafer map格式转换_如何将谷歌地球KML图层转换为Mapinfo TAB图层?
  13. 【seaborn】(五)联合分布图
  14. LLVM 学习(二) -LLVM IR 语法理论学习
  15. QQ VS 360 大战开始
  16. 空指针异常可能出现的原因
  17. padStart(),padEnd()方法
  18. 微信小程序超长页面生成截图的一种解决方案
  19. 网站软文推广类的文章怎么写?
  20. 使用360手机助手连接真机。图文教程

热门文章

  1. Unity应用架构设计(11)——一个网络层的构建
  2. Citrix Xendesktop中VDA注册DDC的流程
  3. 美容院会籍管理,看着简单,其实很复杂
  4. 手机+笔记本上NET网教程--5步让你轻松上网
  5. Tungsten Fabric SDN — SmartNIC vRouter 软件架构设计
  6. Linux Kernel TCP/IP Stack — L2 Layer — Linux VLAN device for 802.1.q(虚拟局域网)
  7. Python 笔试集:什么时候 i = i + 1 并不等于 i += 1?
  8. 快速理解 session/token/cookie 认证方式
  9. Keil错误Unable to automatically place AT section malloc.o(.ARM.__AT_0x20000000) with required base add
  10. 6.3 OpenSSH