从Tomcat源码看其类加载实现
2015-12-31 20:19
696 查看
上篇博文主要从jdk源码角度讲解JVM的类加载委派机制,现在来看看tomcat是如何实现类加载的。
首先从大的方向上,Tomcat的类加载可配置成两种,一种是常说的“委派机制”,另外一种简单地说和“委派机制”有些不同。如何配置成“委派机制”?就是在想要配置的webapp <Context>节点下,内置 <Loader delegate="true"/>就可以了。先看看其“委派机制”。
先看一张图:
1)设置catalina.home、catalina.base环境变量,这主要是针对tomcat实例部署上的区别,可以网上了解多实例部署;
2)初始化commonLoader、catalinaLoader、sharedLoader,这三个ClassLoader在tomcat5.5版本及之前的类加载机制中是明显区分的,但是现在Tomcat6、7都已经不区分了。所以一般在catalina.home\conf\catalina.properties中采用默认配置:server.loader、shared.loader为空,配置common.loader,即:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
initClassLoaders()在创建commonLoader时,首先根据common.loader所配置的路径,获取URLs(这些URL的jar都是Tomcat自身源码打的jar),来创建commonLoader(注意commonLoader是继承于URLClassLoader的),所以commonLoader会加载common.loader下的class,也就是说Tomcat源码中类都是由commonLoader来加载的,当然,除了bootstrap.jar、tomcat-juli.jar。由于server.loader、shared.loader路径都为空,所以catalinaLoader、sharedLoader都与commonLoader指向同一个ClassLoader。在Tomcat的多实例部署中,可分别在每个实例的catalina.base/lib下面放置其下Webapp所公用的jar,在${catalina.home}/lib下放置所有Webapp所公用的jar。
另外这里需要留意一下Thread的contextClassLoader,这个ClassLoader的作用是什么?一般在“委派机制”中,低级委派层次的ClassLoader如果加载较高层次的class,如java.lang.String,是委派给较高委派层次的ClassLoader去加载,那反过来,如果较高委派层次的ClassLoader需要加载较低层次的class,这就需要用到Thread的contextClassLoader(当然,这种反过来的加载行为一般都是自行实现较高层次、较低层次的ClassLoader,因为jdk源码中Bootstrap
ClassLoader、ExtClassLoader、AppClassLoader是严格遵守“委派机制”的,不会反过来去加载类的)。就是说,通过设置当前线程的contextClassLoader来反向加载:
下面举例说明Tomcat是加载Webapp下的conf\web.xml:
先说明:commonLoader为较高层次的ClassLoader,WebappClassLoader为较低层次的,commonLoader为WebappClassLoader的parent ClassLoader。commonLoader加载了org.apache.catalina.core.StandardContext,StandardContext在startInternal()中,需要加载conf\web.xml,这样会加载WEB-INF\classes、WEB-INF\lib下面的class,看看startInternal()中一段源码:
try {
...省略其他部分代码
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Notify our interested LifecycleListeners,这里会触发加载conf\web.xml
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
...省略其他部分代码
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
再看看bindThread():
protected ClassLoader bindThread() {
ClassLoader oldContextClassLoader =
Thread.currentThread().getContextClassLoader();
if (getResources() == null)
return oldContextClassLoader;
if (getLoader() != null && getLoader().getClassLoader() != null) {
// 将当前Thread contextClassLoader设置为WebappClassLoader
// 这样WebappClassLoader就能加载Webapp路径WEB-INF\classes、WEB-INF\lib下的class
Thread.currentThread().setContextClassLoader
(getLoader().getClassLoader());
}
...省略其他部分代码
return oldContextClassLoader;
} 在StandardContext的startInternal中, 通过Thread的contextClassLoader来传递WebappClassLoader,让WebappClassLoader来加载较低层次的class。另外,需要说明的是,当前线程所创建出来的线程,其contextClassLoader会具有传递性,对于线程池也是如此。可以看看Thread的源码。
@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);
}
}
// 检测WebappClassLoader的class缓存resourceEntries是否有待加载的name,
// 如果存在则说明WebappClassLoader已经加载过该class
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug
bba6
(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 检测JVM是否已经加载过该class
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// j2seClassLoader先加载,其中j2seClassLoader为ExtClassLoader,
// J2SE classes是需要j2seClassLoader加载的
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);
}
}
}
boolean delegateLoad = delegate || filter(name);
// 如果配置了“委派机制”,先让WebappClassLoader的parent ClassLoader即commonLoader来加载
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
}
}
// WebappClassLoader自行加载,依次在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
}
// 如果未配置“委派机制”,最后让WebappClassLoader的parent ClassLoader即commonLoader来加载
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);
} 所以,对于Tomcat类加载委派机制与非委派机制的区别,就是看commonLoader与WebappClassLoader谁先加载的,如果是commonLoader先加载,当然就是采用Tomcat的类加载委派机制,否则就是非委派机制。
首先从大的方向上,Tomcat的类加载可配置成两种,一种是常说的“委派机制”,另外一种简单地说和“委派机制”有些不同。如何配置成“委派机制”?就是在想要配置的webapp <Context>节点下,内置 <Loader delegate="true"/>就可以了。先看看其“委派机制”。
先看一张图:
1. 启动初始化 ——涉及类加载部分
Bootstrap ClassLoader、ExtClassLoader、AppClassLoader指的jvm的那套委派关系图,这个已经很熟悉,就不说了。需要注意的是Tomcat如何设置AppClassLoader的“java.class.path”环境变量?以linux系统下为例,其会在$CATALINA_HOME\bin\catalina.sh中设置该环境变量。其实就是增加了两个jar:bootstrap.jar、tomcat-juli.jar,所以tomcat在启动的时候,是利用catalina.sh脚本设置"java.class.path"环境,依靠AppClassLoader加载org.apache.catalina.startup.Bootstrap.class来启动的。// 为了说明重点,简化了main的源码,详细请看tomcat源码 public static void main(String args[]) { Bootstrap bootstrap = new Bootstrap(); bootstrap.init(); if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); } }在Bootstrap.class的main函数中,有三个重要环节:bootstrap.init()、daemon.load(args)、daemon.start(),其中daemon为Bootstrap的单例实现。看看init()做了什么:
/** Initialize daemon.*/ public void init() throws Exception { // Set Catalina path setCatalinaHome(); // 设置catalina.home环境变量 setCatalinaBase(); // 设置catalina.base环境变量 initClassLoaders(); // 初始化commonLoader、catalinaLoader、sharedLoader Thread.currentThread().setContextClassLoader(catalinaLoader); // 设置main线程contextClassLoader SecurityClassLoad.securityClassLoad(catalinaLoader); // catalinaLoader加载Catalina.class Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // 利用反射,设置Catalina实例parentClassLoader为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; }看看initClassLoaders:
private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) { commonLoader=this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } }从init()、initClassLoaders()源码中,初始化的主要工作:
1)设置catalina.home、catalina.base环境变量,这主要是针对tomcat实例部署上的区别,可以网上了解多实例部署;
2)初始化commonLoader、catalinaLoader、sharedLoader,这三个ClassLoader在tomcat5.5版本及之前的类加载机制中是明显区分的,但是现在Tomcat6、7都已经不区分了。所以一般在catalina.home\conf\catalina.properties中采用默认配置:server.loader、shared.loader为空,配置common.loader,即:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
initClassLoaders()在创建commonLoader时,首先根据common.loader所配置的路径,获取URLs(这些URL的jar都是Tomcat自身源码打的jar),来创建commonLoader(注意commonLoader是继承于URLClassLoader的),所以commonLoader会加载common.loader下的class,也就是说Tomcat源码中类都是由commonLoader来加载的,当然,除了bootstrap.jar、tomcat-juli.jar。由于server.loader、shared.loader路径都为空,所以catalinaLoader、sharedLoader都与commonLoader指向同一个ClassLoader。在Tomcat的多实例部署中,可分别在每个实例的catalina.base/lib下面放置其下Webapp所公用的jar,在${catalina.home}/lib下放置所有Webapp所公用的jar。
另外这里需要留意一下Thread的contextClassLoader,这个ClassLoader的作用是什么?一般在“委派机制”中,低级委派层次的ClassLoader如果加载较高层次的class,如java.lang.String,是委派给较高委派层次的ClassLoader去加载,那反过来,如果较高委派层次的ClassLoader需要加载较低层次的class,这就需要用到Thread的contextClassLoader(当然,这种反过来的加载行为一般都是自行实现较高层次、较低层次的ClassLoader,因为jdk源码中Bootstrap
ClassLoader、ExtClassLoader、AppClassLoader是严格遵守“委派机制”的,不会反过来去加载类的)。就是说,通过设置当前线程的contextClassLoader来反向加载:
Thread.currentThread().setContextClassLoader(lowerClassLoader); // lowerClassLoader为较低委派层次的ClassLoader
下面举例说明Tomcat是加载Webapp下的conf\web.xml:
先说明:commonLoader为较高层次的ClassLoader,WebappClassLoader为较低层次的,commonLoader为WebappClassLoader的parent ClassLoader。commonLoader加载了org.apache.catalina.core.StandardContext,StandardContext在startInternal()中,需要加载conf\web.xml,这样会加载WEB-INF\classes、WEB-INF\lib下面的class,看看startInternal()中一段源码:
try {
...省略其他部分代码
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Notify our interested LifecycleListeners,这里会触发加载conf\web.xml
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
...省略其他部分代码
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
再看看bindThread():
protected ClassLoader bindThread() {
ClassLoader oldContextClassLoader =
Thread.currentThread().getContextClassLoader();
if (getResources() == null)
return oldContextClassLoader;
if (getLoader() != null && getLoader().getClassLoader() != null) {
// 将当前Thread contextClassLoader设置为WebappClassLoader
// 这样WebappClassLoader就能加载Webapp路径WEB-INF\classes、WEB-INF\lib下的class
Thread.currentThread().setContextClassLoader
(getLoader().getClassLoader());
}
...省略其他部分代码
return oldContextClassLoader;
} 在StandardContext的startInternal中, 通过Thread的contextClassLoader来传递WebappClassLoader,让WebappClassLoader来加载较低层次的class。另外,需要说明的是,当前线程所创建出来的线程,其contextClassLoader会具有传递性,对于线程池也是如此。可以看看Thread的源码。
2. WebappClassLoader加载class
WebappClassLoader的loadClass函数:@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);
}
}
// 检测WebappClassLoader的class缓存resourceEntries是否有待加载的name,
// 如果存在则说明WebappClassLoader已经加载过该class
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug
bba6
(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 检测JVM是否已经加载过该class
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// j2seClassLoader先加载,其中j2seClassLoader为ExtClassLoader,
// J2SE classes是需要j2seClassLoader加载的
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);
}
}
}
boolean delegateLoad = delegate || filter(name);
// 如果配置了“委派机制”,先让WebappClassLoader的parent ClassLoader即commonLoader来加载
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
}
}
// WebappClassLoader自行加载,依次在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
}
// 如果未配置“委派机制”,最后让WebappClassLoader的parent ClassLoader即commonLoader来加载
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);
} 所以,对于Tomcat类加载委派机制与非委派机制的区别,就是看commonLoader与WebappClassLoader谁先加载的,如果是commonLoader先加载,当然就是采用Tomcat的类加载委派机制,否则就是非委派机制。
相关文章推荐
- java-模拟tomcat服务器
- i-jetty环境搭配与编译
- Android Native 绘图方法
- 从源码安装Mysql/Percona 5.5
- 实现单Tomcat多Server配置
- 生产环境下的Tomcat配置
- Linux部署Tomcat服务器
- jenkins------结合maven将svn项目自动部署到tomcat下
- 如何搞定tomcat这只喵~
- C#中struct和class的区别详解
- 浅析Ruby的源代码布局及其编程风格
- VBS ArrayList Class vbs中的数组类
- 大家看了就明白了css样式中类class与标识id选择符的区别小结
- 深入了解PHP类Class的概念
- asp.net 抓取网页源码三种实现方法
- jquery 表单验证之通过 class验证表单不为空
- setAttribute 与 class冲突解决
- JavaScript中的类(Class)详细介绍
- javascript面向对象包装类Class封装类库剖析