一切的 Java 程序入口都是 main 方法, Tomcat 的启动类是哪个呢?
直接启动 Tomcat, 使用 jps -l
命令 (Linux 也可以使用 ps -ef | grep java
), 查看本机上所有正在运行的 Java 程序
可以看到, Tomcat 的主启动类是 org.apache.catalina.startup.Bootstrap
初始化各个组件
先来到 Bootstrap 类, 查看 Bootstrap 的 main 方法
synchronized (daemonLock) {
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap();
try {
// 初始化类加载器, 并创建 Catalina 对象
bootstrap.init();
}
....
daemon = bootstrap;
}
else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
....
// 默认进入此行代码
else if (command.equals("start")) {
daemon.setAwait(true);
// 利用反射调用 Catalina 的 load 方法, 加载各个组件
daemon.load(args);
// 启动各个组件
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}......
}
Catalina
查看 load 方法
// 创建一个解析 xml 文件的对象
Digester digester = createStartDigester();
try {
try {
// 拿到 server.xml 配置文件
file = configFile();
.....
}
try {
...
// 完成对 server.xml 配置文件的解析
// 并创建了默认的组件对象 : StandardServer, StandardService
// StandardEngine, StandardHost, Connector, Executor, Mapper
// 并为 Server 添加了六个监听器, 为 Engine 添加监听器 : EngineConfig
// 为 Host 添加监听器 : HostConfig
digester.parse(inputSource);
}
try {
// 初始化 Server 组件
getServer().init();
}
}
接下来初始化各个组件, 其中 StandardServer, StandardService, StandardEngine, Connector, 都继承自 LifecycleBase
抽象类, 这里使用了 模板设计模式
, 将公共代码抽取出来放到他们的公共父类中
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 标记组件生命周期的状态
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 初始化各个组件 Server -> Service -> Engine -> MapperListener -> Connector
// 在启动 Container 组件的时候又会初始化 Host, Context, Wrapper, Pipeline, Valve 组件
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
....
}
监听器
Server 默认存在六个生命周期监听器 (这些监听器对应 server.xml 配置文件中配置的监听器), 监听 Server 在生命周期中发生的事件, 并进行相应处理
-
NameContextListener
用于初始化和填充与每个 Context 和 Server 关联的的 JNDI 服务
-
VersionLoggerListener
用于以日志形式输出服务器 、操作系统、JVM的版本信息
-
AprLifecycleListener
用于加载 (服务器启动) 和 销毁 (服务器停止) APR。 如果找不到 APR 库, 则会输出日志, 并不影响Tomcat启动
-
JreMemoryLeakPreventionListener
用于避免 JRE 内存泄漏问题
-
GlobalResourcesLifecycleListener
用户加载 (服务器启动) 和 销毁 (服务器停止) 全局命名服务
-
ThreadLocalLeakPreventionListener
用于在 Context 停止时重建 Executor 池中的线程, 以避免 ThreadLocal 相关的内存泄漏
Lifecycle
由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性, 所以 Tomcat 在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组件 Server、Service、Container、Executor、Connector 组件 , 都实现了 Lifecycle 接口,从而具有了以下生命周期中的核心方法:
- init() :初始化组件
- start() :启动组件
- stop() :停止组件
- destroy() :销毁组件
在 Lifecycle 的 Javadoc 文档中, 有这样的一段注释
描述了各个组件执行完对应的方法后, 应处于哪种生命周期状态
各组件的默认实现
上面提到的 Server、Service、Engine、Host、Context 都是接口, 的默认实现类都是加上 Standard
前缀的, 比如 StandardServer, StandardService …
ProtocolHandler : Coyote 协议接口,通过封装 Endpoint 和 Processor , 实现针对具体协议的处理功能。Tomcat按照协议和 IO 提供了6个实现类。
AJP协议:
- AjpNioProtocol :采用 NIO 的 IO 模型。
- AjpNio2Protocol:采用 NIO2 的IO模型。
- AjpAprProtocol :采用 APR 的 IO 模型,需要依赖于 APR 库。
HTTP协议:
- Http11NioProtocol :采用 NIO 的 IO 模型,默认使用的协议(如果服务器没有安装APR)。
- Http11Nio2Protocol:采用 NIO2 的IO模型。
- Http11AprProtocol :采用 APR 的IO模型,需要依赖于 APR 库。
当前对于 Endpoint 组件来说,在 Tomcat 中没有对应的 Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint, Nio2Endpoint, AprEndpoint . 这三个实现类,分别对应于前面连接器支持的三种 IO 模型:NIO,NIO2,APR , Tomcat 8.5.57 版本中,默认采用的是 NioEndpoint。
当初始化完 Endpoint 后, 开始启动各个组件
启动各个组件
当执行完 Catalina 的 load 方法后, Bootstrap 开始反射调用 Catalina 的 start 方法, Catalina 直接启动 Server
public final synchronized void start() throws LifecycleException {
....
try {
// 标记组件生命周期的状态
setStateInternal(LifecycleState.STARTING_PREP, null, false)
// 启动组件 Server -> Service -> Engine -> 子容器 (Host -> Pipeline -> HostValve) ->
// 启动所有的 (Context -> Wrapper -> Pipeliine (Wrapper) -> WrapperValve ->
// Pipeline(Context) -> Context(Valve) ) ->
// Pipeline (Engine) -> EngineValve -> MapperListener -> Connector
startInternal();
if (state.equals(LifecycleState.FAILED)) {
stop();
}
....
}
}
可以看到, 同样使用 模板设计模式
, 将 start 方法的逻辑, 抽取放到公共父类 LifecycleBase
中
容器组件
在启动 Engine 组件的时候, 调用了父类 ContainerBase
的 startInternal
方法
protected synchronized void startInternal() throws LifecycleException {
// 启动子组件
logger = null;
getLogger();
// 检查是否存在集群, 如果有就启动他们
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
// 启动该容器对应的域对象
((Lifecycle) realm).start();
}
// 拿到子容器 Engine -> Host
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
// 线程池执行 Callable 任务, 异步调用子容器的 start 方法, 启动子容器
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
// 获取 Callable 任务的返回值, 返回值为 void
for (Future<Void> result : results) {
try {
// 等待异步任务处理完毕才会继续执行
result.get();
}
catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// 启动该容器中 Pipeline 的 Valve (包括父类), 如果存在的话
// 顺序应该为 Engine -> Host -> Wrapper
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 当 Engine 设置为 STARTING 状态后
// 会被 HostConfig 监听到, 调用 HostConfig#start 方法
// HostConfig#start 方法其实就是部署 Web 应用, 并绑定子容器 Context
setState(LifecycleState.STARTING);
// 启动一个线程, 该线程专门用于监听 Session 的过期时间
threadStart();
}
查看 HostConfig 的 start 方法
public void start() {
.....
// 部署 Web 应用程序
if (host.getDeployOnStartup()) {
deployApps();
}
}
---------------------
// 查看 deployApps 方法
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 部署 server.xml 配置的 Web 应用
deployDescriptors(configBase, configBase.list());
// 部署 War 包应用
deployWARs(appBase, filteredAppPaths);
// 部署 webapps 文件夹中的 Web 应用
// 将异步任务 DeployDirectory 交给线程池处理
deployDirectories(appBase, filteredAppPaths);
}
-------------------
// 直接查看 DeployDirectory 的 run 方法
// 发现直接调用了 HostConfig 的 deployDirectory 方法
protected void deployDirectory(ContextName cn, File dir) {
....
Class<?> clazz = Class.forName(host.getConfigClass());
// 创建监听器 ContextConfig, 用于监听 Context 的事件
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
// 将此监听器与此 Context 绑定
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
// 解析 Context 之后, 绑定到此 Host 的子节点中, 然后启动 Context 子组件
host.addChild(context);
...
}
为 Host 绑定子组件后, 开始启动子容器组件 Context, 查看 Context 的标准实现 StandardContext
的 start 方法, 发现 Context 还未初始化, 先执行其 init 方法, 完成对 Context 的初始化, 然后启动 Context, 查看 startInternal 方法
protected synchronized void startInternal() throws LifecycleException {
.....
setConfigured(false);
boolean ok = true;
// 启动命名资源
if (namingResources != null) {
namingResources.start();
}
// 创建 work 目录
postWorkDirectory();
// 根据需要添加缺少的组件
if (getResources() == null) { // (1) Required by Loader
...
try {
// 设置 WebResourceRoot , 用于加载 context.xml 中的资源
// 加载的类型支持三种 : 1. pre : 加载 context.xml 中的 <PreResource>
// 2. main : 加载 Web 应用下 /WEB-INF/lib(class)
// 3. jar : 加载 context.xml 中的 <JarResource>
setResources(new StandardRoot(this));
}
....
}
if (ok) {
// 启动资源处理器 WebResourceRoot
resourcesStart();
}
// 设置 WebApp 类加载器
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader();
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
....
try {
if (ok) {
// 启动 WebApp 类加载器
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
.....
// 启动此 Context 的的域
Realm realm = getRealmInternal();
if (null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
....
}
// 发出事件, 通知监听器监听, Context 会被 ContextConfig 监听到
// 然后解析 web.xml 配置文件和 /WEB-INF/class 的文件, 将里面的
// Servlet, Filter, Listener ... 包装成 Wrapper, 添加到 Context 子组件中
// 然后启动子组件 Wrapper, 然后启动 Wrapper 的 Pipeline, WrapperValve
// 启动 Wrapper 的时候, 也是调用父类的 startInternal 方法
// 这里的 Servlet 并没有加载到 JVM 中, 只是解析并封装了
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// 启动此 Context 的子容器 Wrapper
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// 等待所有子容器启动完毕后, 启动 Context 的 Pipeline 和 ContextValve
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 集群管理器
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
...
}
// 如果没指定, 使用默认的管理器
if (contextManager != null) {
...
}
...
// 将资源放入 ServerContext 中
if (ok) {
.....
}
// Set up the context init params
mergeParameters();
// 调用 ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
}
....
}
// 启动监听器
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
....
try {
// 启动管理器
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
}
...
// 启动过滤器
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// 加载并实例化配置了 <load-up-start> 的 Servlet
if (ok) {
if (!loadOnStartup(findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
....
}
发现, 每个容器组件都继承了 ContainerBase
抽象类, 每个容器组件都有一个属性 Pipeline
, 该 Pipeline
类似于管道, 可以添加 Valve
, 而 Valve
就像阀门, 可以打开管道, 这就是 责任链模式
的经典应用
启动容器组件的顺序,
- 先启动
Engine
, 然后启动该容器对应的域对象 LockOutRealm, UserDatabaseRealm- 启动子容器
Host
, 接着依次启动 : Pipeline -> AccessLogValve -> ErrorReportValve -> HostValve, 然后将 Host 的时间设置为STARTING
, 让 HostConfig 监听, 交个 HostConfig 处理- 然后 HostConfig 部署 Web 应用, 为
Host
添加了多个子容器Context
, 线程池异步启动Host
子容器, 然后启动 : StandardRoot -> DirResourceSet -> WebappLoader, 然后解析 Web 应用中的 web.xml 配置文件和 /WEB-INF/class 文件 的 Servlet, Listener, Filter, 包装成Wrapper
对象, 并添加到Context
的子组件中, 并启动它们- 接着启动
Context
的子容器Wrapper
, 依次启动 Wrapper -> Pipeline -> WrapperValve- 启动完所有的
Wrapper
后, 启动Context
的 Pipeline, 和 ContextValve, 接着启动 StandardManager 和 StandardSessionIDGenerator- 启动完所有的
Context
后, 才来启动 Engine 的 Pipeline, EngineValve- 最后再来启动
MapperListener
自此, 所有的 Container 组件才算启动完成, , 然后依次启动 线程池 (如果已配置的话) -> MapperListener -> Connector -> protocolHandler, 最后启动 Endpoint , 直接查看 startInternal 方法
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// 实例化各个缓存
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// 如果 server.xml 没有配置线程池, 则创建默认线程池
// 线程池的核心线程数为 10, 最大线程数为 200, 最大空闲时间为 60s
if (getExecutor() == null) {
createExecutor();
}
// 初始化连接限制器, 用于限制最大连接数
initializeConnectionLatch();
// 创建 Poller 线程, 专门用于处理 Socket 的数据
pollers = new Poller[getPollerThreadCount()];
for (int i = 0; i < pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 新开一个 Acceptor 线程, 用于监听 Socket 的连接
startAcceptorThreads();
}
}
至此, Tomcat 启动完成, 由 Acceptor 线程监听 Socket 连接请求, Poller 线程处理 Socket 的读写请求, 并进一步处理