不同类加载器加载的类之间是不兼容的(继承,实现,强制转换等),相当于jvm通过类加载机制提供了不同的类空间。
在进行程序的架构设计时,可以通过类加载机制实现更好的模块划分和更优的实现,在tomcat7的代码中,为了优化内存和实现不同应用程序之间的隔离,tomcat实现了一些自定义的类加载器来完成这些功能。
1、jvm的类加载机制
JVM中通常有三层默认的类加载器:
1) 根类加载器(Bootstrap,c++实现)
2) 扩展类加载器(Extension,java实现)
3) 系统类加载器(System,java实现)
这三者间都是父子关系,即前者是后者的父亲或者双亲类加载器,并由此构建了一个“双亲委派关系”,或叫“代理”关系。
2、ClassLoader
ClassLoader类有如下三个关键方法:
1) loaderClass(String name,Boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的此方法来获取指定类对应的Class对象。
2) findClass(String name):根据二进制名称来查找类。
3) Class defineClass(String name,byte[] b,int off,int len)该方法负责将指定类的字节码文件(即Class文件)读入字节数组byte[]b,并把它转化为Class对象,该字节码文件可以来源于文件,网络等。
实现自定义的ClassLoader,可以通过重写以上两个方法来实现,一般重写findClass()方法,而不是重写loadClass()方法,因为loadClass()方法的执行步骤如下:
1) 用findLoadedClass(String )来检查是否已经加载类,如果已经加载则直接返回。
2) 在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器来加载
3) 调用findClass(String)方法查找类
从以上执行过程可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略,如果重写loadClass()方法,则需要自己实现。
3、tomcat基础类加载器
Tomcat是一个Java工程,也是在jvm提供的几层类加载器下运行起来的。Tomcat中的一些最基本的类,也和其它Java工程一样,是通过jvm的类加载器来加载的,比如tomcat/bin目录下的bootstrap.jar、tomcat-juli.jar、commons-daemon.jar这几个jar包中的类。
在Tomcat启动时,会定义和初始化3个类加载器,commonLoader,catalinaLoader和sharedLoader。他们的加载范围是在catalina.properties配置文件中配置的。
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
其中最重要的一个类加载器是commonLoader,它的父类加载器是AppClassLoader,负责加载 $CATALINA_BASE/lib、 $CATALINA_HOME/lib两个目录下所有的.class跟.jar文件。
在Tomcat7中,catalinaLoader和sharedLoader这两个类加载器也存在,只是默认情况下catalina.properties配置文件没有对server.loader跟share.loader两项进行配置,所以在程序里,这两个类加载器实例就被赋值为commonLoader,即一个tomcat实例其实就只有commonLoader实例。
Tomcat启动时初始化类加载器的代码:
private void initClassLoaders() {
try {
//创建commonLoader
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();
}
//commonLoader作为参数传进createClassLoader方法
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
首先创建commonLoader,再把commonLoader作为参数传给createClassLoader方法里,此方法中会根据catalina.properties中的server.loader和share.loader属性是否为空判断是否创建新的类加载器,如果属性为空则把commonLoader直接赋值给catalinaLoader和sharedLoader。否则创建新的类加载器,其父类加载器为commonLoader。
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//catalina.properties配置文件中的对应项
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
//${catalina.base}等替换为绝对路径
value = replace(value);
List repositories = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(value, ",");//拆分
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken().trim();
if (repository.length() == 0) {
continue;
}
// …Check for a JAR URL repository
//将加载路径封装为Repository对象,根据路径后缀分为DIR,GLOB,JAR,URL四种类型
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));
}
}
//创建类加载器
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
在这个方法中,将创建类加载器的任务交给ClassLoaderFactory的createClassLoader方法实现,该方法最终会通过URLClassLoader类的构造方法创建类加载器,因此commonLoader是URLClassLoader类的实例,而URLClassLoader最终继承于java.lang.ClassLoader。
4、WebappLoader类加载器
Tomcat中可以部署多个应用程序,应用程序的部署可以通过server.xml中的context标签完成。在具体的tomcat源码中,Context对象表示一个应用程序,它使用WebappLoader类中的WebappClassLoader加载应用,WebappLoader继承了Lifecycle接口,由tomcat进行生命周期管理。
StandardContext启动部分的代码:
protected synchronized void startInternal() throws LifecycleException {
//...
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();
//...
}
}catch(..){
//...
}
//...
}
启动StandardContext时,会初始化和启动WebappLoader的实例loader。在此方法中,通过((Lifecycle) loader).start()方法创建WebappClassLoader类型的类加载器。
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.setClearReferencesHttpClientKeepAliveThread(
((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
}
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
// Configure our repositories
setRepositories();
setClassPath();
setPermissions();
((Lifecycle) classLoader).start();
//...
} catch (Throwable t) {
//...
}
//...
}
WebappLoader中保存的类加载器WebappClassLoader,是Tomcat最重要的类加载器,它创造了各个应用程序空间。
通常实现自定义的类加载器重写findClass方法就可以了,这里由于实现的需要,重写loadClass()方法,打破了系统默认的“双亲委托”机制,自定义了一套加载流程:
默认情况下,对于一个未加载过的类,WebappClassLoader会先让系统加载java.lang.Object等Java本身的基础类,如果不是java本身的类则优先在当前应用Context范围内查找并加载,如果不能加载,再交给commonLoader走标准的双亲委派机制加载。每个WebappLoader加载一个Web程序,加载路径为/WebApp/WEB-INF目录。
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
//...
Class clazz = null;
// (0) 检查本地缓存中是否加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.1)检查父类加载器是否已加载过
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.2) 先尝试用jvm的系统类加载器加载此类,以防止应用中的类覆盖系统类
try {
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);
}
}
}
/**
* JVM的ClassLoader建议先由parent去load,load不到自己再去加载,
* 而Servlet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的 delegate定义),
* 默认使用Servlet规范的建议,即delegate=false
*/
boolean delegateLoad = delegate || filter(name);
// (1) delegateLoad为true时,先由parent去尝试加载
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) findClass搜索本地目录WEB-INF/lib和WEB-INF/classes,默认是先搜索WEB-INF/classes后搜索WEB-INF/lib
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) 由parent再尝试加载一下
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);
}
由于每个Web应用程序都有单独的WebAppClassLoader,一个tomcat中的多个应用之间就可以相互隔离。因此可以使tomcat不受应用程序影响,commonLoader是WebAppClassLoader的父加载器,它的存在可以使多个应用之间共享类库。
参考:
http://cuda.itpub.net/29900383/viewspace-1735725/
http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者何欢授权发布。