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; //
value = 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 repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
} // Local repository
if (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));
}
}
//
ClassLoader 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 any
if ((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方法,它的代码如下:

 public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException { 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
// 1
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
// 3
try {
clazz = system.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;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
} //4
boolean delegateLoad = delegate || filter(name); // (1) Delegate to our parent if requested
// 5
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
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");
// 6
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);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
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. 标注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方法,那我们就来分析一下它的代码:

 protected Class<?> findClassInternal(String name)
throws ClassNotFoundException { //
if (!validate(name))
throw new ClassNotFoundException(name); String tempPath = name.replace('.', '/');
String classPath = tempPath + ".class"; ResourceEntry entry = null; if (securityManager != null) {
PrivilegedAction<ResourceEntry> dp =
new PrivilegedFindResourceByName(name, classPath);
entry = AccessController.doPrivileged(dp);
} else {
// 1
entry = findResourceInternal(name, classPath);
} if (entry == null)
throw new ClassNotFoundException(name); Class<?> clazz = entry.loadedClass;
if (clazz != null)
return clazz; synchronized (this) {
clazz = entry.loadedClass;
if (clazz != null)
return clazz; if (entry.binaryContent == null)
throw new ClassNotFoundException(name); try {
//
clazz = defineClass(name, entry.binaryContent, 0,
entry.binaryContent.length,
new CodeSource(entry.codeBase, entry.certificates));
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
ucve.getLocalizedMessage() + " " +
sm.getString("webappClassLoader.wrongVersion",
name));
}
entry.loadedClass = clazz;
entry.binaryContent = null;
entry.source = null;
entry.codeBase = null;
entry.manifest = null;
entry.certificates = null;
} return clazz; }

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

Reference

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

Tomcat学习之ClassLoader

tomcat的classloader机制

Tomcat类加载器机制的更多相关文章

  1. Java类加载机制与Tomcat类加载器架构

    Java类加载机制 类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

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

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

  3. Tomcat类加载器

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

  4. Tomcat类加载器破坏双亲委派

    转载:https://blog.csdn.net/qq_38182963/article/details/78660779 http://www.cnblogs.com/aspirant/p/8991 ...

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

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

  6. Tomcat 类加载器的实现

    Tomcat 内部定义了多个 ClassLoader,以便应用和容器访问不同存储库中的类和资源,同时达到应用间类隔离的目的.本文首发于公众号:顿悟源码. 1. Java 类加载机制 类加载就是把编译生 ...

  7. 深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现

    打破双亲委派模型 JNDI JNDI 的理解   JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一 ...

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

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

  9. 深入JVM类加载器机制,值得你收藏

    先来一道题,试试水平 public static void main(String[] args) { ClassLoader c1 = ClassloaderStudy.class.getClass ...

随机推荐

  1. PHP报错: Can't use method return value in write context

    $dp_id = $this->getParam('dpId'); if(!empty($this->getParam('dpId'))) { $this->smarty->a ...

  2. XPath学习:parent,child

    XPath 是一门在 XML 文档中查找信息的语言.XPath 可用来在 XML 文档中对元素和属性进行遍历. XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointe ...

  3. Python操作MySQL以及中文乱码的问题

    Python操作MySQL需要安装Python-MySQL可以从网上搜索一下,和一般的Python包一样安装 安装好之后,模块名字叫做MySQLdb ,在Window和Linux环境下都可以使用,试验 ...

  4. (转)Linux下MatlabCompilerRuntime的安装和使用

    1MCR简介 MCR之前是 Matlab Component Runtime的缩写,后更名为Matlab Compiler Runtime.MCR实际上是一组独立的共享库,也即是常说的动态连接库,所起 ...

  5. svn截图

        一.合并一个范围的版本 此类型应用最为广泛,主要是把分支中的修改合并到主干上来.在主干上点击右键选择合并,然后选择合并类型:合并一个范围的版本.合并的源URL填写的是要合并的分支的URL,待合 ...

  6. [daily][device][bluetooth] 蓝牙怎么办!(archlinux下驱动蓝牙鼠标,以及三星手机)

    去年地摊买的破无线鼠标坏掉了.看上微软的Designer Mouse蓝牙鼠,但是买之前我要确认我能不能驱起来. 这款鼠标只支持蓝牙4.0.系统支持windows8以上,不支持xp和windows7. ...

  7. jquery_easyui的使用

    一.引入jquery,jquery_easyui,jquery_easyui css,图标css,本地语言 二.通过学习jquery_easyui 手册,用简单的js代码来实现(按钮.表单.表格.弹出 ...

  8. UltraEdit 标签(tab)不见的3个解决办法

    UltraEdit 标签(tab)不见的3个解决办法 2010-11-08 09:19 1042人阅读 评论(0) 收藏 举报 工具c 方法1:点 视图->视图/列表(V)->打开文件标签 ...

  9. 【nodejs】使用Node.js实现REST Client调用REST API

    最近在产品中开发基于REST的API接口,结合自己最近对Node.js的研究,想基于它开发一个REST Client做测试之用. 通过初步研究,Node.js开发HTTP Client还是挺方便的. ...

  10. Ubuntu 更改默认apt-get源

    原文转自:http://zhoushijun.iteye.com/blog/1942475 方法: 1.修改源地址:cp /etc/apt/sources.list /etc/apt/sources. ...