Tomcat类加载机制
1. 概述
Tomcat内置了一系列的类加载器,保证运行在容器中的不同Web应用程序使用不同的类路径和资源仓库。在Java环境中,类加载机制采用双亲委派机制;但是,在Tomcat中WebappClassLoader加载器并未采用这种机制(在后面将详细讨论)。 随着Tomcat版本的不断变化,Tomcat5,Tomcat6和Tomcat7,8在类加载的过程中存在一些不同之处,在后面章节将逐一讨论。
2. Tomcat中的类加载器
Tomcat中自定义的类加载器有4种,分别是Common,Catalina,Shared 和WebApp,具体定义如下:
名称 | 实现类 | 父加载器 | ClassPath |
Common | org.apache.catalina.loader.StandardClassLoader | system | /common/* |
Catalina | 同上 | common | /server/* |
Shared | 同上 | common | /shared/* |
WebApp | org.apache.catalina.loader.WebappClassLoader | shared | /WebApp/WEB-INF/* |
3. 类加载器初始化过程
Tomcat启动后首先启动初始化:Common,Catalina和Shared类加载器。具体调用如下:
org.apache.catalina.startup.Bootstrap#main
↓
org.apache.catalina.startup.Bootstrap#init
↓
org.apache.catalina.startup.Bootstrap#initClassLoaders
private void initClassLoaders() { try { // Common类加载器 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(); } // Catalina类加载器 catalinaLoader = createClassLoader("server", commonLoader); // Shared类加载器 sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { log.error("Class loader creation threw exception", t); System.exit(1); } }
org.apache.catalina.startup.Bootstrap#createClassLoader代码如下:
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { // 根据${Tomcat_home}\conf\catalina.properties获取属性值 String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; -中略-
4. Tomcat各个版本类加载器区别
Tomcat5.5
Tomcat5的中默认catalina.properties属性文件如下:
common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
因此,Tomcat5 会分别创建:Common,Catalina和Shared类加载器,相应关系如(pic2-1)
Tomcat6.x or later
默认catalina.properties属性文件如下:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
因此,他们创建的Common,Catalina和Shared类加载器为同一个实例,即Common,相应的关系图如下(pic4-1)
5. Tomcat的类加载机制
Tomcat 的类加载器实现类有两种:StandardClassLoader和WebappClassLoader两种。其中StandardClassLoader未重载loadClass方法,因此采用的仍然是双亲委派原则;然而WebappClassLoader重载了loadClass方法,使用了另外一种类加载策略。然而,各个版本又有稍许差别。
Tomcat6
当delegate="false"时,默认值
- 检查缓存中是否存在要加载的类
- 若缓存中没有,则首先使用系统类加载器加载,防止Web应用程序中的类覆盖J2SE的类
- 从当前仓库中载入相关类
- 使用父类载入器。若父类载入器为null,使用系统的类加载器进行加载
-
若仍未找到需要的类,则抛出ClasNotFoundException异常。
类加载顺序:
- Bootstrap classes of your JVM
- System class loader classes (described above)
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
- $CATALINA_HOME/common/classes
- $CATALINA_HOME/common/endorsed/*.jar
- $CATALINA_HOME/common/i18n/*.jar
- $CATALINA_HOME/common/lib/*.jar
- $CATALINA_BASE/shared/classes
- $CATALINA_BASE/shared/lib/*.jar
当delegate="true"时
- 检查缓存中是否存在要加载的类
- 若缓存中没有,则首先使用系统类加载器加载,防止Web应用程序中的类覆盖J2SE的类
- 使用父类载入器。若父类载入器为null,使用系统的类加载器进行加载
- 从当前仓库中载入相关类
-
若仍未找到需要的类,则抛出ClasNotFoundException异常。
类加载顺序:
- Bootstrap classes of your JVM
- System class loader classes (described above)
- $CATALINA_HOME/common/classes
- $CATALINA_HOME/common/endorsed/*.jar
- $CATALINA_HOME/common/i18n/*.jar
- $CATALINA_HOME/common/lib/*.jar
- $CATALINA_BASE/shared/classes
- $CATALINA_BASE/shared/lib/*.jar
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
备注:默认情况下,Tomcat6 shared类加载器路径。
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 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 = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // (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) { ; } // (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 = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } throw new ClassNotFoundException(name); }
Tomcat7 or later
当delegate="false"时,默认值
- 检查缓存中是否存在要加载的类
- 若缓存中没有,则首先使用引导类加载器加载,防止Web应用程序中的类覆盖J2SE的类
- 从当前仓库中载入相关类
- 使用父类载入器。若父类载入器为null,使用引导类加载器进行加载
- 若仍未找到需要的类,则抛出ClasNotFoundException异常。
类加载顺序:
- Bootstrap classes of your JVM
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
- System class loader classes (described above)
- Common class loader classes (described above)
当delegate="true"时
- 检查缓存中是否存在要加载的类
- 若缓存中没有,则首先使用引导类加载器加载,防止Web应用程序中的类覆盖J2SE的类
- 使用父类载入器。若父类载入器为null,使用引导类加载器进行加载
- 从当前仓库中载入相关类
-
若仍未找到需要的类,则抛出ClasNotFoundException异常。
类加载顺序:
- Bootstrap classes of your JVM
- System class loader classes (described above)
- Common class loader classes (described above)
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
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 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 { // j2seClassLoader为引导类加载器。 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; 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); }
j2seClassLoader类的初始化
public WebappClassLoader() { -中略- ClassLoader j = String.class.getClassLoader(); if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) { j = j.getParent(); } } this.j2seClassLoader = j; -中略- }
参考链接
[1].Tomcat5.5 Class Loader HOW-TO
[2].Tomcat6.0 Class Loader HOW-TO
[3].Tomcat7.0 Class Loader HOW-TO
[4].Tomcat8.0 Class Loader HOW-TO
[5].Tomcat download
以上。