Tomcat7源码分析——启动过程和类加载器
来源:互联网 发布:手机视频播放器 知乎 编辑:程序博客网 时间:2024/06/05 15:55
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的主体部分基于源代码作分析。
- Tomcat7源码分析——启动过程和类加载器
- Tomcat7.0源码分析——类加载体系
- Tomcat7.0源码分析——类加载体系
- Tomcat7.0源码分析——类加载体系
- Tomcat7源码解读 —— 类加载器
- spring启动component-scan类扫描加载过程---源码分析
- spring启动component-scan类扫描加载过程---源码分析
- spring启动component-scan类扫描加载过程---源码分析
- spring启动component-scan类扫描加载过程---源码分析
- spring启动component-scan类扫描加载过程---源码分析
- Launcher3源码分析 — 数据加载过程
- Launcher3源码分析 — 数据加载过程
- Launcher3源码分析 — 启动过程
- Launcher3源码分析 — 启动过程
- Tomcat7.0源码分析——server.xml文件的加载与解析
- Tomcat7.0源码分析——启动与停止服务原理
- Android 4.0 Launcher2源码分析——启动过程分析
- Android 4.0 Launcher2源码分析——启动过程分析
- MySQL 5.6 for Windows 解压缩版配置安装
- 2017 Indeed-tokyo笔试题-6*6的矩阵放置元素
- java多线程基础总结
- 前端性能优化:jquery性能优化
- 2-7parent > child选择器
- Tomcat7源码分析——启动过程和类加载器
- 插入排序
- 左右最值最大差
- Android中定时任务的实现
- Android的getLeft,getX,getTranslation,getRawX的理解
- 2-8prev + next选择器
- python 网络一分钟 get IP
- 关于我
- Tomcat内核之Tomcat的类加载器