您的位置:首页 > 运维架构 > Tomcat

从Tomcat源码看其类加载实现

2015-12-31 20:19 696 查看
       上篇博文主要从jdk源码角度讲解JVM的类加载委派机制,现在来看看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的类加载委派机制,否则就是非委派机制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息