前言
Apache Tomcat是一个开源的web服务器,它实现了J2EE中的Java Servlet,JSP等规范。本文基于Tomcat 7.0,对Tomcat的启动时序进行相关介绍,适合于已经了解Tomcat使用方法的读者。由于涉及代码较多,本文不贴详细代码,建议配合源代码阅读,源码链接 http://tomcat.apache.org/download-70.cgi
相关概念
Tomcat的层次结构如下图,图片来自网络:
- Server 代表整个Servlet容器,也是Tomcat层次结构中最外层唯一的一个组件。包含一或多个Service。
- Service 代表一个web服务,包含一或多个Connector和一个Engine。
- Connector 代表一个网络连接器,一个Connector设置了本机的某一个端口(如80端口)使用某一种协议接收网络请求。
- Engine 代表Service的请求处理器,负责处理和分发Connector接收的所有请求,是最上层的Container。包含一或多个Host。
- Host 代表一个虚拟主机(也可以看做一个域名,如//localhost/),例如Engine会把请求传递给HTTP请求头的host对应的Host容器。包含一或多个Context。
- Context 代表一个web应用,Host会根据请求URI的路径把请求传递给相应的Context容器,Context会继续把请求传递给Wrapper(Servlet)。
启动流程
开门见山,先贴一张自绘的时序图作直观的认识:
Tomcat作为一个Java应用,也一定是由一个main方法启动的。以Tomcat7为例,bin目录下的启动脚本catalina.sh文件中指出了main方法所在的类org.apache.catalina.startup.Bootstrap。探寻Tomcat的启动流程也是从这个方法开始的。
1 | public static void main(String args[]) { |
在输入参数带有”start”时,tomcat会执行启动,主要调用三个方法,分别是bootstrap.init(),bootstrap.load()和bootstrap.start()。(deamon即bootstrap对象,在init时创建)
bootstrap.init()
主要做了两件事1.先从JVM启动参数中读取出tomcat目录路径 2.然后初始化tomcat的classLoader。
Tomcat7的classLoader整体结构如下,其中Bootstrap、Extension和System是JVM默认的三种类加载器,遵循双亲委派原则,即优先通过父加载器加载。
具体来说,Bootstrap负责加载运行时核心Java类/jre/lib/rt.jar,Extension加载/jre/lib/ext/下的jar包;由于JVM具体实现的不同,Bootstrap和Extension有时可以认为是同一个类加载器。System负责加载系统变量CLASSPATH下的类,而Tomcat启动脚本catalina.sh则指定了CLASSPATH只有三个jar包:*$CATALINA_HOME/bin/bootstrap.jar、tomcat-juli.jar和commons-daemon.jar*。
bootstrap.init()中,创建了Common类加载器。其类加载目录由catalina.properties配置,默认为 ${catalina.base}/lib和 ${catalina.home}/lib。创建后Common被设置为线程上下文类加载器,并负责加载Tomcat后续启动流程使用的相关类。
Webapp类加载器在每个web应用部署时创建,负责加载每个web应用内使用的类,后续会有介绍。
bootstrap.load()
1 使用Digester解析server.xml
server.xml位于Tomcat目录的conf文件夹,它允许了用户配置tomcat的各种组件和层级关系,解析时会根据配置文件,创建Server、Service、Engine、Host及Connector对象。
Digester是一个xml解析工具类,用于解析xml文件并根据解析结果执行相关操作。解析前需要用户配置解析规则(Rule),规则定义了每种xml标签(pattern)解析时的begin、body和end方法;begin方法在遇到它的开始标签时调用,end方法在遇到结束标签时调用。例如ObjectCreateRule在begin方法里根据配置的className创建了对象实例。
load()方法首先会读取server.xml文件,创建Digester并执行解析。
同时,server.xml文件解析出来的组件和容器继承了LifecycleMBeanBase类,其父类LifecycleBase实现了容器的生命周期管理,其接口MBeanRegistration由Java JMX(Java Management Extensions)框架提供,用于监听JMX注册事件;LifecycleMBeanBase的子类都遵循JMX标准,通过getObjectNameKeyProperties()抽象方法定义类的注册名称,并在每个对象初始化(执行生命周期init方法)时注册到MBeanServer中,MBeanServer给出了外部管理系统访问JMX框架的接口,因此外部系统或工具可以实时监控到MBean的各种数据信息。
2 调用各级组件和容器的init()
通过调用catalina.init(),依次执行了server.init()、service.init()、engine.init()和container.init()。每个init方法都实现了向MBeanServer注册MBean。
Engine是唯一一个在此阶段执行init()的Container,每个Container的初始化方法都会创建一个自己的startStopExecutor,该线程池用于start/stop时并发执行所有子container的启动或终止。
Connector也在此时执行了init方法,Connector负责网络连接的配置和管理,具体处理逻辑交给protocolHandler(默认实例为Http11Protocol)和EndPoint。Connector的初始化只是创建了ServerSocket对象,绑定了服务器端口。
bootstrap.start()
1 调用各级组件和容器的start()
bootstrap.start()会依次调用catalina.start()、server.start()、service.start()、engine.start()和host.start()。Container(包括engine,host,context,wrapper)在启动时除了会使用startStopExecutor并发启动子container以外,还会触发其内部的次级组件和pipeline的启动(如果存在),次级组件可以由开发者选择性的配置,包括loader,manager,realm,cluster,resources,分别对应Container级别的类加载器,session管理,权限检验,集群管理和JDNI服务。
2 部署web应用
每个组件和容器都间接继承自LifecycleBase,负责生命周期管理。当对象的状态发生变化时(见LifecycleState枚举类,例如INITIALIZING,STARTING)都会触发该对象配置过的生命周期监听器,处理状态变化的事件。
HostConfig就是StandardHost(Host的实现类)的生命周期监听器,当接收到启动事件时,就会执行web应用的部署。
1 | //Class HostConfig |
如上,Tomcat提供三种应用(对应Context)部署的配置方式,分别对应deployDescriptors、deployWARs和deployDirectories,三个方法会在启动时依次执行。每个应用(Context)通过其路径来标识,对于已经部署的Context不会重复部署。三种部署方式只是配置方式的区别,具体部署逻辑相似,如下:
2.1 解析context.xml
每个web应用都对应一个Context,其配置文件context.xml记录了应用目录、url path(//localhost/path/…)等信息,同样是由Digester类解析,从三种路径下读取:
- $CATALINA_HOME/conf/context.xml:这里的context的配置会被所有web应用共享,在 ContextConfig.init() 时被解析。
- $CATALINA_HOME/conf/[enginename]/[hostname]/[pathname].xml:deployDescriptor的部署方式就是通过这里的xml文件配置的,xml的文件名会被作为Context的path,配置文件中需要指定web应用的路径。仅在第一种部署方式 deployDescriptor() 开始时解析。
- $WEBAPP_HOME/META-INF/context.xml:在每个web应用目录或者war包内的context配置,在后两种部署方式deployWAR() 和deployDirectory() 开始时解析,解析时会拷贝一份xml至 $CATALINA_HOME/conf/[enginename]/[hostname] 目录。
2.2 创建WebAppClassLoader
如上文提到的Tomcat类加载器结构,每个web应用都有自己的类加载器WebAppClassLoader,Common ClassLoader是他们共同的父类加载器。
WebAppClassLoader创建后会被切换为线程上下文类加载器(部署web应用和处理该web应用请求时都会切换)。负责加载web.xml配置的所有相关类,例如用户的filter,listener和servlet都由本应用的WebAppClassLoader加载。
WebAppClassLoader的类加载规则默认不直接委托(delegate=false),而是在保障Java核心类(boostrap)的基础上优先加载项目路径提供的类,其加载类的顺序(如下列表,自上而下查找,找到类即返回):
- 缓存
- BootstrapLoader
- /WEB-INF/classes
- /WEB-INF/lib/*.jar
- 委托至父类加载器(Bootstrap–>Extension–>System–>Common)
2.3 解析web.xml
/WEB-INF/web.xml是Servlet规范的部署描述符,它描述了如何在一个Servlet容器中部署一个web应用,包括:session配置、servlet配置和映射、filter配置和映射、监听应用生命周期的listener、首页和错误页面配置、安全配置等。
同时,Servlet 3.0提供了web模块化规范,部署描述符也可以存在于*/WEB-INF/lib/*.jar/web-fragment.xml中,并在web应用部署时解析。
另外,Tomcat提供了全局web.xml:*$CATALINA_BASE/conf/web.xml*。与web-fragment一样的是,其配置将与web.xml合并,顺序可以通过ordering标签配置,具体合并规则见Servlet 3.0标准8.2.3节。
解析web.xml的过程就是读取各种部署描述符文件,合并并应用到容器中的过程,大体过程如下:
- 使用WebXmlDigester,将web.xml的数据解析到WebXml对象中
- 扫描*/WEB-INF/lib下每个Jar包内的/META-INF/web-fragment.xml*并解析,根据一定的规则排序,并放入Set
中 - 扫描*/WEB-INF/lib下每个Jar包内的/META-INF/services/*目录下的ServletContainerInitializer实现类,放入StandardContext.initializers中(后续将执行其onStartup方法)
- 扫描*/WEB-INF/classes*下的servlet注解并添加相应配置
- 将第一步解析的应用web.xml和第二步解析的web-fragment.xml以及全局的web.xml文件(conf/web.xml 、web.xml.default)合并到WebXml对象中
- 将合并后的WebXml配置到Context中,例如为每个Servlet创建Wrapper并作为Context的子容器
2.4 web应用初始化
- 根据上一步对 **.jar/META-INF/services/ServletContainerInitializer*的扫描结果,调用他们的ServletContainerInitializer.onStartup()方法
- 实例化listener,并调用listener.contextInitialized()
- 实例化filter,并调用filter.init()
- 对于配置load-on-startup的servlet,实例化servlet并执行servlet.init()
在实际项目中,listener和filter的初始化是web应用部署的重头戏,各种框架如Spring, Struts, Spring MVC等都会在此做大量初始化工作。
3 启动Connector,接收网络请求
Connector负责网络连接的处理,Tomcat7提供了三种Connector:Java Blocking Connector,Java Nio Blocking Connector,APR/native Connector。
Java Blocking Connector是Tomcat7默认的网络连接处理方式,其处理模型为一个或多个线程(Acceptor)接收TCP连接,每个连接的请求都会交由单独的线程(Worker)处理。
Java Nio Blocking Connector是Tomcat8默认的处理方式。为了避免连接过多(尤其是keepalive的连接会阻塞worker线程)造成的线程资源浪费,它在接收网络连接的线程(Acceptor)和请求处理线程(Worker)之间增加了Poller事件队列,每当有IO就绪事件都会放入Poller队列,Poller线程会不断消费队列里的事件,交由Worker线程处理。
APR/native Connector,APR即Apache Portable Runtime Libraries,使用了native代码来优化网络请求的处理。
参考资料
JSR-000315 Java Servlet 3.0 Final Release
Apache Tomcat 7 Configuration Reference (7.0.72)
Tomcat7源码解析 - 博客频道 - CSDN.NET