Tomcat7源码分析——启动过程和类加载器
2016-07-03 09:36
1111 查看
0. Tomcat简介
Tomcat,全名Apache Tomcat,是Apache Jakarta下的一个子项目。Tomcat是对Servlet API定义的容器的一个完整实现,同时它也不仅仅是一个容器,也完全可以当做一个纯Java实现的HTTP服务器来使用。按照维基百科最早的记载,是在1999年发布了3.0.x版本,可以说是一个比较早的Servlet容器实现。最重要的,Tomcat是开源的,并且完整性很好,我们本文以Tomcat为例,进行分析。当然,其它优秀的servlet容器还有很多,如Jetty等。
1. 启动过程
不管是操作系统,还是应用程序,都要从无到有,有一个循序渐进的启动过程。Tomcat也不例外,也是一切从头开始。嵌入其它应用,通过代码启动在新版本的Tomcat中也是支持的,但本文这里已最常见的命令行启动为例开始介绍。我们打开Tomcat的压缩包,里面有startup.sh脚本和为了支持Windows环境的.bat文件,Tomcat启停的奥义就在这里。startup.sh里有一句:
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
其中的$EXECUTABLE是在上面赋值的catalina.sh,也就是说会去执行catalina.sh,而且紧随其后的参数是start,我们继续看catalina.sh这个脚本,里面有这么一行开启的段落:
elif [ "$1" = "start" ] ; then
最终的执行语句是:
eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ >> "$CATALINA_OUT" 2>&1 "&"
而$_RUNJAVA就是系统环境下的的java,忽略掉中间的参数不管,我们可以看到启动的Java类是org.apache.catalina.startup.Bootstrap。
_RUNJAVA="$JRE_HOME"/bin/java
而这个org.apache.catalina.startup.Bootstrap类里面有一个main方法,这和我们简单直接写的一个HelloWorld类编译执行的原理是一样的。
这里,我们可以有一个结论,在读任何系统的源码时,都将有一个入口,把握住了入口就把握住了一切。而任何用java运行起来的应用最终也都会有一个public class和一个main,这是绝对的,虽然servlet开发不需关注,只要针对API编程即可,但即使这样最终还是通过servlet的container来满足这一点。
也就是这样,Tomcat启动了。
2. Bootstrap类
上文书说到org.apache.catalina.startup.Bootstrap 的 main方法得到执行。找到对应的tomcat源代码,我们看到其中可以分为2大块:静态对象daemon的创建和初始化、根据命令行参数对daemon调用对应的方法。先看第一块,从daemon的声明我们可以看出其就是一个Bootstrap的对象,在其为null的时候需要新创建一个,并且执行init()方法,初始化后将其赋值给daemon。我们这里更进一步,看看Bootstrap的这个init()方法到底做了哪些事情。
public void init() throws Exception { // Set Catalina path setCatalinaHome(); setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class<?> startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader 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; }
而这个方法,又可以分为两大部分:
前5条语句,用来初始化catalina类加载器相关环境的变量,然后初始化各个类加载器对象,包括commonLoader、sharedLoader,其中最重要的就是catalinaLoader,将其设置为Bootstrap的一个属性值。
从第六条语句开始,使用前面已经构件好的catalinaLoader加载tomcat最核心的对象,那就是org.apache.catalina.startup.Catalina类的对象catalinaDaemon,并以反射的方式调用其setParentClassLoader方法,把sharedLoader作为参数传入。
我们看到这个过程中,Bootstrap类持有的几个对象(静态的daemon、非静态的catalinaDaemon、commonLoader、sharedLoader、catalinaLoader)都得到了创建和必要的初始化。
那么接下来我们看main方法的后半部分。比如刚刚启动,我们得到的命令行参数是start,那么会执行如下这段代码:
else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); }
会分别调用Bootstrap类的daemon对象的setAwait()、load()和start()三个方法。这三个方法我们略微深入一些,发现是从Bootstrap的方法到Catalina方法的一个调用,而且中间都是用了反射方式。至于这3个方法最终具体做了什么,我们这里先不细说,后面会分块做详细整理,不过可以简单了解下,其中:
setAwait()是设置了Catalina对象的一个属性值,其作用是告诉服务器启动后保持运行状态,并开启特定端口监听后续发来的指令,直到收到SHUTDOWN指令,做关闭服务器处理。
load()则是加载和初始化。对整个Tomcat服务器相关的配置文件进行加载和解析处理,并对Tomcat的各个组件进行初始化配置操作。
start(),这个其实不用多说,就是正式启动Catalina,或者说启动了Tomcat服务器的核心工作。
以此为例,对于Bootstrap的其它命令,比如stop等,本文就不想详细叙述了,感兴趣的可以按照这个路子去查看源代码。
3. Tomcat中的类加载器
了解Java和JVM的各位朋友一定知道,现代的JVM中通常有三层默认的类加载器,分别是bootstrap类加载器、扩展类加载器和系统类加载器。这三者每两者间都是父子关系,即前者是后者的父亲或者双亲类加载器,并由此构建了一个“双亲委派关系”,或叫“代理”关系。关于JVM和Java类加载器,我本人争取在后面有机会单独给大家介绍,这里先简单提一下,感兴趣的可以先参考这篇文章:http://www.ibm.com/developerworks/cn/java/j-lo-classloader除了Java环境自身的三层类加载器,前面提到的,在Tomcat中主要有commonLoader、catalinaLoader、sharedLoader这几个类加载器。细看源码,实际上这几个loader都是Tomcat中的org.apache.catalina.loader.StandardClassLoader类。StandardClassLoader直接继承于java.net中的URLClassLoader类,最终继承于java.lang.ClassLoader类。
而除了这三个loader外,Tomcat中还有个关键的ClassLoader,我也在这里一起介绍,那就是org.apache.catalina.loader.WebappClassLoader类。在实际的Tomcat实例中,会由多个WebappClassLoader类对象,就像其名字说描述的,一个Web app,就有一个这样的loader。
此节上面的第一段中提到,JVM中已经给我们提供了3层类加载器,并通过java.lang.ClassLoader 的loadClass()方法实现逻辑,我们可以知道这3层类加载器中使用了“双亲委派”的方式来加载和定义类,从IBM那篇文章中我们也可以看到这一点。这样做的方式,一来大大提高了系统的灵活性和可扩展性,拓宽了Java类定义的空间,二来按照“双亲委派”模式,java.lang包中的系统类只能由系统自身加载,提高了系统的安全性。
对于一般的Java应用开发来讲,我们其实并不需要太过关注JVM所提供的运行环境,不必关注ClassLoader。但在Tomcat中,为了提高系统的灵活性,引入了commonLoader、sharedLoader、catalinaLoader;为了支持和分隔多个web应用,使用了WebappClassLoader。
Tomcat中的系统类加载器。Tomcat也是一个Java应用,他也是在最初系统提供的几层类加载器环境下运行起来的。那么Tomcat的一些最基本的类,也和其它简单Java应用一样,是通过系统的类加载器来加载的,比如默认配置下的tomcat/bin目录下的bootstrap.jar、tomcat-juli.jar、commons-daemon.jar这几个jar包中的类。
Tomcat的Common Loader。Common Loader是Tomcat在系统类加载器之上建立起来的,其父loader是系统类加载器。Common Loader负责上面几个jar包外的Tomcat的大部分java类,通常情况下是tomcat/lib下的所有jar包。
Webapp Class Loader。这个类加载器可以说是Tomcat种最重要的Class Loader,它创造了各个Web app空间。在实现上,它打破了系统默认规则,或者说是打破了java.lang.ClassLoader逻辑中的“双亲委派”模式,提供了一套自定义的类加载流程。默认情况下,对于一个未加载过的类,WebappClassLoader会先让系统加载java.lang.Object等Java本身的基础类,如果不是基础类则优先在当前Web
app范围内查找并加载,如果没加载到,再交给common loader走标准的双亲委派模式加载。
对于上面这三点做一些说明:
在tomcat/conf目录的catalina.properties中有common.loader、server.loader、shared.loader的配置,这分别对应着commonLoader、catalinaLoader和sharedLoader。我们可以看到,默认情况下,serverl.loader和shared.loader的配置是空的,这意味着此两者在运行时和commonLoader相同。实际上,在Tomcat5.5之后的版本中,做了简化,只有commonLoader具备实际意义。而在5.5及之前的版本中,三者各不相同,各有分工。可参看官方文档作对比: Tomcat-5.5-ClassLoader 、 Tomcat-7.0-ClassLoader 。
WebappClassLoader的代理关系或者说类查找加载顺序,其实是可以通过delegate来进行配置的。如果配置不同,代码在查找类时会走不同的路劲。
以上,Tomcat7.0的类加载器结构图总体来看大致如下:
Bootstrap | System | Common / \ Webapp1 Webapp2 ...
有关Tomcat的启动过程和类加载器,本文先总结至此,后面的文章将继续对Tomcat的主体部分基于源代码作分析。
相关文章推荐
- java-模拟tomcat服务器
- i-jetty环境搭配与编译
- 实现单Tomcat多Server配置
- 生产环境下的Tomcat配置
- Linux部署Tomcat服务器
- jenkins------结合maven将svn项目自动部署到tomcat下
- 如何搞定tomcat这只喵~
- tomcat在opensuse下开机自启失败的原因分析及解决方法
- jsp项目中更改tomcat的默认index.jsp访问路径的方法
- Tomcat 多端口 多应用
- tomcat 5.0 + apache 2.0 完全安装步骤详解
- Tomcat安全设置 win2003 下tomcat权限限制
- Jsp和PHP共用80端口整合Apache和Tomcat(访问时无需加端口号)
- Tomcat服务器 安全设置第1/3页
- tomcat 6.0.20在一个机器上安装多个服务的方法
- Tomcat 5.5 数据库连接池配置
- Tomcat内存溢出分析及解决方法
- apache tomcat 一个网站多域名的实现方法
- Tomcat无法加载css和js等静态资源文件的解决思路