要说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的值来初始化,它的代码例如以下:

org.apache.catalina.startup.Bootstrap#createClassLoader
rivate ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception { String value = CatalinaProperties.getProperty(name + ".loader");
// 1
if ((value == null) || (value.equals("")))
return parent; // 2
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));
}
}
// 3
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(repositories, parent); 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,详细代码片段例如以下:

org.apache.catalina.startup.Bootstrap#init
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方法中调用了它,那么我们查看一下它的代码,包括了例如以下代码片段:

org.apache.catalina.core.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。我们摘取一部分与本篇相关的代码片段例如以下:

org.apache.catalina.loader.WebappLoader#startInternal
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);

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

org.apache.catalina.loader.WebappLoader#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方法,它的代码例如以下:

org.apache.catalina.loader.WebappClassLoader#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
// 2
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
// 7
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方法。那我们就来分析一下它的代码:

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 {
// 2
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对象返回。


Tomcat类载入器机制(Tomcat源代码解析六)的更多相关文章

  1. Java类载入器 ClassLoader的解析

    //參考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类载入器基本概念 类载入器是 Java 语言的一个创新,也是 Ja ...

  2. Java类载入器(一)——类载入器层次与模型

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

  3. Java类载入器

    1.   系统载入器简单介绍 Java虚拟机中能够安装多个类载入器,系统默认三个主要类载入器(BootStrap.ExtClassLoader.AppClassLoader).每一个类载入器负责载入特 ...

  4. Java类载入器(二)——自己定义类载入器

      用户定制自己的ClassLoader能够实现以下的一些应用: 自己定义路径下查找自己定义的class类文件,或许我们须要的class文件并不总是在已经设置好的Classpath以下,那么我们必须想 ...

  5. 黑马程序猿——Java中的类载入器

    ------- android培训.java培训.期待与您交流! -------- 类载入器 Java虚拟机中能够安装多个类载入器,系统默认三个主要类载入器,每一个类负责载入特定位置的类: BootS ...

  6. Java类载入器原理分析

    一:Java虚拟机中能够安装多个类载入器,系统默认是三个基本的类载入器: Bootstrap  ExtClassLoader  AppClassLoader 类载入器也是Java类.由于其它Java类 ...

  7. java类载入器——ClassLoader

    Java的设计初衷是主要面向嵌入式领域,对于自己定义的一些类,考虑使用依需求载入原则.即在程序使用到时才载入类,节省内存消耗,这时就可以通过类载入器来动态载入. 假设你平时仅仅是做web开发,那应该非 ...

  8. Jboss7类载入器

    1. 类载入器理论知识介绍 类载入器基于Jboss Module,代替了层次类载入环境,避免了当类存在多个版本号时,导致类载入错误. 类载入是基于模块的.必须显示的定义模块依赖.部署也是模块化的,假设 ...

  9. Android HandlerThread 消息循环机制之源代码解析

    关于 HandlerThread 这个类.可能有些人眼睛一瞟,手指放在键盘上,然后就是一阵狂敲.立即就能敲出一段段华丽的代码: HandlerThread handlerThread = new Ha ...

随机推荐

  1. PHP电影小爬虫(2)

    学习了别人的爬虫后自己改的一个,算是又回顾了一下php的使用 我们来利用simple_html_dom的采集数据实例,这是一个PHP的库,上手很容易.simple_html_dom 可以很好的帮助我们 ...

  2. Java. How to use headless browsers for crawling web and scraping data from website.--转

    https://www.linkedin.com/pulse/java-how-use-headless-browsers-crawling-web-scraping-data-taluyev/ Di ...

  3. Web程序安全机制

    ASP.NET提供了一个多层的安全模型,这个模型能够非常容易的保护Web应用程序. 安全策略没有必要非常复杂,但是需要应用安全策略的地方却是非常广泛的.程序员需要保证自己的应用程序不能被骗取,而把私有 ...

  4. Log4J2的 PatternLayout

    Log4J2 PatternLayout 参考 日志样例 : 2018-10-21 07:30:05,184 INFO - DeviceChannelServiceImpl.java:434[defa ...

  5. fresh_bank、、

    最近新学习了一个bank系统来和大家分享一下,新人求罩! 破索式之_链子枪_ 废话不多说了直接本主题 如果我们要写出bank系统,就要先考虑这个问题:总共需要几个类? 既然是银行系统,那么必不可少的就 ...

  6. Skeleton Screen — 骨架屏

    用户体验一直是前端开发需要考虑的重要部分,在数据请求时常见到锁屏的loading动画,而现在越来越多的产品倾向于使用Skeleton Screen Loading(骨架屏)替代,以优化用户体验. Sk ...

  7. suse 下的gcc安装

    在付出了一天的努力之后终于在win7系统上面硬盘安装suse操作系统成功,可是随之而来的问题居然是没有安装GCC,这对我来说是一个不小的打击,因为很多工作和工具安装需要通过GCC来编译,因此我只好求助 ...

  8. jQuery——自定义动画

    动画方法:animate(json,1000, function (){}) 参数说明:json代表属性设置,1000是动画时间,最后一个是回调函数,其中动画时间可选 属性支持:http://www. ...

  9. python PIL相关操作

    项目中需要用python生成二维码,这里记录一下相关PIL相关操作. RGBA问题: 需要将图片A粘贴到图片B上,之前没有注意透明度问题,A的背景是透明的,粘贴到B上后,A的周围是黑的.后来才发现是P ...

  10. CNN结构:HSV中的饱和度解析

    参考:颜色的前世今生-饱和度 详解,划重点- 关键这个"纯"是指什么? 是指颜色明亮么?明度高的颜色看起来也明亮啊,不一定纯度高啊- 是说颜色鲜艳么?颜色 "不鲜艳&qu ...