二. 初始化
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)方法。
发表评论
-
出现java.lang.UnsupportedClassVersionError 错误的原因
2010-08-16 23:11 867出现java.lang.UnsupportedClassVer ... -
Tomcat请求处理(七) - Servlet实例的调用
2009-05-07 11:36 1125Tomcat请求处理中Servlet实例的调用是和Filter ... -
请求在Tomcat中传到了CoyoteAdapter的#service()方法中后,就要准备进入Pi
2009-05-07 11:35 1797首先,来看一下Servlet的载入过程。 具体是在org.ap ... -
Tomcat请求处理(五) -- 请求在容器间的流动
2009-05-07 11:34 1428请求在Tomcat中传到了CoyoteAdapter的#ser ... -
Tomcat请求处理(四) -- Request, Response, 和Pipeline
2009-05-07 11:33 12811. Request和Response 当处理请求的时候,To ... -
Tomcat请求处理(三) -- coyote请求处理
2009-05-07 11:32 1319在上一篇文章文章中,Tomcat的请求处理到了JIoEndpo ... -
Tomcat请求处理(二) -- 请求处理框架
2009-05-07 11:30 983书接上文。 当Tomcat的Acceptor监听到有请求到来时 ... -
Tomcat请求处理(一) -- 服务器端口监听
2009-05-07 11:29 1393其实tomcat在哪个类中监听请求的代码很容易找到: 在org ... -
Tomcat启动部分源代码分析(五) -- 应用程序加载
2009-05-07 11:28 1194前面所叙述的tomcat启动 ... -
Tomcat启动部分源代码分析(四) -- 开启容器
2009-05-07 11:27 1199四. 开启容器 最后是Bootstrap#start()方法的 ... -
Tomcat启动部分源代码分析(三) -- 载入
2009-05-07 11:23 1188二. 载入 2. Bootstrap的#Bootstrap#l ... -
Tomcat启动部分源代码分析(一) -- 概览
2009-05-07 11:17 1426一. 概览 本文所涉及的Tomcat为6.0版本。 Tomca ... -
Tomcat的Session管理(二) - Session后台处理
2009-05-07 10:10 968Tomcat会开启一个后台线程每隔一段时间检查Session的 ... -
Tomcat的Session管理(一) - Session的生成
2009-05-07 10:02 1490Session对象的创建一般是源于这样的一条语句: Sessi ...
相关推荐
笔记作者在深入Tomcat源代码时发现,请求处理过程中,过滤器链的组装是在`StandardWrapperValve`中进行的,具体实现位于`org.apache.catalina.core.ApplicationFilterFactory.createFilterChain`方法。作者尝试复制...
对于初学者,建议先从main方法开始,找到Tomcat启动的地方,然后逐步追踪到各个组件的初始化和启动过程。同时,阅读和理解web.xml以及server.xml等配置文件,它们定义了Tomcat的行为和配置。 此外,阅读源代码的...
Apache Tomcat 6的源代码结构清晰,主要包含以下几个关键部分: - `conf`:配置文件目录,如server.xml、context.xml等。 - `webapps`:存放Web应用的目录。 - `bin`:启动和关闭Tomcat的脚本。 - `lib`:包含...
【标题】:“Tomcat 7 源码分析 - 4 server初始化背后getServer().init()” 在这篇文章中,我们将深入探讨Apache Tomcat 7服务器的内部工作机制,重点关注`getServer().init()`方法在服务器初始化过程中的作用。...
Tomcat中的组件都遵循一个标准的生命周期,包括初始化、启动、暂停、停止和销毁。`Lifecycle`接口和`LifecycleListener`接口定义了这个过程。 6. **部署与加载** Tomcat使用`org.apache.catalina.deploy`和`org....
Apache Tomcat源代码主要分为以下几个关键部分: 1. **Catalina**: 这是Tomcat的核心组件,负责处理Servlet和JSP的生命周期管理,包括加载、实例化、初始化、服务和销毁。Catalina提供了基于容器的概念,每个Web...
访问`http://localhost:8080/jenkins`(默认端口)来初始化Jenkins。 7. **安全设置**:Jenkins首次启动会生成一个管理员密码,需要在浏览器中输入以继续配置。 8. **插件管理**:根据项目需求,安装必要的Jenkins...
《深入剖析Tomcat源代码:探索底层实现原理》 Tomcat作为开源的Java Servlet容器,是许多Web开发者和系统管理员的首选。它以其轻量级、高效和稳定性著称,而深入理解其源代码,有助于我们更好地优化应用性能,解决...
它主要负责加载配置、初始化全局对象、启动JVM服务,最后调用Catalina的start方法启动整个服务器。通过分析这个类,可以了解Tomcat的启动流程和配置加载机制。 三、源代码编译 在Eclipse中导入apache-tomcat-6.0.41...
【标题】:“在Eclipse中导入和运行的Tomcat源代码” 【正文】: Tomcat,作为Apache软件基金会的一个开源项目,是Java Servlet和JavaServer Pages(JSP)的最广泛应用服务器之一。它以其轻量级、高效和易用性而广...
对于初学者,理解Tomcat源代码有助于提升对Java Web应用服务器的理解,而对于经验丰富的开发者,阅读源代码可以解决实际问题,例如性能优化、调试或自定义扩展。然而,由于这是5.5.28的老版本,建议使用更新的版本...
【标题】"Tomcat 8源代码 Servlet源代码" 涉及到的是Apache Tomcat服务器的源码分析,特别是Servlet容器的相关实现。Tomcat是一个开源的轻量级Web应用服务器,广泛用于部署Java Servlet和JavaServer Pages (JSP)应用...
2. **生命周期管理**:容器及其组件如何启动、停止和加载,以及在不同阶段进行的初始化和销毁操作。 3. **会话管理**:Tomcat如何实现会话跟踪,包括会话的创建、过期、复制和分布式环境下的管理。 4. **安全配置*...
Tomcat源码中包含对Web应用生命周期的管理,如初始化、启动、停止和卸载等阶段的处理。 9. **部署与配置**: 通过`server.xml`, `web.xml`等配置文件,开发者可以定制Tomcat的行为和Web应用的部署方式。 10. **...
《深入剖析Tomcat源代码:...总结起来,这份Tomcat源代码是学习Java Web技术的宝贵资料,它涵盖了Servlet和JSP的核心概念,通过实践和分析,开发者能够提升自己的技能水平,为日后的项目开发和问题解决打下坚实基础。
当你下载并解压Tomcat 8的源代码后,可以将其导入Eclipse或其他IDE中进行分析和学习。源代码目录结构清晰,包括`common`, `conf`, `webapps`, `work`, `logs`, `bin`, 和 `lib`等目录,它们各自承载着特定的功能。 ...
《深入剖析Tomcat 5.0.28源代码》 Tomcat,作为Apache软件基金会下的一个开源项目,是Java Servlet和JavaServer Pages...因此,无论是初学者还是经验丰富的开发者,对Tomcat源代码的探索都是一项极其有价值的任务。
在`org.apache.catalina.util.LifecycleBase`类中,可以看到Tomcat组件的生命周期管理,包括初始化、启动、停止和销毁等状态的转换。这对于理解如何在Tomcat中管理和控制自定义组件的生命周期至关重要。 4. **连接...
"apache-tomcat-7.0.57-src"是Tomcat 7.0.57版本的源代码包,对于开发者来说,深入理解其内部工作原理、自定义配置或进行二次开发具有重要意义。 1. **Tomcat结构**: - `bin`目录:包含启动和停止Tomcat的脚本。 ...
Apache Tomcat 7.0.14 源代码是Java Web服务器和Servlet容器的开源实现,它遵循Java EE(现在称为Jakarta EE)规范,特别是Servlet、JSP(JavaServer Pages)和EL(Expression Language)标准。这个版本的Tomcat在...