目录
  1. 前言
  2. 相关概念
  3. 启动流程
    1. bootstrap.init()
    2. bootstrap.load()
      1. 1 使用Digester解析server.xml
      2. 2 调用各级组件和容器的init()
    3. bootstrap.start()
      1. 1 调用各级组件和容器的start()
      2. 2 部署web应用
        1. 2.1 解析context.xml
        2. 2.2 创建WebAppClassLoader
        3. 2.3 解析web.xml
        4. 2.4 web应用初始化
      3. 3 启动Connector,接收网络请求
  4. 参考资料

前言

  Apache Tomcat是一个开源的web服务器,它实现了J2EE中的Java Servlet,JSP等规范。本文基于Tomcat 7.0,对Tomcat的启动时序进行相关介绍,适合于已经了解Tomcat使用方法的读者。由于涉及代码较多,本文不贴详细代码,建议配合源代码阅读,源码链接 http://tomcat.apache.org/download-70.cgi

相关概念

Tomcat的层次结构如下图,图片来自网络:

Tomcat的层次结构

  开门见山,先贴一张自绘的时序图作直观的认识:
image

  Tomcat作为一个Java应用,也一定是由一个main方法启动的。以Tomcat7为例,bin目录下的启动脚本catalina.sh文件中指出了main方法所在的类org.apache.catalina.startup.Bootstrap。探寻Tomcat的启动流程也是从这个方法开始的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String args[]) {
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
}
// ......
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
}

  在输入参数带有”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.jartomcat-juli.jarcommons-daemon.jar
  bootstrap.init()中,创建了Common类加载器。其类加载目录由catalina.properties配置,默认为 ${catalina.base}/lib${catalina.home}/lib。创建后Common被设置为线程上下文类加载器,并负责加载Tomcat后续启动流程使用的相关类。
  Webapp类加载器在每个web应用部署时创建,负责加载每个web应用内使用的类,后续会有介绍。


image

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
2
3
4
5
6
7
8
9
10
11
12
//Class HostConfig
protected void deployApps() {
File appBase = appBase();
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}

  如上,Tomcat提供三种应用(对应Context)部署的配置方式,分别对应deployDescriptors、deployWARs和deployDirectories,三个方法会在启动时依次执行。每个应用(Context)通过其路径来标识,对于已经部署的Context不会重复部署。三种部署方式只是配置方式的区别,具体部署逻辑相似,如下:

2.1 解析context.xml

  每个web应用都对应一个Context,其配置文件context.xml记录了应用目录、url path(//localhost/path/…)等信息,同样是由Digester类解析,从三种路径下读取:

2.2 创建WebAppClassLoader

  如上文提到的Tomcat类加载器结构,每个web应用都有自己的类加载器WebAppClassLoader,Common ClassLoader是他们共同的父类加载器。
  WebAppClassLoader创建后会被切换为线程上下文类加载器(部署web应用和处理该web应用请求时都会切换)。负责加载web.xml配置的所有相关类,例如用户的filter,listener和servlet都由本应用的WebAppClassLoader加载。
  WebAppClassLoader的类加载规则默认不直接委托(delegate=false),而是在保障Java核心类(boostrap)的基础上优先加载项目路径提供的类,其加载类的顺序(如下列表,自上而下查找,找到类即返回):

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的过程就是读取各种部署描述符文件,合并并应用到容器中的过程,大体过程如下:

2.4 web应用初始化

  在实际项目中,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