`
刘琛颖
  • 浏览: 48355 次
  • 性别: Icon_minigender_1
  • 来自: 成都
最近访客 更多访客>>
社区版块
存档分类
最新评论

Tomcat启动部分源代码分析(二) -- 初始化

阅读更多

二. 初始化

1. 首先是Bootstrap的#init()操作。

	public void init() throws Exception {

		// 设定Catalina
		setCatalinaHome();
		setCatalinaBase();

		// 初始化ClassLoader
		initClassLoaders();

		// 设置线程的上下文的ClassLoader
		Thread.currentThread().setContextClassLoader(catalinaLoader);

		SecurityClassLoad.securityClassLoad(catalinaLoader);

		// 实例化Catalina对象
		if (log.isDebugEnabled())
			log.debug("Loading startup class");
		Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
		Object startupInstance = startupClass.newInstance();

		if (log.isDebugEnabled())
			log.debug("Setting startup class properties");
		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;

	}



init方法主要是对Catalina环境的设定和ClassLoader的初始化,下面来一个一个的查看:

① Catalina的环境设定:
这里主要是setCatalinaHome()和setCatalinaBase()这两个方法

	private void setCatalinaHome() {
		if (System.getProperty("catalina.home") != null)
			return;
		File bootstrapJar = new File(System.getProperty("user.dir"), "bootstrap.jar");
		if (bootstrapJar.exists()) {
			try {
				System.setProperty("catalina.home",
						(new File(System.getProperty("user.dir"), "..")).getCanonicalPath());
			} catch (Exception e) {
				// Ignore
				System.setProperty("catalina.home", System.getProperty("user.dir"));
			}
		} else {
			System.setProperty("catalina.home", System.getProperty("user.dir"));
		}

	}



这个方法主要是对catalina.home的设定。

	private void setCatalinaBase() {

		if (System.getProperty("catalina.base") != null)
			return;
		if (System.getProperty("catalina.home") != null)
			System.setProperty("catalina.base", System.getProperty("catalina.home"));
		else
			System.setProperty("catalina.base", System.getProperty("user.dir"));

	}



这个方法主要是对catalina.base的设定。

② 初始化ClassLoader

ClassLoader在Tomcat的启动中是比较重要的部分,我在Apache Tomcat的网站上找到了一些关于Tomcat ClassLoader的说明,简单的介绍下。

这个是Tomcat的ClassLoader的继承结构

       Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 ...

Bootstrap是JVM提供的ClassLoader,它主要负责载入$JAVA_HOME/jre/lib/ext下的类文件。

System主要负责载入CLASSPATH下的类文件,这些类对Tomcat容器内部的类和Web应用程序都可见。

Common主要载入$CATALINA_HOME/lib下面的类文件,也是同时对Tomcat容器内部的类和Web应用程序都可见。

WebappX载入每一个Web应用程序的类文件,包括/WEB-INF/classes下的类文件和/WEB-INF/lib下的jar文件。并且WebApp之间是不可见的。

而Tomcat5.5的ClassLoader结构是与Tomcat6.0有所不同的

       Bootstrap
          |
       System
          |
       Common
      /      \
Catalina   Shared
             /   \
        Webapp1  Webapp2 ...

在Common下边还有Catalina和Shared两个ClassLoader(Tomcat6.0也是有的,只是默认没有使用)。Catalina负责载入Tomcat内部可见而WebApp不可见的类文件。Shared负责载入所有WebApp都可见的类文件。

原文的网址是: http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html
http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html

简单介绍过之后,看一下#initClassLoaders()方法,此方法中实现了ClassLoader的初始化:

	private void initClassLoaders() {
		try {
			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();
			}
			// 父loader是commonloader
			catalinaLoader = createClassLoader("server", commonLoader);
			// 父loader是commonloader
			sharedLoader = createClassLoader("shared", commonLoader);
		} catch (Throwable t) {
			log.error("Class loader creation threw exception", t);
			System.exit(1);
		}
	}



这个方法很容易看懂,就是对3个ClassLoader的初始化。其中调用了createClassLoader()方法,这个也是比较重要的,下面来看下:

	private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {

		// 获取与ClassLoader相关的属性值,可能为common.loader, server.loader, shared.loader
		// 定义在org/apache/catalina/startup/catalina.properties
		String value = CatalinaProperties.getProperty(name + ".loader");
		// catalinaLoader和sharedLoader默认没有配置值,所以默认使用父loader,就是Common loader
		if ((value == null) || (value.equals("")))// 如果没有定义,那么使用父loader
			return parent;

		// 路径位置
		ArrayList repositoryLocations = new ArrayList();
		// 路径类型
		ArrayList repositoryTypes = new ArrayList();
		int i;

		// ClassLoader将载入的路径是用","分割的
		StringTokenizer tokenizer = new StringTokenizer(value, ",");
		while (tokenizer.hasMoreElements()) {
			// 类文件路径
			String repository = tokenizer.nextToken();

			// 是否对目录进行过替换
			boolean replace = false;
			String before = repository;

			// 对"${catalina.home}"进行替换
			while ((i = repository.indexOf(CATALINA_HOME_TOKEN)) >= 0) {
				replace = true;
				if (i > 0) {// 如果"${catalina.home}"不是在字符串首
					repository = repository.substring(0, i) + getCatalinaHome()
							+ repository.substring(i + CATALINA_HOME_TOKEN.length());
				} else {// 如果"${catalina.home}"在字符串首
					repository = getCatalinaHome()
							+ repository.substring(CATALINA_HOME_TOKEN.length());
				}
			}
			// 对"${catalina.base}"进行替换
			while ((i = repository.indexOf(CATALINA_BASE_TOKEN)) >= 0) {
				replace = true;
				if (i > 0) {
					repository = repository.substring(0, i) + getCatalinaBase()
							+ repository.substring(i + CATALINA_BASE_TOKEN.length());
				} else {
					repository = getCatalinaBase()
							+ repository.substring(CATALINA_BASE_TOKEN.length());
				}
			}
			if (replace && log.isDebugEnabled())
				log.debug("Expanded " + before + " to " + replace);

			// Check for a JAR URL repository
			try {
				// url用于测试这个目录是否是合法的URL,如果不是,会报出异常,跳到后边。
				URL url = new URL(repository);
				// URL
				repositoryLocations.add(repository);
				repositoryTypes.add(ClassLoaderFactory.IS_URL);
				continue;
			} catch (MalformedURLException e) {
				// Ignore
			}

			if (repository.endsWith("*.jar")) {
				repository = repository.substring(0, repository.length() - "*.jar".length());
				// jar文件目录
				repositoryLocations.add(repository);
				repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
			} else if (repository.endsWith(".jar")) {
				// 单独jar文件
				repositoryLocations.add(repository);
				repositoryTypes.add(ClassLoaderFactory.IS_JAR);
			} else {
				// 目录
				repositoryLocations.add(repository);
				repositoryTypes.add(ClassLoaderFactory.IS_DIR);
			}
		}

		String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
		Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);

		// 返回ClassLoader
		ClassLoader classLoader = ClassLoaderFactory.createClassLoader(locations, types, parent);

		// MBean server,我不懂JMX~~
		MBeanServer mBeanServer = null;
		if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
			mBeanServer = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
		} else {
			mBeanServer = MBeanServerFactory.createMBeanServer();
		}

		ObjectName objectName = new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
		mBeanServer.registerMBean(classLoader, objectName);

		return classLoader;

	}



关键点给标上了一些注释,其中返回ClassLoader的那一行值得关注一下,
ClassLoader classLoader = ClassLoaderFactory.createClassLoader(locations, types, parent);

自然,要看一下ClassLoaderFactory#createClassLoader()到底做了什么。

public static ClassLoader createClassLoader(String locations[], Integer types[],
			ClassLoader parent) throws Exception {

		if (log.isDebugEnabled())
			log.debug("Creating new class loader");

		// Construct the "class path" for this class loader
		ArrayList list = new ArrayList();

		// 确保locations和types不为空且长度相同
		if (locations != null && types != null && locations.length == types.length) {
			// 查看每一个location
			for (int i = 0; i < locations.length; i++) {
				String location = locations[i];
				if (types[i] == IS_URL) {// URL
					URL url = new URL(location);
					if (log.isDebugEnabled())
						log.debug("  Including URL " + url);
					list.add(url);
				} else if (types[i] == IS_DIR) {// 目录
					File directory = new File(location);
					directory = new File(directory.getCanonicalPath());
					if (!directory.exists() || !directory.isDirectory() || !directory.canRead())
						continue;
					URL url = directory.toURI().toURL();
					if (log.isDebugEnabled())
						log.debug("  Including directory " + url);
					list.add(url);
				} else if (types[i] == IS_JAR) {// 单独的jar文件
					File file = new File(location);
					file = new File(file.getCanonicalPath());
					if (!file.exists() || !file.canRead())
						continue;
					URL url = file.toURI().toURL();
					if (log.isDebugEnabled())
						log.debug("  Including jar file " + url);
					list.add(url);
				} else if (types[i] == IS_GLOB) {// jar目录
					File directory = new File(location);
					if (!directory.exists() || !directory.isDirectory() || !directory.canRead())
						continue;
					if (log.isDebugEnabled())
						log.debug("  Including directory glob " + directory.getAbsolutePath());
					String filenames[] = directory.list();
					for (int j = 0; j < filenames.length; j++) {
						String filename = filenames[j].toLowerCase();
						if (!filename.endsWith(".jar"))
							continue;
						File file = new File(directory, filenames[j]);
						file = new File(file.getCanonicalPath());
						if (!file.exists() || !file.canRead())
							continue;
						if (log.isDebugEnabled())
							log.debug("    Including glob jar file " + file.getAbsolutePath());
						URL url = file.toURI().toURL();
						list.add(url);
					}
				}
			}
		}

		// 类文件的URL列表
		URL[] array = (URL[]) list.toArray(new URL[list.size()]);
		if (log.isDebugEnabled())
			for (int i = 0; i < array.length; i++) {
				log.debug("  location " + i + " is " + array[i]);
			}

		// 继承自URLClassLoader
		StandardClassLoader classLoader = null;
		if (parent == null)
			classLoader = new StandardClassLoader(array);
		else
			classLoader = new StandardClassLoader(array, parent);
		return (classLoader);

	}


至此,ClassLoader生成完毕,并将需要载入的类文件路径传给了构造函数。

初始化ClassLoader之后,调用了Thread.currentThread().setContextClassLoader(catalinaLoader);这一句包含的内容也很有趣,下面来研究一下:
虽然有些本末倒置之嫌,但还是要介绍一下ClassLoader的相关内容:

与ClassLoader相关的概念主要有4个,分别是基本的Classloader,自定义Classloader,Caller Classloader,当前线程的上下文Classloader,下面一一介绍:

基本的Classloader
⑴最基本的Classloader是Bootstrap Classloader和System Classloader(或者说AppClassLoader)

很多内容借鉴自 http://fyjava.iteye.com/blog/198119

Bootstrap Classloader

这个Classloader装载Java虚拟机提供的基本运行时刻类($JAVA_HOME/jre/lib),还包括放置在系统扩展目录($ JAVA_HOME/jre/lib/ext)内的JAR文件中的类。这个Classloader是java程序最顶层的Classloader,只有它没有父Classloader。如果你将一个自己写的类或第三方jar包放进$JAVA_HOME/jre/lib/ext目录中,那么它将被 Bootstrap Classloader装载。

System Classloader

System Classloader通常负责装载系统环境变量CLASSPATH中设置的类。

⑵自定义Classloader
在编写应用代码的时候,常常有需要动态加载类和资源,比如显式的调用classLoader.loadClass(“ClassName”),虽然直接使用 ClassLoader.getSystemClassLoader(),可以得到SystemlassLoader来完成这项任务。但是,由于 System Classloader是JVM创建的Classloader,它的职责有限,只适合于普通的java应用程序,在很多复杂场景中不能满足需求,比如在应用服务器中。这时候就需要自行实现一个Classloader的子类。

⑶Caller Classloader

Caller Classloader指的是当前所在的类装载时使用的Classloader,它可能是System Classloader,也可能是一个自定义的Classloader,这里,我们都称之为Caller Classloader。我们可以通过getClass().getClassLoader()来得到Caller Classloader。例如,存在A类,是被AClassLoader所加载,A.class.getClassLoader()为AClassLoader的实例,它就是A.class的Caller Classloader。

如果在A类中使用new关键字,或者Class.forName(String className)和Class.getResource(String resourceName)方法,那么这时也是使用Caller Classloader来装载类和资源。比如在A类中初始化B类:

/**
  * A.java
*/
...
public void foo() {
    B b = new B();
    b.setName("b");
}
那么,B类由当前Classloader,也就是AClassloader装载。同样的,修改上述的foo方法,其实现改为:

Class clazz = Class.forName("foo.B");最终获取到的clazz,也是由AClassLoader所装载。

那么,如何使用指定的Classloader去完成类和资源的装载呢?或者说,当需要去实例化一个Caller Classloader和它的父Classloader都不能装载的类时,怎么办呢?

一个很典型的例子是JAXP,当使用xerces的SAX实现时,我们首先需要通过rt.jar中的 javax.xml.parsers.SAXParserFactory.getInstance()得到xercesImpl.jar中的 org.apache.xerces.jaxp.SAXParserFactoryImpl的实例。由于JAXP的框架接口的class位于 JAVA_HOME/lib/rt.jar中,由Bootstrap Classloader装载,处于Classloader层次结构中的最顶层,而xercesImpl.jar由低层的Classloader装载,也就是说SAXParserFactoryImpl是在SAXParserFactory中实例化的,如前所述,使用SAXParserFactory的Caller Classloader(这里是Bootstrap Classloader)是完成不了这个任务的。

这时,我们就需要了解一下线程上下文Classloader了。

⑷线程上下文Classloader

每个线程都有一个关联的上下文Classloader。如果使用new Thread()方式生成新的线程,新线程将继承其父线程的上下文Classloader。如果程序对线程上下文Classloader没有任何改动的话,程序中所有的线程将都使用System Classloader作为上下文Classloader。

当使用Thread.currentThread().setContextClassLoader(classloader)时,线程上下文 Classloader就变成了指定的Classloader了。此时,在本线程的任意一处地方,调用Thread.currentThread(). getContextClassLoader(),都可以得到前面设置的Classloader。

回到JAXP的例子,假设 xercesImpl.jar只有AClassLoader能装载,现在A.class内部要使用JAXP,但是A.class却不是由 AClassLoader或者它的子Classloader装载的,那么在A.class中,应该这样写才能正确得到xercesImpl的实现:

AClassLoader aClassLoader = new AClassLoader(parent);
Thread.currentThread().setContextClassLoader(aClassLoader);
SAXParserFactory factory = SAXParserFactory.getInstance();
...
JAXP这时就可以通过线程上下文Classloader装载xercesImpl的实现类了,当然,还有一个前提是在配制文件或启动参数中指定了使用xerces作为JAXP的实现。下面是JAXP中的代码片断:

ClassLoader cl = Thread.currentThread().getContextClassLoader();

Class providerClass = cl.loadClass(className);


③ 然后,回到Bootstrap#init()方法中来,载入了ClassLoader后,执行了下述代码

	if (log.isDebugEnabled())
		log.debug("Loading startup class");
	Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
	Object startupInstance = startupClass.newInstance();

	// 调用org.apache.catalina.startup.Catalina#setParentClassLoader(),并将前面讲到的Shared ClassLoader作为参数传递给它。
	if (log.isDebugEnabled())
		log.debug("Setting startup class properties");
	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引用Catalina对象。
	catalinaDaemon = startupInstance;



setParentClassLoader()这个方法的定义倒是很简单,如下所示:

	public void setParentClassLoader(ClassLoader parentClassLoader) {

		this.parentClassLoader = parentClassLoader;

	}



具体Catalina的parentClassLoader有什么作用看到这里还无法知道。

至此,init()方法就简单看完了,下面来看一下Bootstrap#load(String[] arguments)方法。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics