从Tomcat源码看其类加载实现

       上篇博文主要从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("  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的类加载委派机制,否则就是非委派机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值