前言

  Tomcat遵循J2EE规范,实现了Web容器。很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解。此外,Tomcat还根据Java虚拟机规范实现了经典的双亲委派模式的类加载体系。本文基于Tomcat7.0的Java源码,对其类加载体系进行分析。

概述

  本节简单介绍Java虚拟机规范中提到的主要类加载器:

  • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
  • Extended Loader:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
  • AppClassLoader:加载System.getProperty("java.class.path")所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。

Tomcat的类加载体系

  除了JVM规范提到的以上三种类加载器之外,Tomcat还实现了自身的类加载体系。为便于理解,图1展示了Tomcat的类加载体系,各个类加载器之间不是继承关系,而是一种委派关系。

图1  Tomcat的类加载体系

这里对图1所示的类加载体系进行介绍:

  • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现;
  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见。

源码分析

  commonLoader、catalinaLoader和sharedLoader是在Tomcat容器初始化的的过程刚刚开始,即调用Bootstrap的init方法时创建的。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身的class。Bootstrap的init方法的部分如代码清单1所示。

代码清单1

    /**
* Initialize daemon.
*/
public void init()
throws Exception
{ // Set Catalina path
setCatalinaHome();
setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader);
// 省略后边的代码

  代码清单1中有关类加载器的执行步骤如下:

  1. 初始化commonLoader、catalinaLoader和sharedLoader;
  2. 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
  3. 线程安全的加载class。

初始化类加载器分析

  initClassLoaders方法的实现如代码清单2所示。

代码清单2

    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) {
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}

  从代码清单2可以看到initClassLoaders调用createClassLoader方法来创建commonLoader、catalinaLoader和sharedLoader,我们来看看createClassLoader的实现,见代码清单3。

代码清单3

    private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception { String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent; ArrayList<String> repositoryLocations = new ArrayList<String>();
ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();
int i; StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken(); // Local repository
boolean replace = false;
String before = repository;
while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaHome()
+ repository.substring(i+CATALINA_HOME_TOKEN.length());
} else {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
}
}
while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaBase()
+ repository.substring(i+CATALINA_BASE_TOKEN.length());
} else {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
}
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository); // Check for a JAR URL repository
try {
new URL(repository);
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_URL);
continue;
} catch (MalformedURLException e) {
// Ignore
} if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
} else if (repository.endsWith(".jar")) {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_JAR);
} else {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_DIR);
}
} String[] locations = repositoryLocations.toArray(new String[0]);
Integer[] types = repositoryTypes.toArray(new Integer[0]); ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(locations, types, parent); // 省略无关代码 return classLoader; }

  createClassLoader的处理步骤如下:

  1. 定位资源路径与资源类型;
  2. 使用ClassLoaderFactory创建类加载器org.apache.catalina.loader.StandardClassLoader。

  需要注意的是,Tomcat默认只会指定commonLoader(通过common属性,默认值为${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar),catalinaLoader和sharedLoader实际也是commonLoader。属性catalina.home默认为Tomcat的根目录。

安全加载class分析

  首先回头看看SecurityClassLoad.securityClassLoad(catalinaLoader)的实现,见代码清单4。

代码清单4

    public static void securityClassLoad(ClassLoader loader)
throws Exception { if( System.getSecurityManager() == null ){
return;
} loadCorePackage(loader);
loadLoaderPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadJavaxPackage(loader);
loadCoyotePackage(loader);
loadTomcatPackage(loader);
}

  securityClassLoad方法主要负责加载Tomcat容器所需的class,包括:

  • Tomcat核心class,即org.apache.catalina.core路径下的class;
  • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
  • Tomcat有关session的class,即org.apache.catalina.session路径下的class;
  • Tomcat工具类的class,即org.apache.catalina.util路径下的class;
  • javax.servlet.http.Cookie;
  • Tomcat处理请求的class,即org.apache.catalina.connector路径下的class;
  • Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class;

  以加载Tomcat核心class的loadCorePackage方法为例(见代码清单5),查看其实现。

代码清单5

    private final static void loadCorePackage(ClassLoader loader)
throws Exception {
String basePackage = "org.apache.catalina.";
loader.loadClass
(basePackage +
"core.ApplicationContextFacade$1");
loader.loadClass
(basePackage +
"core.ApplicationDispatcher$PrivilegedForward");
loader.loadClass
(basePackage +
"core.ApplicationDispatcher$PrivilegedInclude");
loader.loadClass
(basePackage +
"core.AsyncContextImpl");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$AsyncState");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$DebugException");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$1");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$2");
loader.loadClass
(basePackage +
"core.AsyncListenerWrapper");
loader.loadClass
(basePackage +
"core.ContainerBase$PrivilegedAddChild");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$1");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$2");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$3");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$4");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$5");
loader.loadClass
(basePackage +
"core.ApplicationHttpRequest$AttributeNamesEnumerator");
}

WebappClassLoader 的实现分析

  至此,我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,启动StandardContext的方法startInternal的实现见代码清单6。

代码清单6

    /**
* Start this component and implement the requirements
* of {@link LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException { // 省略前边无关的代码 if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// 省略中间无关的代码
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
// 省略后边无关的代码
}

  代码清单6的最后会调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单7。

代码清单7

    /**
* Start associated {@link ClassLoader} and implement the requirements
* of {@link LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException { // 省略无关代码// Construct a class loader based on our current repositories list
try { classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesStatic(
((StandardContext) container).getClearReferencesStatic());
classLoader.setClearReferencesStopThreads(
((StandardContext) container).getClearReferencesStopThreads());
classLoader.setClearReferencesStopTimerThreads(
((StandardContext) container).getClearReferencesStopTimerThreads());
classLoader.setClearReferencesThreadLocals(
((StandardContext) container).getClearReferencesThreadLocals());
} for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}

  最后我们看看WebappLoader的createClassLoader方法的实现,见代码清单8。

代码清单8

    /**
* Create associated classLoader.
*/
private WebappClassLoader createClassLoader()
throws Exception { //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader
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; }

  代码清单8中的parentClassLoader实际就是sharedLoader,即org.apache.catalina.loader.StandardClassLoader。由此也证实了图1中的WebappClassLoader的父类加载器是sharedLoader。至此,整个Tomcat的类加载体系构建完毕。最后我们看看WebappClassLoader(见代码清单9)是如何实现以及部署在tomcat中的各个webapp的资源是如何隔离的?

代码清单9

    @Override
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
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 = 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);
}
}
} boolean delegateLoad = delegate || filter(name); // (1) Delegate to our parent if requested
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");
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); }

  从代码清单9,可以看到WebappClassLoader加载class的步骤如下:

  1. 从之前加载的class的缓存中查找;
  2. 委托sun.misc.Launcher$AppClassLoader加载class;
  3. 安全检查通过后,委托父类加载器org.apache.catalina.loader.StandardClassLoader加载class;
  4. WebappClassLoader自己加载class。

  有关WebappClassLoader的findClass方法的实现很简单,其中主要调用findClassInternal方法来加载webapp自身路径下的class,有兴趣的读者可自行阅读源码。

如需转载,请标明本文作者及出处——作者:jiaan.gja,本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/4860432.html 

Tomcat源码分析——类加载体系的更多相关文章

  1. tomcat 源码分析

    Tomcat源码分析——Session管理分析(下)    Tomcat源码分析——Session管理分析(上)     Tomcat源码分析——请求原理分析(下)     Tomcat源码分析——请 ...

  2. Tomcat源码分析——启动与停止服务

    前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...

  3. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  4. Tomcat源码分析之—具体启动流程分析

    从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...

  5. Tomcat源码分析——Session管理分析(上)

    前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...

  6. Tomcat源码分析——请求原理分析(下)

    前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...

  7. Tomcat源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...

  8. Tomcat 源码分析(转)

    本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...

  9. Tomcat源码分析 (四)----- Pipeline和Valve

    在 Tomcat源码分析 (二)----- Tomcat整体架构及组件 中我们简单分析了一下Pipeline和Valve,并给出了整体的结构图.而这一节,我们将详细分析Tomcat里面的源码. Val ...

随机推荐

  1. OpenSSH服务及其相关应用

    远程登录工具: telnet,TCP/23:认证明文,数据传输明文,不够安全,所以出现了ssh ssh:Secure SHell,TCP/22,刚开始免费,后来商业化了,所以出现了Openssh,这个 ...

  2. Asp.NetCore安全验证之JWT

    本文只是介绍了下基于AspNetCore自带的System.IdentityModel.Tokens.Jwt.dll工具在项目中Token的应用. 我这里谈到的很浅显就两点: 一,超时时间 二,数据的 ...

  3. 【OCP-12c】2019年CUUG OCP 071考试题库(73题)

    73.Which statement correctly grants a system privilege? A. GRANT CREATE VIEW ON table1 TO user1; B. ...

  4. java中的引用类型 部分讲解

    所谓的引用类型 类--接口--数组--枚举 [01--Scanner类] Scanner 这个类是用于键盘输入 它的格式为 类型  对象名称 =  new  类型(): 它的操作格式  对象名.nex ...

  5. SQL Server IF Exists 判断数据库对象是否存在的用法

    1 判断数据库是否存在Sql代码 if exists (select * from sys.databases where name = ’数据库名’)    drop database [数据库名] ...

  6. JAVA 中的 StringBuilder 和 StringBuffer 适用的场景是什么?

    JAVA 中的 StringBuilder 和 StringBuffer 适用的场景是什么? 最简单的回答是,stringbuffer 基本没有适用场景,你应该在所有的情况下选择使用 stringbu ...

  7. BZOJ4766: 文艺计算姬(Prufer序列)

    题面 传送门 题解 结,结论题? 答案就是\(n^{m-1}m^{n-1}\) 我们考虑它的\(Prufer\)序列,最后剩下的两个点肯定是一个在左边一个在右边,设左边\(n\)个点,右边\(m\)个 ...

  8. CTF中密码学一些基础【三】

    本文作者:i春秋签约作家——MAX. 看看今天教程: 看着几个字符在键盘的位置,直接就是三个圈圈,圆心的三个字符就是答案 非常简单! 答案就是KEY 看题解密就好了!! 根据提示Asp encode解 ...

  9. Maven的Mirror和Repository 的详细讲解

    1 Repository(仓库) 1.1 Maven仓库主要有2种: remote repository:相当于公共的仓库,大家都能访问到,一般可以用URL的形式访问 local repository ...

  10. J2SE基本安装和java的环境变量

    J2SE基本安装和java的环境变量   1. 首先登录http://www.oracle.com,下载JDK(J2SE) JDK有很多版本其中JDK 1.0,1.1,1.2,1.3,1.4 1.5 ...