在本系列的第二篇文章中,曾经介绍过在Tomcat启动时会初始化类加载器(ClassLoader),来处理整个Web工程中Class的加载问题。
类加载机制是Java平台中相当重要的核心技术,待笔者有所积累后会再次讨论这个话题。在一般的业务开发中我们可能较少接触和使用ClassLoader,但是在进行框架级程序开发时,设计良好的类加载机制能够实现更好地模块划分和更优的设计,如Java模块化技术OSGi就是通过为每个组件声明独立的类加载器来实现组件的动态部署功能。在Tomcat的代码实现中,为了优化内存空间以及不同应用间的类隔离,Tomcat通过内置的一些类加载器来完成了这些功能。
在Java语言中,ClassLoader是以父子关系存在的,Java本身也有一定的类加载规范。在Tomcat中基本的ClassLoader层级关系如下图所示:
在Tomcat启动的时候,会初始化图示所示的类加载器。而上面的三个类加载器:CommonClassLoader、CatalinaClassLoader和SharedClassLoader是与具体部署的Web应用无关的,而WebappClassLoader则对应Web应用,每个Web应用都会有独立的类加载器,从而实现类的隔离。
我们首先来看Tomcat的初始化,在Bootstrap的init方法中,会调用initClassLoaders方法,该方法负责前图中前三个类加载器的初始化:
private void initClassLoaders() {
try {
//初始化CommonClassLoader
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
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);
}
}
我们可以看到,此书初始化了三个类加载器,并且catalinaLoader和sharedLoader都以commonLoader作为父类加载器,在这个方法中,将核心的业务交给了createClassLoader方法来实现:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//读取配置属性,相关的配置属性在catalina.properties文件中
String value = CatalinaProperties.getProperty(name + ".loader");
//如果没有对应的配置,将不会创建新的类加载器,而是返回传入的父类加载器
if ((value == null) || (value.equals("")))
return parent;
//解析得到的配置文件,确定本ClassLoader要加载那些目录下的资源和JAR包等
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();
//此处省略的代码为将配置文件中的${catalina.base}、${catalina.home}等变量转
//换为绝对路径
//格式化得到的位置路径和类型
String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
//生成真正的类加载器
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(locations, types, parent);
//以下的代码为将生成的类加载器注册为MBean
return classLoader;
}
}
而每个类加载器所加载的路径或JAR是在catalina.properties文件中定义的,默认的配置如下:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
按照默认的配置,catalinaLoader和sharedLoader的配置项为空,因此不会创建对应的ClassLoader,而只会创建CommonClassLoader,该类加载器对应的Java实现类为:org.apache.catalina.loader. StandardClassLoader,该类继承自org.apache.catalina.loader. URLClassLoader,有关Tomcat基础类都会有该类加载器加载。例如在Bootstrap的init方法中,会调用Catalina类的init方法来完成相关操作:
public void init() throws Exception{
//将当前线程的类加载器设置为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
//使用catalinaLoader来加载Catalina类
Class startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
//调用Catalina的setParentClassLoader方法,设置为sharedLoader
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);
catalinaDaemon = startupInstance;
}
以上为基础的三个类加载器的初始化过程。在每个Web应用初始化的时候,StandardContext对象代表每个Web应用,它会使用WebappLoader类来加载Web应用,而WebappLoader中会初始化org.apache.catalina.loader. WebappClassLoader来为每个Web应用创建单独的类加载器
在上一篇文章中,我们介绍过,当处理请求时,容器会根据请求的地址解析出由哪个Web应用来进行对应的处理,进而将当前线程的类加载器设置为请求Web应用的类加载器。让我们看一下WebappClassLoader的核心方法,也就是loadClass:
public synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class clazz = null;
//首先检查已加载的类
// (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);
//Tomcat允许按照配置来确定优先使用本Web应用的类加载器加载还是使用父类
//加载器来进行类加载,此处先使用父类加载器进行加载
// (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);
}
以上就是Web应用中类加载的机制。在默认情况下,WebappClassLoader的父类加载器就是CommonClassLoader,但是我们可以通过修改catalina.properties文件来设置SharedClassLoader,从而实现多个Web应用共用类库的效果。