前言
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。
- AppClass Loader:加载System.getProperty("java.class.path")所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。
这里对图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中有关类加载器的执行步骤如下:
- 初始化commonLoader、catalinaLoader和sharedLoader;
- 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
- 线程安全的加载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的处理步骤如下:
- 定位资源路径与资源类型;
- 使用ClassLoaderFactory创建类加载器org.apache.catalina.loader.StandardClassLoader。
需要注意的是,Tomcat默认只会指定commonLoader(通过common属性,默认值为 catalina.base/lib, catalina.base/lib,{catalina.base}/lib/*.jar, catalina.home/lib, 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, 查询缓存看是否class文件已经加载 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 cach, 查询缓存看是否class文件已经加载 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, 如果指定了需要委托机制,则采用jvm的类加载机制进行加载 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");
//如果没有指定采用jvm机制进行加载, 则首先自己加载class文件, 加载不了, 然后委托父类进行加载
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的步骤如下:
- 从之前加载的class的缓存中查找;
- 委托sun.misc.Launcher$AppClassLoader加载class;
- 安全检查通过后,委托父类加载器org.apache.catalina.loader.StandardClassLoader加载class;
- WebappClassLoader自己加载class。
有关WebappClassLoader的findClass方法的实现很简单,其中主要调用findClassInternal方法来加载webapp自身路径下的class,有兴趣的读者可自行阅读源码。
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
if (log.isDebugEnabled())
log.debug(" findClass(" + name + ")");
checkStateForClassLoading(name);
// (1) Permission to define this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
if (log.isTraceEnabled())
log.trace(" securityManager.checkPackageDefinition");
securityManager.checkPackageDefinition(name.substring(0,i));
} catch (Exception se) {
if (log.isTraceEnabled())
log.trace(" -->Exception-->ClassNotFoundException", se);
throw new ClassNotFoundException(name, se);
}
}
}
// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
if (log.isTraceEnabled())
log.trace(" findClassInternal(" + name + ")");
try {
//自己加载,从WebSource中加载, WebSource关联了路径WEB-INF/classes和WEB-INF/lib
if (securityManager != null) {
PrivilegedAction<Class<?>> dp =
new PrivilegedFindClassByName(name);
clazz = AccessController.doPrivileged(dp);
} else {
clazz = findClassInternal(name);
}
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
//如果自己加载不了,则交给父加载器加载
if ((clazz == null) && hasExternalRepositories) {
try {
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
//如果父加载器也加载不了,则抛出异常
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
// Return the class we have located
if (log.isTraceEnabled())
log.debug(" Returning class " + clazz);
if (log.isTraceEnabled()) {
ClassLoader cl;
if (Globals.IS_SECURITY_ENABLED){
cl = AccessController.doPrivileged(
new PrivilegedGetClassLoader(clazz));
} else {
cl = clazz.getClassLoader();
}
log.debug(" Loaded by " + cl.toString());
}
return (clazz);
}