Tomcat源码分析(从0到1)

开始前的准备

先到官网上去下载Tomcat源码,注意jdk版本,你用的哪个版本就下载哪个,如果你目前的环境是jdk8的话,直接用我下面的压缩包就可以了

Apache Tomcat® - Welcome!
 

📎apache-tomcat-8.5.73.zip📎tomcat-8.5.rar📎apache-tomcat-8.0.1-src.zip

当然你也可以通过GitHub来拉取源代码

GitHub - apache/tomcat: Apache Tomcat

下载完成后在bin目录下找到startup,如果是windows系统,那直接双击startup.bat运行脚本即可,如果是macos系统,就需要通过终端去启动startup.sh

如果启动过程中显示无权限

就使用chmod u+x *.sh命令去启动

看到这个提示,那应该就是启动成功了

接下来我们访问localhost:8080端口,会出现下面这个页面

也可以通过目录中的tomcat-8.5/java/org/apache/catalina/startup中的Boostrap类中的main方法启动(建议使用这个启动),如果已经按照上面的方式启动了,可以在终端使用lsof - i :8080来获取PID,然后通过kill PID来关闭端口

能完成上面的所有步骤,那么就可以步入正题了

前置概要

tomcat大家应该都熟悉,但是tomcat它究竟解决了什么问题,在tomcat出现以前我们的这些Web服务是怎么去实现的?大家可能首先想到的就是socket,这也是大家刚接触网络编程的时候学习到的,这里我写了一个小案例,帮助大家回顾一下

我们首先创建两个类,一个是客户端,一个服务端,端口设置成8899,然后依次启动,结果就如下

  1. ClientSocket
    • 这个类创建了一个客户端,它尝试连接到本地服务器(localhost)的端口8899
    • 它通过Socket类与服务器建立连接,然后使用BufferedWriter写入一段字符串"呼啦呼啦呼啦"到服务器。
    • 在写入完数据后,程序调用flush()方法确保数据发送到服务器,并且关闭了输出流和Socket。
  1. ServerSocket
    • 这个类实现了一个简单的服务器端。
    • 它创建了一个ServerSocket,监听端口8899
    • 当客户端连接时,服务器会接受连接,并且通过BufferedReader读取客户端发送的消息并打印到控制台。
    • 服务器会一直运行在while(true)循环中,等待新的客户端连接。

Tomcat 与 Socket 的关系

在Tomcat出现之前,Web服务的基础就是像这样的基于Socket的低级通信编程。Socket为两个设备之间的通信提供了通道,但它并不能自动处理HTTP协议、会话管理、并发连接等问题。Tomcat是一个基于Servlet规范的Web服务器,它简化了处理HTTP请求的过程,自动处理连接、请求解析、会话管理等问题,从而让开发者可以专注于业务逻辑而不是网络通信的细节。

Tomcat源码目录

Tomcat 源码位于 java 文件夹下面。这个java文件夹中的每个包的作用,我们简单的来介绍下,后面在分析核心源码的时候会重点讲解。

我们可以看到在java目录下,分为了两个结构,一个是javax另一个是org.apache这两个包

javax

在javax中保存的是新的JavaEE规范。可以具体来看看每个目录的作用。

模块

作用说明

annotation

annotation 这个模块的作用是定义了一些公用的注解,避免在不同的规范中定义相同的注解。

ejb

ejb是个古老的传说,我们不管

el

在jsp中可以使用EL表达式,这么模块解析EL表达式的

mail

和邮件相关的规范

persistence

持久化相关的

security

和安全相关的内容

servlet

这个指定的是Servlet的开发规范,Tomcat本质上就是一个实现了Servlet规范的一个容器,Servlet定义了服务端处理Http请求和响应的方式(规范)

websocket

定义了使用 websocket 协议的服务端和客户端 API

xml.ws

定义了基于 SOAP 协议的 xml 方式的 web 服务

org.apache

org.apache这个包是Tomcat的源码包,也是针对上面的JavaEE规范的部分实现,Tomcat的本质就是对JavaEE的某些规范的实现合集,首先肯定实现了Servlet规范

模块

作用

catalina

catalina是Tomcat的核心模块,里面完整的实现了Servlet规范,Tomcat启动的主方法也在里面,后面我们分析的重点。

coyote

tomcat 的核心代码,负责将网络请求转化后和 Catalina 进行通信。

el

这个是上面javax中的el规范的实现

jasper

主要负责把jsp代码转换为java代码。

juli

日志相关的工具

naming

命名空间相关的内容

tomcat

各种辅助工具,包括 websocket 的实现。

现在我们开始进入到源码,在Servlet包下有一个Servlet接口,我们点开

Servlet接口主要方法

  1. init(ServletConfig config):
    • 当Servlet被加载到服务器中时,容器会调用这个方法进行初始化。
    • 它接收一个ServletConfig对象,包含Servlet的配置信息。
    • 如果初始化失败,Servlet抛出ServletException,则无法加载该Servlet。
  1. getServletConfig():
    • 返回ServletConfig对象,通常在init()中保存,以便在后续的操作中获取初始化参数。
  1. service(ServletRequest req, ServletResponse res):
    • 这是处理客户端请求的核心方法。每次有请求到达时,容器都会调用这个方法。
    • 它接收两个参数:ServletRequest(客户端的请求)和ServletResponse(服务器的响应)。
    • 方法可能会抛出ServletExceptionIOException,如果请求处理过程中发生错误。
  1. getServletInfo():
    • 返回Servlet的相关信息(如版本、作者等),以便于识别。
  1. destroy():
    • 当Servlet被销毁时,容器会调用这个方法,通常用于释放资源。

Servlet生命周期

  • 构造: 容器实例化Servlet。
  • 初始化: 调用init()
  • 服务请求: 每次客户端请求都会调用service()
  • 销毁: 当Servlet不再需要时,调用destroy(),释放资源。

Tomcat源码解析

接下来我打开conf包下的server.xml配置文件

这个XML文件是Tomcat服务器的配置文件,展示了Tomcat的整体结构。以下是它的结构和每个主要部分的解释:

  • <Server>:
    • 表示整个Tomcat服务器的配置。
    • 属性port="8005"定义了Tomcat监听关闭请求的端口。
    • shutdown="SHUTDOWN"表示关闭命令的指令。
  1. <Listener>:
    • 这些是Tomcat的监听器,用来监视和管理Tomcat的生命周期和性能。
    • 每个ListenerclassName属性指定了它的具体功能:
      • VersionLoggerListener: 记录Tomcat启动时的版本信息。
      • AprLifecycleListener: 支持APR(Apache Portable Runtime)及SSL配置。
      • JreMemoryLeakPreventionListener: 防止JRE内存泄漏。
      • GlobalResourcesLifecycleListener: 监听全局资源的生命周期。
      • ThreadLocalLeakPreventionListener: 防止ThreadLocal相关的内存泄漏。
  1. <GlobalNamingResources>:
    • 全局资源的定义,如数据库连接池等资源。
    • 这里定义了一个UserDatabase,用于管理用户信息,指向conf/tomcat-users.xml文件。
  1. <Service>:
    • 定义了Tomcat的服务,表示服务运行时的一些配置。
    • 属性name="Catalina"是Tomcat默认的服务名称。
  1. <Connector>:
    • 定义Tomcat的连接器,处理HTTP请求。
    • 第一个Connector:
      • port="8080"表示监听HTTP请求的端口。
      • protocol="HTTP/1.1"定义了使用的协议。
      • connectionTimeout="20000"设置了连接超时时间为20秒。
      • redirectPort="8443"指明在需要SSL时将请求重定向到8443端口。
    • 第二个Connector(被注释掉):
      • 这是一个SSL连接器,使用HTTPS协议和自定义证书(存储在localhost-rsa.jks文件中)。这段配置被注释掉了,可以根据需求启用。
  1. <Engine>:
    • Tomcat的请求处理引擎。每个Service中只有一个Engine
    • **name="Catalina"**是引擎的名字,defaultHost="localhost"是默认的主机。
    • <Realm>:
      • 安全领域(Realm)的配置,用于验证和授权用户。
      • LockOutRealm: 防止用户连续失败的登录尝试。
      • UserDatabaseRealm: 用于从UserDatabase资源中读取用户信息。
  1. <Host>:
    • 表示一个虚拟主机,处理特定的Web应用程序。
    • name="localhost": 定义主机名为localhost。
    • appBase="webapps": Web应用程序的基础目录。
    • unpackWARs="true": 自动解压缩WAR文件。
    • autoDeploy="true": 自动部署新的Web应用程序。
  1. <Valve>:
    • 定义了Tomcat的日志记录组件。
  1. <Context>:
    • 表示Web应用的上下文。每个<Context>标签可以配置不同的Web应用。

以下是整体Tomcat的运行架构图

接下来重点了解一下连接器

  • 连接器功能· 监听网络端口。
  • 接受网络连接请求。
  • 根据具体应用层协议(http/ajp)解析字节流,生成统一的Tomcat Request对象。
  • 将Tomcat Request对象转成标准的ServletRequest。
  • 调用Servlet容器,得到ServletResponse。
  • 将ServletResponse转成Tomcat Response对象。
  • 将Tomcat Response转成网络字节流。
  • 将响应字节流写回给浏览器。

接下来我们在org.apache目录中的catalina包中的根目录下找到Lifecycle接口

如果大家能把这个接口中最顶部这个注释的图看懂,那生命周期的代码就差不多搞定大部分了

tomcat生命周期

在Tomcat中,组件的生命周期通过Lifecycle接口进行管理,组件实现该接口后,便可以通过一系列标准的状态转换和事件来统一管理其生命周期。Lifecycle接口定义了组件的启动、停止、初始化、销毁等生命周期操作,并伴随着相应的生命周期事件。下面详细描述组件生命周期的各个阶段:

1. NEW(新建)

  • 描述:组件刚被创建,还未执行任何初始化工作。
  • 事件:无
  • 状态转换:当调用 start() 方法时,组件会进入 INITIALIZING 状态。

2. INITIALIZING(初始化中)

  • 描述:组件开始执行初始化操作,通常包括加载必要的资源和设置初始状态。
  • 事件
    • BEFORE_INIT_EVENT:在组件开始初始化之前触发。
    • AFTER_INIT_EVENT:在组件完成初始化后触发。
  • 状态转换:初始化完成后,组件进入 INITIALIZED 状态。

3. INITIALIZED(已初始化)

  • 描述:组件已经成功完成初始化,处于等待启动的状态。
  • 事件:无
  • 状态转换:当调用 start() 方法时,组件会进入 STARTING_PREP 状态。

4. STARTING_PREP(准备启动)

  • 描述:组件正在为启动做好准备工作,这一步通常是配置一些启动前的参数。
  • 事件BEFORE_START_EVENT 在组件开始启动之前触发。
  • 状态转换:准备工作完成后,组件进入 STARTING 状态。

5. STARTING(启动中)

  • 描述:组件开始启动,执行实际的启动逻辑。
  • 事件
    • START_EVENT:当组件处于启动过程中触发。
    • AFTER_START_EVENT:在组件完成启动后触发。
  • 状态转换:启动成功后,组件进入 STARTED 状态。

6. STARTED(已启动)

  • 描述:组件已经成功启动,可以正常工作并对外提供服务。
  • 事件:无
  • 状态转换:当调用 stop() 方法时,组件进入 STOPPING_PREP 状态。

7. STOPPING_PREP(准备停止)

  • 描述:组件准备停止,开始执行一些关闭前的操作。
  • 事件BEFORE_STOP_EVENT 在组件开始停止之前触发。
  • 状态转换:准备工作完成后,组件进入 STOPPING 状态。

8. STOPPING(停止中)

  • 描述:组件开始执行停止的操作,关闭相关资源和线程。
  • 事件
    • STOP_EVENT:在组件停止过程中触发。
    • AFTER_STOP_EVENT:在组件成功停止后触发。
  • 状态转换:停止完成后,组件进入 STOPPED 状态。

9. STOPPED(已停止)

  • 描述:组件已经成功停止,不再提供服务。
  • 事件:无
  • 状态转换:当调用 destroy() 方法时,组件进入 DESTROYING 状态。

10. DESTROYING(销毁中)

  • 描述:组件开始执行销毁操作,释放所有占用的资源。
  • 事件
    • BEFORE_DESTROY_EVENT:在组件开始销毁之前触发。
    • AFTER_DESTROY_EVENT:在组件成功销毁后触发。
  • 状态转换:销毁完成后,组件进入 DESTROYED 状态。

11. DESTROYED(已销毁)

  • 描述:组件已经被销毁,所有资源释放,不能再使用。
  • 事件:无

12. FAILED(失败)

  • 描述:组件进入失败状态,通常是由于在生命周期的某个阶段发生了无法恢复的错误。
  • 状态转换:任何状态都可以转换为 FAILED 状态。

特殊状态转换说明

  • 当组件处于 STARTING_PREPSTARTINGSTARTED 状态时,调用 start() 是无效的。
  • 当组件处于 STOPPING_PREPSTOPPINGSTOPPED 状态时,调用 stop() 也是无效的。
  • 无论组件当前处于什么状态,如果检测到无法恢复的错误,组件都会进入 FAILED 状态。

可以看到这个lifecycle接口有102处实现,可想而知其重要性

我们就拿Service接口举例子,可以看到它也继承至Lifecycle

再往下就会发现,StandardService又继承LifecycleMBeanBase接口

我们再进到LifecycleMBeanBase接口,又发现它继承LifecycleBase接口

下面就是LifecycleBase中的完整代码

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.util;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;

/**
 * Base implementation of the {@link Lifecycle} interface that implements the
 * state transition rules for {@link Lifecycle#start()} and
 * {@link Lifecycle#stop()}
 * Lifecycle 接口的基本实现
 * 在Lifecycle中声明了 生命周期的方法 init start stop destory方法
 *                    声明了监听器和相关的事件
 *                    在 LifecycleBase中实现了基本的串联。
 *
 */
public abstract class LifecycleBase implements Lifecycle {

	private static final Log log = LogFactory.getLog(LifecycleBase.class);

	private static final StringManager sm = StringManager.getManager(LifecycleBase.class);


	/**
     * The list of registered LifecycleListeners for event notifications.
     */
	private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();


	/**
     * The current state of the source component.
     * 记录当前对象状态的实例
     */
	private volatile LifecycleState state = LifecycleState.NEW;


	private boolean throwOnFailure = true;


	/**
     * Will a {@link LifecycleException} thrown by a sub-class during
     * {@link #initInternal()}, {@link #startInternal()},
     * {@link #stopInternal()} or {@link #destroyInternal()} be re-thrown for
     * the caller to handle or will it be logged instead?
     *
     * @return {@code true} if the exception will be re-thrown, otherwise
     *         {@code false}
     */
	public boolean getThrowOnFailure() {
		return throwOnFailure;
	}


	/**
     * Configure if a {@link LifecycleException} thrown by a sub-class during
     * {@link #initInternal()}, {@link #startInternal()},
     * {@link #stopInternal()} or {@link #destroyInternal()} will be re-thrown
     * for the caller to handle or if it will be logged instead.
     *
     * @param throwOnFailure {@code true} if the exception should be re-thrown,
     *                       otherwise {@code false}
     */
	public void setThrowOnFailure(boolean throwOnFailure) {
		this.throwOnFailure = throwOnFailure;
	}


	/**
     * {@inheritDoc}
     */
	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycleListeners.add(listener);
	}


	/**
     * {@inheritDoc}
     */
	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return lifecycleListeners.toArray(new LifecycleListener[0]);
	}


	/**
     * {@inheritDoc}
     */
	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycleListeners.remove(listener);
	}


	/**
     * Allow sub classes to fire {@link Lifecycle} events.
     *     监听器触发相关的事件
     * @param type  Event type  事件类型
     * @param data  Data associated with event. 传递给触发的监听器的数据
     */
	protected void fireLifecycleEvent(String type, Object data) {
		LifecycleEvent event = new LifecycleEvent(this, type, data);
		for (LifecycleListener listener : lifecycleListeners) {
			// 遍历获取所有的监听器-->触发
			listener.lifecycleEvent(event);
		}
	}


	/**
     * 实现了 Lifecycle 中定义的init方法
     * 该方法和对应的组件的状态产生的关联
     * 1.具体的组件初始化操作 Server  Service Connector Engine Host Context
     * 2.状态变化
     * 3.事件发布
     * @throws LifecycleException
     */
	@Override
	public final synchronized void init() throws LifecycleException {
		if (!state.equals(LifecycleState.NEW)) {
			// 无效的操作  只有状态为 New 的才能调用init方法进入初始化
			invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
		}

		try {
			// 设置状态为初始化进行中....同步在方法中会触发对应的事件
			setStateInternal(LifecycleState.INITIALIZING, null, false);
			initInternal(); // 交给子类具体的实现 初始化操作
			// 更新状态为初始化完成 同步在方法中会触发对应的事件
			setStateInternal(LifecycleState.INITIALIZED, null, false);
		} catch (Throwable t) {
			handleSubClassException(t, "lifecycleBase.initFail", toString());
		}
	}


	/**
     * Sub-classes implement this method to perform any instance initialisation
     * required.
     *
     * @throws LifecycleException If the initialisation fails
     */
	protected abstract void initInternal() throws LifecycleException;


	/**
     * 和 init 方法相识的逻辑,其实就是简化了子类的处理
     *    在本方法中完成了 生命周期方法和相关事件及监听器的处理
     * {@inheritDoc}
     */
	@Override
	public final synchronized void start() throws LifecycleException {

		if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
				LifecycleState.STARTED.equals(state)) {

			if (log.isDebugEnabled()) {
				Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
			} else if (log.isInfoEnabled()) {
				log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
			}

			return;
		}

		if (state.equals(LifecycleState.NEW)) {
			init();
		} else if (state.equals(LifecycleState.FAILED)) {
			stop();
		} else if (!state.equals(LifecycleState.INITIALIZED) &&
							 !state.equals(LifecycleState.STOPPED)) {
			invalidTransition(Lifecycle.BEFORE_START_EVENT);
		}

		try {
			// 设置状态为启动之前
			setStateInternal(LifecycleState.STARTING_PREP, null, false);
			// 交给子类来具体的实现
			startInternal();
			// 子类处理完成后
			if (state.equals(LifecycleState.FAILED)) {
				// This is a 'controlled' failure. The component put itself into the
				// FAILED state so call stop() to complete the clean-up.
				stop();
			} else if (!state.equals(LifecycleState.STARTING)) {
				// Shouldn't be necessary but acts as a check that sub-classes are
				// doing what they are supposed to.
				invalidTransition(Lifecycle.AFTER_START_EVENT);
			} else {
				setStateInternal(LifecycleState.STARTED, null, false);
			}
		} catch (Throwable t) {
			// This is an 'uncontrolled' failure so put the component into the
			// FAILED state and throw an exception.
			handleSubClassException(t, "lifecycleBase.startFail", toString());
		}
	}


	/**
     * Sub-classes must ensure that the state is changed to
     * {@link LifecycleState#STARTING} during the execution of this method.
     * Changing state will trigger the {@link Lifecycle#START_EVENT} event.
     *
     * If a component fails to start it may either throw a
     * {@link LifecycleException} which will cause it's parent to fail to start
     * or it can place itself in the error state in which case {@link #stop()}
     * will be called on the failed component but the parent component will
     * continue to start normally.
     *
     * @throws LifecycleException Start error occurred
     */
	protected abstract void startInternal() throws LifecycleException;


	/**
     * {@inheritDoc}
     */
	@Override
	public final synchronized void stop() throws LifecycleException {

		if (LifecycleState.STOPPING_PREP.equals(state) || LifecycleState.STOPPING.equals(state) ||
				LifecycleState.STOPPED.equals(state)) {

			if (log.isDebugEnabled()) {
				Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyStopped", toString()), e);
			} else if (log.isInfoEnabled()) {
				log.info(sm.getString("lifecycleBase.alreadyStopped", toString()));
			}

			return;
		}

		if (state.equals(LifecycleState.NEW)) {
			state = LifecycleState.STOPPED;
			return;
		}

		if (!state.equals(LifecycleState.STARTED) && !state.equals(LifecycleState.FAILED)) {
			invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
		}

		try {
			if (state.equals(LifecycleState.FAILED)) {
				// Don't transition to STOPPING_PREP as that would briefly mark the
				// component as available but do ensure the BEFORE_STOP_EVENT is
				// fired
				fireLifecycleEvent(BEFORE_STOP_EVENT, null);
			} else {
				setStateInternal(LifecycleState.STOPPING_PREP, null, false);
			}

			stopInternal();

			// Shouldn't be necessary but acts as a check that sub-classes are
			// doing what they are supposed to.
			if (!state.equals(LifecycleState.STOPPING) && !state.equals(LifecycleState.FAILED)) {
				invalidTransition(Lifecycle.AFTER_STOP_EVENT);
			}

			setStateInternal(LifecycleState.STOPPED, null, false);
		} catch (Throwable t) {
			handleSubClassException(t, "lifecycleBase.stopFail", toString());
		} finally {
			if (this instanceof Lifecycle.SingleUse) {
				// Complete stop process first
				setStateInternal(LifecycleState.STOPPED, null, false);
				destroy();
			}
		}
	}


	/**
     * Sub-classes must ensure that the state is changed to
     * {@link LifecycleState#STOPPING} during the execution of this method.
     * Changing state will trigger the {@link Lifecycle#STOP_EVENT} event.
     *
     * @throws LifecycleException Stop error occurred
     */
	protected abstract void stopInternal() throws LifecycleException;


	@Override
	public final synchronized void destroy() throws LifecycleException {
		if (LifecycleState.FAILED.equals(state)) {
			try {
				// Triggers clean-up
				stop();
			} catch (LifecycleException e) {
				// Just log. Still want to destroy.
				log.error(sm.getString("lifecycleBase.destroyStopFail", toString()), e);
			}
		}

		if (LifecycleState.DESTROYING.equals(state) || LifecycleState.DESTROYED.equals(state)) {
			if (log.isDebugEnabled()) {
				Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyDestroyed", toString()), e);
			} else if (log.isInfoEnabled() && !(this instanceof Lifecycle.SingleUse)) {
				// Rather than have every component that might need to call
				// destroy() check for SingleUse, don't log an info message if
				// multiple calls are made to destroy()
				log.info(sm.getString("lifecycleBase.alreadyDestroyed", toString()));
			}

			return;
		}

		if (!state.equals(LifecycleState.STOPPED) && !state.equals(LifecycleState.FAILED) &&
				!state.equals(LifecycleState.NEW) && !state.equals(LifecycleState.INITIALIZED)) {
			invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
		}

		try {
			setStateInternal(LifecycleState.DESTROYING, null, false);
			destroyInternal();
			setStateInternal(LifecycleState.DESTROYED, null, false);
		} catch (Throwable t) {
			handleSubClassException(t, "lifecycleBase.destroyFail", toString());
		}
	}


	/**
     * Sub-classes implement this method to perform any instance destruction
     * required.
     *
     * @throws LifecycleException If the destruction fails
     */
	protected abstract void destroyInternal() throws LifecycleException;


	/**
     * {@inheritDoc}
     */
	@Override
	public LifecycleState getState() {
		return state;
	}


	/**
     * {@inheritDoc}
     */
	@Override
	public String getStateName() {
		return getState().toString();
	}


	/**
     * Provides a mechanism for sub-classes to update the component state.
     * Calling this method will automatically fire any associated
     * {@link Lifecycle} event. It will also check that any attempted state
     * transition is valid for a sub-class.
     *
     * @param state The new state for this component
     * @throws LifecycleException when attempting to set an invalid state
     */
	protected synchronized void setState(LifecycleState state) throws LifecycleException {
		setStateInternal(state, null, true);
	}


	/**
     * Provides a mechanism for sub-classes to update the component state.
     * Calling this method will automatically fire any associated
     * {@link Lifecycle} event. It will also check that any attempted state
     * transition is valid for a sub-class.
     *
     * @param state The new state for this component
     * @param data  The data to pass to the associated {@link Lifecycle} event
     * @throws LifecycleException when attempting to set an invalid state
     */
	protected synchronized void setState(LifecycleState state, Object data)
	throws LifecycleException {
		setStateInternal(state, data, true);
	}


	private synchronized void setStateInternal(LifecycleState state, Object data, boolean check)
	throws LifecycleException {

		if (log.isDebugEnabled()) {
			log.debug(sm.getString("lifecycleBase.setState", this, state));
		}

		if (check) {
			// Must have been triggered by one of the abstract methods (assume
			// code in this class is correct)
			// null is never a valid state
			if (state == null) {
				invalidTransition("null");
				// Unreachable code - here to stop eclipse complaining about
				// a possible NPE further down the method
				return;
			}

			// Any method can transition to failed
			// startInternal() permits STARTING_PREP to STARTING
			// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
			// STOPPING
			if (!(state == LifecycleState.FAILED ||
						(this.state == LifecycleState.STARTING_PREP &&
						 state == LifecycleState.STARTING) ||
						(this.state == LifecycleState.STOPPING_PREP &&
						 state == LifecycleState.STOPPING) ||
						(this.state == LifecycleState.FAILED &&
						 state == LifecycleState.STOPPING))) {
				// No other transition permitted
				invalidTransition(state.name());
			}
		}

		this.state = state; // 修改当前对象的状态
		// 根据状态和事件的绑定关系获取对应的事件
		String lifecycleEvent = state.getLifecycleEvent();
		if (lifecycleEvent != null) {
			// 发布对应的事件  获取所有的监听器,执行触发的方法
			fireLifecycleEvent(lifecycleEvent, data);
		}
	}


	private void invalidTransition(String type) throws LifecycleException {
		String msg = sm.getString("lifecycleBase.invalidTransition", type, toString(), state);
		throw new LifecycleException(msg);
	}


	private void handleSubClassException(Throwable t, String key, Object... args) throws LifecycleException {
		setStateInternal(LifecycleState.FAILED, null, false);
		ExceptionUtils.handleThrowable(t);
		String msg = sm.getString(key, args);
		if (getThrowOnFailure()) {
			if (!(t instanceof LifecycleException)) {
				t = new LifecycleException(msg, t);
			}
			throw (LifecycleException) t;
		} else {
			log.error(msg, t);
		}
	}
}

init方法

我们就以LifecycleBase中的init方法为例,来分析生命周期的过程。首先在执行init方法时,第一句就做了一个判断,如果当前状态不是新建状态的话,就会调用初始化之前的操作,把代码拉到上面就会发现,刚开始的默认状态就是NEW,也就是当一个对象被创建出来的初始状态就是NEW状态

下面就是定义好的各种状态枚举

  • NEW(false, null):表示新创建的状态,尚未初始化。
  • INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT):表示正在初始化的状态,初始化之前的事件。
  • INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT):表示初始化完成的状态,初始化之后的事件。
  • STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT):表示准备启动的状态,启动之前的事件。
  • STARTING(true, Lifecycle.START_EVENT):表示正在启动的状态,启动事件。
  • STARTED(true, Lifecycle.AFTER_START_EVENT):表示启动完成的状态,启动之后的事件。
  • STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT):表示准备停止的状态,停止之前的事件。
  • STOPPING(false, Lifecycle.STOP_EVENT):表示正在停止的状态,停止事件。
  • STOPPED(false, Lifecycle.AFTER_STOP_EVENT):表示停止完成的状态,停止之后的事件。
  • DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT):表示准备销毁的状态,销毁之前的事件。
  • DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT):表示销毁完成的状态,销毁之后的事件。
  • FAILED(false, null):表示失败的状态,没有关联的生命周期事件。

start方法

接下来我们再看看satrt方法

  1. 状态检查:
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
    LifecycleState.STARTED.equals(state)) {
  // 组件已经处于启动准备、启动中或已启动状态
  if (log.isDebugEnabled()) {
    Exception e = new LifecycleException();
    log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
  } else if (log.isInfoEnabled()) {
    log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
  }
  return;
}
    • 如果组件的状态已经是 STARTING_PREPSTARTINGSTARTED,则说明组件已经在启动过程中,或者已经启动。这时,会记录日志,并返回,避免重复启动。
  1. 状态处理:
if (state.equals(LifecycleState.NEW)) {
  init();
} else if (state.equals(LifecycleState.FAILED)) {
  stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
           !state.equals(LifecycleState.STOPPED)) {
  invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
    • 如果当前状态是 NEW,则调用 init() 方法进行初始化。
    • 如果当前状态是 FAILED,则调用 stop() 方法停止组件。
    • 如果当前状态既不是 INITIALIZED 也不是 STOPPED,则调用 invalidTransition() 方法,表示状态转换无效。
  1. 状态设置和处理:
try {
  setStateInternal(LifecycleState.STARTING_PREP, null, false);
  startInternal();
  if (state.equals(LifecycleState.FAILED)) {
    stop();
  } else if (!state.equals(LifecycleState.STARTING)) {
    invalidTransition(Lifecycle.AFTER_START_EVENT);
  } else {
    setStateInternal(LifecycleState.STARTED, null, false);
  }
} catch (Throwable t) {
  handleSubClassException(t, "lifecycleBase.startFail", toString());
}
    • 设置状态为 STARTING_PREP,表示开始准备启动。
    • 调用 startInternal() 方法进行实际的启动操作。这个方法通常由子类实现。
    • 如果 startInternal() 方法后状态变为 FAILED,则调用 stop() 方法完成清理。
    • 如果状态不符合预期(应该是 STARTING),则调用 invalidTransition() 方法。
    • 如果状态符合预期,更新状态为 STARTED
    • 捕获所有 Throwable 异常,将组件状态设置为 FAILED 并处理异常。

stop方法

再来看看stop方法

  • 状态检查:
if (LifecycleState.STOPPING_PREP.equals(state) || LifecycleState.STOPPING.equals(state) ||
    LifecycleState.STOPPED.equals(state)) {

  if (log.isDebugEnabled()) {
    Exception e = new LifecycleException();
    log.debug(sm.getString("lifecycleBase.alreadyStopped", toString()), e);
  } else if (log.isInfoEnabled()) {
    log.info(sm.getString("lifecycleBase.alreadyStopped", toString()));
  }

  return;
}
  • 如果组件的状态已经是 STOPPING_PREPSTOPPINGSTOPPED,则说明组件已经在停止过程中或已经停止。这时,记录日志并返回,避免重复停止。
  • 状态处理:
if (state.equals(LifecycleState.NEW)) {
  state = LifecycleState.STOPPED;
  return;
}

if (!state.equals(LifecycleState.STARTED) && !state.equals(LifecycleState.FAILED)) {
  invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
}
  • 如果当前状态是 NEW,则直接将状态设置为 STOPPED 并返回。
  • 如果当前状态既不是 STARTED 也不是 FAILED,则调用 invalidTransition() 方法,表示状态转换无效。
  • 状态设置和处理:
try {
  if (state.equals(LifecycleState.FAILED)) {
    // 失败状态下,不转到 STOPPING_PREP,但确保触发 BEFORE_STOP_EVENT 事件
    fireLifecycleEvent(BEFORE_STOP_EVENT, null);
  } else {
    setStateInternal(LifecycleState.STOPPING_PREP, null, false);
  }

  stopInternal();

  if (!state.equals(LifecycleState.STOPPING) && !state.equals(LifecycleState.FAILED)) {
    invalidTransition(Lifecycle.AFTER_STOP_EVENT);
  }

  setStateInternal(LifecycleState.STOPPED, null, false);
} catch (Throwable t) {
  handleSubClassException(t, "lifecycleBase.stopFail", toString());
} finally {
  if (this instanceof Lifecycle.SingleUse) {
    // 完成停止过程后,销毁组件
    setStateInternal(LifecycleState.STOPPED, null, false);
    destroy();
  }
}
  • 如果状态是 FAILED,则不转到 STOPPING_PREP 状态,但确保触发 BEFORE_STOP_EVENT 事件。
  • 否则,将状态设置为 STOPPING_PREP,表示开始准备停止。
  • 调用 stopInternal() 方法进行实际的停止操作。这个方法通常由子类实现。
  • 检查状态是否符合预期(应该是 STOPPINGFAILED),如果不符合,调用 invalidTransition() 方法。
  • 如果状态符合预期,更新状态为 STOPPED
  • 捕获所有 Throwable 异常,将组件状态设置为 FAILED 并处理异常。
  • 销毁处理:
finally {
  if (this instanceof Lifecycle.SingleUse) {
    setStateInternal(LifecycleState.STOPPED, null, false);
    destroy();
  }
}
  • 如果组件是 Lifecycle.SingleUse 类型,意味着组件只能使用一次。完成停止过程后,将状态设置为 STOPPED 并调用 destroy() 方法销毁组件。

destroy方法

最后也就是destroy方法了

  1. 处理失败状态:
if (LifecycleState.FAILED.equals(state)) {
  try {
    // 触发清理操作
    stop();
  } catch (LifecycleException e) {
    // 记录日志,但仍然进行销毁
    log.error(sm.getString("lifecycleBase.destroyStopFail", toString()), e);
  }
}
    • 如果组件处于 FAILED 状态,尝试调用 stop() 方法进行清理。如果 stop() 失败,则记录错误日志,但仍然继续执行销毁操作。
  1. 检查销毁状态:
if (LifecycleState.DESTROYING.equals(state) || LifecycleState.DESTROYED.equals(state)) {
  if (log.isDebugEnabled()) {
    Exception e = new LifecycleException();
    log.debug(sm.getString("lifecycleBase.alreadyDestroyed", toString()), e);
  } else if (log.isInfoEnabled() && !(this instanceof Lifecycle.SingleUse)) {
    // 如果组件不是单次使用的,记录信息日志
    log.info(sm.getString("lifecycleBase.alreadyDestroyed", toString()));
  }
  return;
}
    • 如果组件已经在 DESTROYING 状态或者已经 DESTROYED,则说明组件已经处于销毁过程中或已销毁。这时记录相应的日志并返回,避免重复销毁。
  1. 状态验证和处理:
if (!state.equals(LifecycleState.STOPPED) && !state.equals(LifecycleState.FAILED) &&
    !state.equals(LifecycleState.NEW) && !state.equals(LifecycleState.INITIALIZED)) {
  invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
}
    • 如果当前状态既不是 STOPPEDFAILEDNEW 也不是 INITIALIZED,则调用 invalidTransition() 方法,表示状态转换无效。
  1. 设置状态和执行销毁:
try {
    setStateInternal(LifecycleState.DESTROYING, null, false);
    destroyInternal();
    setStateInternal(LifecycleState.DESTROYED, null, false);
} catch (Throwable t) {
    handleSubClassException(t, "lifecycleBase.destroyFail", toString());
}
    • 设置状态为 DESTROYING,表示开始销毁操作。
    • 调用 destroyInternal() 方法进行实际的销毁操作。这个方法通常由子类实现。
    • 将状态更新为 DESTROYED,表示销毁完成。
    • 捕获所有 Throwable 异常,将组件状态设置为 FAILED 并处理异常。

这四个方法的总体流程

在组件的生命周期管理中,start() 方法的核心任务是将组件从准备状态转变为活动状态。方法首先检查当前状态是否为 STARTING_PREPSTARTINGSTARTED,以确保组件没有处于这些状态中,如果是,则记录调试或信息日志以避免重复启动,并直接返回。如果组件的状态为 NEW,则执行初始化操作以准备启动;如果状态不符合 INITIALIZEDSTOPPED,则调用 invalidTransition() 方法处理无效的状态转换。随后,方法将状态设置为 STARTING_PREP,表示组件正在准备启动,接着调用 startInternal() 方法,由子类实现具体的启动逻辑。启动完成后,方法检查状态是否变为 FAILED,如是则调用 stop() 方法进行清理,若状态不符合 STARTING,则处理无效的状态转换。如果一切正常,则将状态更新为 STARTED。在过程中捕获任何异常,方法通过 handleSubClassException() 记录详细的异常信息,并将组件状态设置为 FAILED,确保所有异常都得到妥善处理。

stop() 方法负责将组件从运行状态转换为停止状态。首先,它检查当前状态是否为 STOPPING_PREPSTOPPINGSTOPPED,以避免重复停止。如果组件状态为 NEW,直接将状态设置为 STOPPED,跳过停止操作。若组件处于 STARTEDFAILED 状态,则执行状态验证,若状态不符合要求,则调用 invalidTransition() 处理无效的状态转换。接着,如果状态为 FAILED,直接触发 BEFORE_STOP_EVENT 事件而不改变状态;否则,将状态设置为 STOPPING_PREP,准备进入停止过程。然后,调用 stopInternal() 方法,由子类实现具体的停止逻辑。停止操作完成后,检查状态是否符合预期(应为 STOPPINGFAILED),否则处理无效的状态转换,并将状态更新为 STOPPED。方法在处理异常时,通过 handleSubClassException() 记录详细的异常信息,并将状态设置为 FAILED。最后,如果组件是 Lifecycle.SingleUse 类型,完成停止操作后调用 destroy() 方法进行销毁。

destroy() 方法中,组件的销毁过程从检查失败状态开始。如果状态为 FAILED,则尝试调用 stop() 进行清理,尽管 stop() 可能失败,但仍然继续销毁过程。接着,检查组件是否已经在 DESTROYINGDESTROYED 状态,如果是,则记录相应的日志并返回,避免重复销毁。如果状态不符合 STOPPEDFAILEDNEWINITIALIZED,则调用 invalidTransition() 处理无效的状态转换。然后,将状态设置为 DESTROYING,表示组件正在进入销毁过程,并调用 destroyInternal() 方法,由子类实现具体的销毁逻辑。销毁操作完成后,状态更新为 DESTROYED。在异常处理方面,destroy() 方法通过 handleSubClassException() 捕获并记录所有异常,确保销毁过程中的问题得到妥善处理。

handleSubClassException() 方法负责处理来自子类的异常,并记录详细的错误信息。方法通过指定的消息键和组件的 toString() 信息来记录异常,以提供丰富的上下文信息,帮助诊断问题。

我们再来看看bin目录下的catalina脚本

这段代码用于设置环境变量并配置 Java 应用程序的启动参数,特别是当需要启用 Java 调试代理(JPDA)时。具体来说,它执行以下步骤:

  1. 设置默认变量:初始化 _EXECJAVAMAINCLASSACTIONSECURITY_POLICY_FILEDEBUG_OPTSJPDA 环境变量。其中,_EXECJAVA 被设置为 _RUNJAVAMAINCLASS 被设置为 org.apache.catalina.startup.BootstrapACTION 被设置为 start,其余变量则保持空值。
  2. 检查调试参数:检查脚本的第一个参数(%1)是否为 "jpda"。如果是,则启用 JPDA 调试,并将 JPDA 变量设置为 "jpda"。如果不是 "jpda",则跳过后续的 JPDA 配置。
  3. 配置 JPDA 调试选项
    • 设置传输方式:如果 JPDA_TRANSPORT 变量为空,则将其设置为默认的 dt_socket。这指定了调试代理的传输协议。
    • 设置调试地址:如果 JPDA_ADDRESS 变量为空,则将其设置为 localhost:8000,这是调试代理监听的地址和端口。
    • 设置挂起选项:如果 JPDA_SUSPEND 变量为空,则将其设置为 n,即启动时不挂起。
    • 设置调试选项:如果 JPDA_OPTS 变量为空,则根据前面的设置生成调试选项,并将其配置到 JPDA_OPTS 变量中。调试选项的格式为 -agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
  1. 移除第一个参数shift 命令用于将参数向左移动一个位置,这样可以处理脚本中的其他参数。
  2. 跳过 JPDA 配置noJpda 标签下的代码块在检查完参数后被执行,确保仅在不需要 JPDA 调试时才执行。

第二行mainclass指向的就是Bootstrap中的main方法,这就是为什么我什么点击脚本就能启动项目,现在我们再回到Bootstrap中main方法,好好分析一下

  1. 初始化 daemon
    • 使用 synchronizeddaemonLock 对象加锁,以确保线程安全。
    • 检查 daemon 是否为 null。如果是,执行初始化操作:
      • 创建 Bootstrap 实例 bootstrap
      • 调用 bootstrap.init() 方法进行初始化。若发生异常,通过 handleThrowable(t) 处理异常,打印堆栈信息,然后退出方法。
      • 初始化成功后,将 daemon 设为 bootstrap 实例。
    • 如果 daemon 已初始化,设置当前线程的上下文类加载器为 daemon.catalinaLoader
  1. 解析命令行参数并执行操作
    • 确定命令
      • 默认命令设置为 "start"
      • 如果传入的 args 数组不为空,则取数组的最后一个元素作为实际命令。
    • 根据命令执行相应操作
      • startd
        • 修改命令为 "start"
        • 调用 daemon.load(args) 加载配置。
        • 调用 daemon.start() 启动服务。
      • stopd
        • 修改命令为 "stop"
        • 调用 daemon.stop() 停止服务。
      • start
        • 调用 daemon.setAwait(true) 设置服务启动后等待操作。
        • 加载配置并启动服务。
        • 如果 daemon.getServer() 返回 null,则通过 System.exit(1) 退出,表示启动失败。
      • stop
        • 调用 daemon.stopServer(args) 停止服务。
      • configtest
        • 加载配置并检查 daemon.getServer()
        • 如果 daemon.getServer()null,则通过 System.exit(1) 退出,表示配置测试失败。
        • 成功时,通过 System.exit(0) 退出,表示配置测试通过。
      • 其他命令
        • 记录警告信息,提示不支持的命令。
  1. 异常处理
    • 捕获 Throwable 异常。如果异常是 InvocationTargetException 且有 cause,则将 t 设为其 cause
    • 调用 handleThrowable(t) 处理异常,打印堆栈信息,并通过 System.exit(1) 退出程序,表示发生错误。

下面是Bootstrap中的所有代码

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.catalina.security.SecurityClassLoad;
import org.apache.catalina.startup.ClassLoaderFactory.Repository;
import org.apache.catalina.startup.ClassLoaderFactory.RepositoryType;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * Bootstrap loader for Catalina.  This application constructs a class loader
 * for use in loading the Catalina internal classes (by accumulating all of the
 * JAR files found in the "server" directory under "catalina.home"), and
 * starts the regular execution of the container.  The purpose of this
 * roundabout approach is to keep the Catalina internal classes (and any
 * other classes they depend on, such as an XML parser) out of the system
 * class path and therefore not visible to application level classes.
 *
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 */
public final class Bootstrap {

    private static final Log log = LogFactory.getLog(Bootstrap.class);

    /**
     * Daemon object used by main.
     */
    private static final Object daemonLock = new Object();
    private static volatile Bootstrap daemon = null;

    private static final File catalinaBaseFile;
    private static final File catalinaHomeFile;

    private static final Pattern PATH_PATTERN = Pattern.compile("(\"[^\"]*\")|(([^,])*)");

    static {
        // Will always be non-null 将永远是非空的
        String userDir = System.getProperty("user.dir");

        // Home first
        String home = System.getProperty(Constants.CATALINA_HOME_PROP);
        File homeFile = null;

        if (home != null) {
            File f = new File(home);
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        if (homeFile == null) {
            // First fall-back. See if current directory is a bin directory
            // in a normal Tomcat install
            File bootstrapJar = new File(userDir, "bootstrap.jar");

            if (bootstrapJar.exists()) {
                File f = new File(userDir, "..");
                try {
                    homeFile = f.getCanonicalFile();
                } catch (IOException ioe) {
                    homeFile = f.getAbsoluteFile();
                }
            }
        }

        if (homeFile == null) {
            // Second fall-back. Use current directory
            File f = new File(userDir);
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        catalinaHomeFile = homeFile;
        System.setProperty(
                Constants.CATALINA_HOME_PROP, catalinaHomeFile.getPath());

        // Then base
        String base = System.getProperty(Constants.CATALINA_BASE_PROP);
        if (base == null) {
            catalinaBaseFile = catalinaHomeFile;
        } else {
            File baseFile = new File(base);
            try {
                baseFile = baseFile.getCanonicalFile();
            } catch (IOException ioe) {
                baseFile = baseFile.getAbsoluteFile();
            }
            catalinaBaseFile = baseFile;
        }
        System.setProperty(
                Constants.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
    }

    // -------------------------------------------------------------- Variables


    /**
     * 本质上是 CataLina 实例
     * Daemon reference.
     */
    private Object catalinaDaemon = null;

    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;


    // -------------------------------------------------------- Private Methods


    private void initClassLoaders() {
        try {
            // 创建 commonLoader
            commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }


    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {

        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals(""))) {
            return parent;
        }

        value = replace(value);

        List<Repository> repositories = new ArrayList<>();

        String[] repositoryPaths = getPaths(value);

        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            // Local repository
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(new Repository(repository, RepositoryType.DIR));
            }
        }

        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }


    /**
     * System property replacement in the given string.
     *
     * @param str The original string
     * @return the modified string
     */
    protected String replace(String str) {
        // Implementation is copied from ClassLoaderLogManager.replace(),
        // but added special processing for catalina.home and catalina.base.
        String result = str;
        int pos_start = str.indexOf("${");
        if (pos_start >= 0) {
            StringBuilder builder = new StringBuilder();
            int pos_end = -1;
            while (pos_start >= 0) {
                builder.append(str, pos_end + 1, pos_start);
                pos_end = str.indexOf('}', pos_start + 2);
                if (pos_end < 0) {
                    pos_end = pos_start - 1;
                    break;
                }
                String propName = str.substring(pos_start + 2, pos_end);
                String replacement;
                if (propName.length() == 0) {
                    replacement = null;
                } else if (Constants.CATALINA_HOME_PROP.equals(propName)) {
                    replacement = getCatalinaHome();
                } else if (Constants.CATALINA_BASE_PROP.equals(propName)) {
                    replacement = getCatalinaBase();
                } else {
                    replacement = System.getProperty(propName);
                }
                if (replacement != null) {
                    builder.append(replacement);
                } else {
                    builder.append(str, pos_start, pos_end + 1);
                }
                pos_start = str.indexOf("${", pos_end + 1);
            }
            builder.append(str, pos_end + 1, str.length());
            result = builder.toString();
        }
        return result;
    }


    /**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    public void init() throws Exception {
        // 创建相关的类加载器
        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");
        }
        // 通过反射创建了 Catalina 类对象
        Class<?> startupClass = catalinaLoader
            .loadClass("org.apache.catalina.startup.Catalina");
        // 创建了 Catalina 实例
        Object startupInstance = startupClass.getConstructor().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;
        // 把 sharedLoader 设置为了 commonLoader的父加载器
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);
        // Catalina 实例 赋值给了 catalinaDaemon
        catalinaDaemon = startupInstance;
    }


    /**
     * Load daemon.
     */
    private void load(String[] arguments) throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled()) {
            log.debug("Calling startup class " + method);
        }
        // 会执行 Catalina的load方法
        method.invoke(catalinaDaemon, param);
    }


    /**
     * getServer() for configtest
     */
    private Object getServer() throws Exception {

        String methodName = "getServer";
        Method method = catalinaDaemon.getClass().getMethod(methodName);
        return method.invoke(catalinaDaemon);
    }


    // ----------------------------------------------------------- Main Program


    /**
     * Load the Catalina daemon.
     * @param arguments Initialization arguments
     * @throws Exception Fatal initialization error
     */
    public void init(String[] arguments) throws Exception {

        init();
        load(arguments);
    }


    /**
     * Start the Catalina daemon.
     * @throws Exception Fatal start error
     */
    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }

        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        // 执行 Catalina 的start方法
        method.invoke(catalinaDaemon, (Object [])null);
    }


    /**
     * Stop the Catalina Daemon.
     * @throws Exception Fatal stop error
     */
    public void stop() throws Exception {
        Method method = catalinaDaemon.getClass().getMethod("stop", (Class []) null);
        method.invoke(catalinaDaemon, (Object []) null);
    }


    /**
     * Stop the standalone server.
     * @throws Exception Fatal stop error
     */
    public void stopServer() throws Exception {

        Method method =
            catalinaDaemon.getClass().getMethod("stopServer", (Class []) null);
        method.invoke(catalinaDaemon, (Object []) null);
    }


   /**
     * Stop the standalone server.
     * @param arguments Command line arguments
     * @throws Exception Fatal stop error
     */
    public void stopServer(String[] arguments) throws Exception {

        Object param[];
        Class<?> paramTypes[];
        if (arguments == null || arguments.length == 0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
        method.invoke(catalinaDaemon, param);
    }


    /**
     * Set flag.
     * @param await <code>true</code> if the daemon should block
     * @throws Exception Reflection error
     */
    public void setAwait(boolean await)
        throws Exception {

        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    }

    public boolean getAwait() throws Exception {
        Class<?> paramTypes[] = new Class[0];
        Object paramValues[] = new Object[0];
        Method method =
            catalinaDaemon.getClass().getMethod("getAwait", paramTypes);
        Boolean b=(Boolean)method.invoke(catalinaDaemon, paramValues);
        return b.booleanValue();
    }


    /**
     * Destroy the Catalina Daemon.
     */
    public void destroy() {

        // FIXME

    }


    /**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     * 通过脚本启动Tomcat时的主方法和入口点
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {
        synchronized (daemonLock) {
            if (daemon == null) {
                // init()完成之前不要设置daemon
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init(); // 通过名称是 初始化的方法
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }try {String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args); //
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            if (t instanceof InvocationTargetException && t.getCause() != null) {
                t = t.getCause();}
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);}}


    /**
     * Obtain the name of configured home (binary) directory. Note that home and
     * base may be the same (and are by default).
     * @return the catalina home
     */
    public static String getCatalinaHome() {
        return catalinaHomeFile.getPath();
    }


    /**
     * Obtain the name of the configured base (instance) directory. Note that
     * home and base may be the same (and are by default). If this is not set
     * the value returned by {@link #getCatalinaHome()} will be used.
     * @return the catalina base
     */
    public static String getCatalinaBase() {
        return catalinaBaseFile.getPath();
    }


    /**
     * Obtain the configured home (binary) directory. Note that home and
     * base may be the same (and are by default).
     * @return the catalina home as a file
     */
    public static File getCatalinaHomeFile() {
        return catalinaHomeFile;
    }


    /**
     * Obtain the configured base (instance) directory. Note that
     * home and base may be the same (and are by default). If this is not set
     * the value returned by {@link #getCatalinaHomeFile()} will be used.
     * @return the catalina base as a file
     */
    public static File getCatalinaBaseFile() {
        return catalinaBaseFile;
    }


    // Copied from ExceptionUtils since that class is not visible during start
    static void handleThrowable(Throwable t) {
        if (t instanceof ThreadDeath) {
            throw (ThreadDeath) t;
        }
        if (t instanceof StackOverflowError) {
            // Swallow silently - it should be recoverable
            return;
        }
        if (t instanceof VirtualMachineError) {
            throw (VirtualMachineError) t;
        }
        // All other instances of Throwable will be silently swallowed
    }

    // Copied from ExceptionUtils so that there is no dependency on utils
    static Throwable unwrapInvocationTargetException(Throwable t) {
        if (t instanceof InvocationTargetException && t.getCause() != null) {
            return t.getCause();
        }
        return t;
    }

    // Protected for unit testing
    protected static String[] getPaths(String value) {

        List<String> result = new ArrayList<>();
        Matcher matcher = PATH_PATTERN.matcher(value);

        while (matcher.find()) {
            String path = value.substring(matcher.start(), matcher.end());

            path = path.trim();
            if (path.length() == 0) {
                continue;
            }

            char first = path.charAt(0);
            char last = path.charAt(path.length() - 1);

            if (first == '"' && last == '"' && path.length() > 1) {
                path = path.substring(1, path.length() - 1);
                path = path.trim();
                if (path.length() == 0) {
                    continue;
                }
            } else if (path.contains("\"")) {
                // Unbalanced quotes
                // Too early to use standard i18n support. The class path hasn't
                // been configured.
                throw new IllegalArgumentException(
                        "The double quote [\"] character can only be used to quote paths. It must " +
                        "not appear in a path. This loader path is not valid: [" + value + "]");
            } else {
                // Not quoted - NO-OP
            }

            result.add(path);
        }
        return result.toArray(new String[0]);
    }
}

这段代码是 Apache Tomcat 启动过程中的 Bootstrap 类实现。Bootstrap 类负责初始化 Tomcat 容器并管理其生命周期。让我们详细分析一下这个类的主要功能和关键部分:

1. 类的主要功能

  • 初始化系统属性:设置 catalina.homecatalina.base 属性。
  • 创建类加载器:初始化 Tomcat 所需的 ClassLoader
  • 启动 Tomcat 容器:通过反射调用 Tomcat 的启动类,执行容器的 start 方法。
  • 停止 Tomcat 容器:通过反射调用 Tomcat 的停止方法,执行容器的 stop 方法。

2. 关键部分分析

静态代码块
static {
  // 获取并设置 catalina.home
  String userDir = System.getProperty("user.dir");
  String home = System.getProperty(Constants.CATALINA_HOME_PROP);
  File homeFile = getHomeFile(home, userDir);
  catalinaHomeFile = homeFile;
  System.setProperty(Constants.CATALINA_HOME_PROP, catalinaHomeFile.getPath());

  // 获取并设置 catalina.base
  String base = System.getProperty(Constants.CATALINA_BASE_PROP);
  catalinaBaseFile = (base == null) ? catalinaHomeFile : new File(base).getCanonicalFile();
  System.setProperty(Constants.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
  • 获取 catalina.home:尝试从系统属性中获取,如果没有设置则通过检查当前目录和 bootstrap.jar 文件来推断。
  • 设置 catalina.base:如果没有设置 catalina.base,则使用 catalina.home 作为 catalina.base
类加载器初始化
private void initClassLoaders() {
  try {
    commonLoader = createClassLoader("common", null);
    if (commonLoader == null) {
      commonLoader = this.getClass().getClassLoader();
    }
    catalinaLoader = createClassLoader("server", commonLoader);
    sharedLoader = createClassLoader("shared", commonLoader);
  } catch (Throwable t) {
    handleThrowable(t);
    log.error("Class loader creation threw exception", t);
    System.exit(1);
  }
}
  • 创建类加载器:分别为 common, server, 和 shared 目录创建类加载器。若配置文件中没有指定,则使用默认的类加载器。
创建类加载器
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
  String value = CatalinaProperties.getProperty(name + ".loader");
  if ((value == null) || (value.equals(""))) {
    return parent;
  }
  value = replace(value);

  List<Repository> repositories = new ArrayList<>();
  String[] repositoryPaths = getPaths(value);

  for (String repository : repositoryPaths) {
    try {
      URL url = new URL(repository);
      repositories.add(new Repository(repository, RepositoryType.URL));
    } catch (MalformedURLException e) {
      if (repository.endsWith("*.jar")) {
        repository = repository.substring(0, repository.length() - "*.jar".length());
        repositories.add(new Repository(repository, RepositoryType.GLOB));
      } else if (repository.endsWith(".jar")) {
        repositories.add(new Repository(repository, RepositoryType.JAR));
      } else {
        repositories.add(new Repository(repository, RepositoryType.DIR));
      }
    }
  }
  return ClassLoaderFactory.createClassLoader(repositories, parent);
}
  • 处理不同类型的仓库:支持 URL、JAR 文件、目录等作为类加载源。
初始化和启动
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();

Method method = startupInstance.getClass().getMethod("setParentClassLoader", Class.forName("java.lang.ClassLoader"));
method.invoke(startupInstance, sharedLoader);
catalinaDaemon = startupInstance;
}
  • 初始化 Tomcat 启动类:通过反射加载 Catalina 启动类并创建其实例,将其设置为 catalinaDaemon
public void start() throws Exception {
if (catalinaDaemon == null) {
  init();
}
Method method = catalinaDaemon.getClass().getMethod("start");
method.invoke(catalinaDaemon);
}
  • 启动 Tomcat:通过反射调用 Catalina 实例的 start 方法。
停止 Tomcat
public void stop() throws Exception {
Method method = catalinaDaemon.getClass().getMethod("stop");
method.invoke(catalinaDaemon);
}

public void stopServer() throws Exception {
  Method method = catalinaDaemon.getClass().getMethod("stopServer");
  method.invoke(catalinaDaemon);
}
  • 停止 Tomcat:通过反射调用 Catalina 实例的 stopstopServer 方法。
主方法
public static void main(String args[]) {
  synchronized (daemonLock) {
    if (daemon == null) {
      Bootstrap bootstrap = new Bootstrap();
      try {
        bootstrap.init();
      } catch (Throwable t) {
        handleThrowable(t);
        t.printStackTrace();
        return;
      }
      daemon = bootstrap;
    } else {
      Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
  }
  try {
    String command = (args.length > 0) ? args[args.length - 1] : "start";
    switch (command) {
      case "startd":
        args[args.length - 1] = "start";
        daemon.load(args);
        daemon.start();
        break;
      case "stopd":
        args[args.length - 1] = "stop";
        daemon.stop();
        break;
      case "start":
        daemon.setAwait(true);
        daemon.load(args);
        daemon.start();
        if (null == daemon.getServer()) {
          System.exit(1);
        }
        break;
      case "stop":
        daemon.stopServer(args);
        break;
      case "configtest":
        daemon.load(args);
        if (null == daemon.getServer()) {
          System.exit(1);
        }
        System.exit(0);
        break;
      default:
        log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        break;
    }
  } catch (Throwable t) {
    if (t instanceof InvocationTargetException && t.getCause() != null) {
      t = t.getCause();
    }
    handleThrowable(t);
    t.printStackTrace();
    System.exit(1);
  }
}
  • 处理命令行参数:根据命令行参数调用相应的方法,如 startstopconfigtest 等。上面已经解释了,这里也就不再重复了。

从上面的代码我们可以看到Bootstrap其实没有做什么核心的事情,主要还是Catalina来完成的。

Tomcat核心对象的拆解

Tomcat架构原理

我们的Tomcat是一个Web容器,也是一个Servlet容器,那么我们先来考虑第一个问题,Tomcat是如何绑定端口,并且创建对应的ServerSocket的

可以看到这个配置文件中的Connector绑定了8080端口,至于它是如何绑定的,我们就需要走到Connector类里面去看看

进到Connector这个类后,找到initInternal这个方法,关键在于init方法,我们直接点进去看看

我们进来后发现是一个接口,主要看看AbstractProtocol的具体实现

进去之后,会有一个init方法,我主要去看看endpoint的init方法

endpoint的init方法点开之后,会发现有一个bind方法

点开bind方法之后,就会看见一个一个抽象方法,这个方法我们肯定要去找实现

可以看到有三个实现类,如果说我们想要手动的切换,或者说在tomcat中默认的实现方式是哪一个,这一块可能就要去研究研究

至于默认的是哪一个,我们就要去看看server.xml文件,我们所对应的Connector里面

终于找到了,在Connector中的setProtocol方法中,这个方法是用来设置 Coyote 协议的,在 Tomcat 中,Coyote 是 Tomcat 的连接器组件,负责处理底层的 HTTP 或 AJP 协议。下面我们来分析一下

APR(Apache Portable Runtime)连接器的检查:

boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
  • AprLifecycleListener.isAprAvailable():这是检查 APR 是否可用的方法。如果 APR 可用,表示 Tomcat 可以使用本地库来处理网络连接,提供更好的性能。
  • AprLifecycleListener.getUseAprConnector():检查当前配置是否允许使用 APR 连接器。

如果这两个条件都满足,那么 aprConnector 会被设置为 true,表示可以使用 APR 协议。如果条件不满足,则表示需要使用基于 Java 的 NIO 处理协议。

根据协议设置相应的处理类

根据传入的 protocol 字符串值,方法会决定使用哪个具体的协议处理类。

1. 处理 HTTP/1.1null 情况:
if ("HTTP/1.1".equals(protocol) || protocol == null) {
  if (aprConnector) {
    setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
  } else {
    setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
  }
}
  • 当传入 protocolHTTP/1.1null
    • 如果支持 APR(即 aprConnectortrue),则设置 Http11AprProtocol,这是使用本地库处理 HTTP/1.1 协议。
    • 否则,使用 Http11NioProtocol,这是一种基于 Java 的 NIO 实现,适用于没有 APR 的环境。
2. 处理 AJP/1.3 情况:
} else if ("AJP/1.3".equals(protocol)) {
  if (aprConnector) {
    setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
  } else {
    setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
  }
}
  • 当传入 protocolAJP/1.3 时:
    • 如果支持 APR,则设置为 AjpAprProtocol,这是使用本地库处理 AJP/1.3 协议。
    • 否则,设置为 AjpNioProtocol,这是基于 Java NIO 实现的 AJP 协议。
3. 处理其他协议:
} else {
  setProtocolHandlerClassName(protocol);
}
  • 当传入的 protocol 为其他值时,直接将该值作为协议处理类,允许用户自定义协议处理类名。
补充说明
  • @Deprecated:表示此方法已过时,会在未来版本(Tomcat 9)中被移除。未来的协议配置将通过构造函数设置,而不是通过该方法。
  • "HTTP/1.1" 协议示例
    • 如果 APR 可用且启用了 APR 连接器,setProtocolHandlerClassName 将被设置为 Http11AprProtocol,表示使用 APR 实现的 HTTP/1.1。
    • 如果 APR 不可用,则设置为 Http11NioProtocol,使用 Java NIO 实现。
  • "AJP/1.3" 协议示例
    • 如果 APR 可用,则设置为 AjpAprProtocol,表示使用 APR 实现的 AJP/1.3。
    • 如果 APR 不可用,则使用 AjpNioProtocol
  • 其他协议示例
    • 对于自定义的协议(非 HTTP/1.1AJP/1.3),传入的 protocol 字符串会直接作为协议处理类名。

上面我们分析了Tomcat是如何绑定端口服务的,接下来我们需要讨论下Tomcat是如何管理Servlet的,通过上面的绘图我们看到Tomcat是一个Servlet容器,我们每一个Web项目都是通过实现Servlet规范来完成相关的业务处理的。那么我们就要来看看Tomcat是如何管理我们的Web项目的。其实在server.xml文件中我们应该清楚其中的 `Context`标签其实代表的就是一个Web服务。

而且在官网中也有这样的描述:Apache Tomcat 8 Architecture (8.5.100) - Architecture Overview

通过上面的分析其实我们可以得到结论:

  • 一个Context标签代表了一个web项目
  • 要加载Servlet,只需要找到加载web.xml的工具类

Context标签对应的了一个Context类,Context是一个接口,默认的实现是StandardContext,在loadOnStartup中可以找到答案。

  • 遍历子容器:首先,代码遍历传入的 children 容器数组,找到每个 Wrapper(Servlet 的包装器)。如果 WrapperloadOnStartup 值大于等于0,则表示需要在应用启动时进行加载。将这些需要加载的 WrapperloadOnStartup 的顺序存储到 TreeMap 中。
  • 按启动顺序排序:由于 TreeMap 会根据键的大小自动排序,因此存入其中的 WrapperloadOnStartup 值排序。
  • 加载Servlet:在收集完需要加载的 Wrapper 后,代码依次对其进行加载,调用每个 Wrapperload() 方法。如果加载时发生 ServletException,则记录日志并判断是否需要中止应用启动。中止的条件是配置了 failCtxIfServletStartFailstrue
  • 返回加载结果:如果所有 Wrapper 都加载成功,返回 true;如果因为某个 Wrapper 的加载失败且需要中止应用,则返回 false

Wrapper是对Servlet的包装,增强了Servlet的应用。其中进入Wrapper的load方法中可以看到Servlet创建和init方法的执行。当然我们要看看Servlet是如何加载的,这时Servlet是配置在web.xml中的,那么web.xml的加载解析我们需要看看 `ContextConfig`中的处理。

里面会有一个createWebXml的方法。创建的WebXml对象其实就是对应的web.xml文件了webConfig()方法中。

这个方法真的超级无敌长,我们下面来分析一下

configureContext 方法的主要功能是通过读取 WebXml 对象中的信息,将其配置应用到 context 中,以确保应用在启动时根据部署描述符(通常是 web.xml 文件)中的配置来正确运行。这个方法非常全面,几乎涵盖了 web.xml 中所有可配置的元素,从上下文参数到安全约束,从过滤器到Servlet。

1. 公共ID和版本信息设置

  • context.setPublicId(webxml.getPublicId());
    这一步用于设置Web应用程序的 Public ID,即该应用所属的DTD或者XML Schema的标识符。这个标识符对于验证和处理XML文件很重要。
  • context.setEffectiveMajorVersion(webxml.getMajorVersion());
    设置Web应用的主版本号(比如Servlet API版本号)。有助于区分Web应用需要遵循的Servlet规范。
  • context.setEffectiveMinorVersion(webxml.getMinorVersion());
    设置Web应用的次版本号,与主版本号一起定义了该Web应用遵循的API级别。

2. 上下文参数设置

  • for (Entry<String, String> entry : webxml.getContextParams().entrySet()) { context.addParameter(entry.getKey(), entry.getValue()); }
    web.xml 中的 <context-param> 标签可以为应用程序定义全局的初始化参数。这个部分将这些参数(键值对)逐一添加到上下文对象 context,从而在应用程序运行时可以通过上下文获取这些参数。

3. HTTP方法安全设置

  • context.setDenyUncoveredHttpMethods(webxml.getDenyUncoveredHttpMethods());
    配置是否拒绝未被明确覆盖的HTTP方法。如果设置为 true,任何没有在 web.xml 中显式允许的HTTP方法(如DELETE、PATCH等)将会被拒绝。

4. 显示名称与分布式设置

  • context.setDisplayName(webxml.getDisplayName());
    设置Web应用的显示名称,通常是在管理控制台或服务器管理工具中显示的友好名称。
  • context.setDistributable(webxml.isDistributable());
    设置是否允许Web应用分布式部署,这决定了是否可以在多个JVM中同时运行此应用。

5. EJB(企业JavaBean)引用处理

  • for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) { context.getNamingResources().addLocalEjb(ejbLocalRef); }
    web.xml 中的 <ejb-local-ref><ejb-ref> 标签定义了本地EJB和远程EJB的引用。方法通过 addLocalEjbaddEjb 将这些引用信息添加到上下文命名资源中,确保Web应用可以正确调用EJB。

6. 环境条目(EnvEntry)和错误页面

  • for (ContextEnvironment environment : webxml.getEnvEntries().values()) { context.getNamingResources().addEnvironment(environment); }
    处理 <env-entry> 标签,将其添加到命名资源中。它定义了应用程序可通过JNDI访问的环境变量。
  • for (ErrorPage errorPage : webxml.getErrorPages().values()) { context.addErrorPage(errorPage); }
    处理 <error-page> 标签,设置自定义错误页面。当应用程序中发生指定的HTTP状态码(例如404)或异常时,会跳转到指定的错误页面。

7. 过滤器定义和映射

  • for (FilterDef filter : webxml.getFilters().values()) { context.addFilterDef(filter); }
    过滤器(FilterDef)用于拦截和处理请求和响应。方法通过读取 <filter> 标签中的定义,将过滤器添加到 context
  • for (FilterMap filterMap : webxml.getFilterMappings()) { context.addFilterMap(filterMap); }
    处理 <filter-mapping> 标签,定义过滤器的URL模式或Servlet名称,确保过滤器按预期绑定到特定的请求路径或Servlet。

8. JSP配置

  • context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
    这一步设置JSP的配置描述符,配置JSP相关的属性,如页面编码、EL表达式等。

9. 监听器配置

  • for (String listener : webxml.getListeners()) { context.addApplicationListener(listener); }
    处理 <listener> 标签,添加应用程序监听器。监听器可以监听Web应用的生命周期事件,如启动、关闭等。

10. 本地编码映射

  • for (Entry<String, String> entry : webxml.getLocaleEncodingMappings().entrySet()) { context.addLocaleEncodingMappingParameter(entry.getKey(), entry.getValue()); }
    处理 <locale-encoding-mapping-list>,为不同的语言环境设置字符编码,确保不同语言环境下的文本可以被正确处理。

11. 登录配置与安全约束

  • if (webxml.getLoginConfig() != null) { context.setLoginConfig(webxml.getLoginConfig()); }
    如果在 web.xml 中配置了 <login-config>,则设置登录验证的配置,如身份验证方法、登录页面和错误页面。
  • for (SecurityConstraint constraint : webxml.getSecurityConstraints()) { context.addConstraint(constraint); }
    处理 <security-constraint> 标签,定义Web应用的访问控制规则,确保应用程序可以根据定义的角色和URL模式保护资源。

12. Servlet与Servlet映射

  • for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); ... context.addChild(wrapper); }
    处理 <servlet> 标签,为每个Servlet创建 Wrapper,并设置Servlet类、初始化参数、加载顺序等信息。Wrapper 是Servlet实例在应用程序中的包装器。
  • for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); }
    处理 <servlet-mapping> 标签,将Servlet绑定到指定的URL模式。

13. Session配置

  • SessionConfig sessionConfig = webxml.getSessionConfig(); if (sessionConfig != null) { ... }
    处理 <session-config> 标签,设置Session相关的配置,如Session超时时间、Session Cookie属性(如名称、域、路径、HttpOnly、Secure、MaxAge等),以及Session的跟踪模式。

14. 欢迎文件

  • for (String welcomeFile : webxml.getWelcomeFiles()) { context.addWelcomeFile(welcomeFile); }
    处理 <welcome-file-list> 标签,设置Web应用的欢迎文件,即默认访问路径的文件(如 index.html)。

15. JSP属性组和URL模式

  • for (JspPropertyGroup jspPropertyGroup : webxml.getJspPropertyGroups()) { String jspServletName = context.findServletMapping("*.jsp"); ... }
    处理 <jsp-property-group> 标签,设置JSP的URL模式。如果匹配的JSP Servlet存在,则将对应的URL模式与Servlet进行映射。

16. 生命周期回调方法

  • for (Entry<String, String> entry : webxml.getPostConstructMethods().entrySet()) { context.addPostConstructMethod(entry.getKey(), entry.getValue()); }
    处理 <post-construct> 标签,添加初始化完成后的回调方法。
  • for (Entry<String, String> entry : webxml.getPreDestroyMethods().entrySet()) { context.addPreDestroyMethod(entry.getKey(), entry.getValue()); }
    处理 <pre-destroy> 标签,添加销毁之前的回调方法。

该方法通过逐条读取 WebXml 中的配置,将Web应用启动过程中需要的各种元素(如Servlet、过滤器、监听器、错误页面、安全约束、会话配置等)应用到 context。通过这种方式,应用在运行时会遵循这些配置项进行请求处理、安全控制、生命周期管理等功能。

顶级元素:

  • Server:是Tomcat架构的最顶层元素,表示整个Tomcat服务器实例。它包含多个Service,Server负责管理服务器生命周期以及处理服务器的全局配置。
  • Service:Server下的每个Service元素包含一个Engine和多个Connector。它将客户端请求的接入(Connector)与应用程序请求的处理(Engine)组合在一起,对外提供服务。

连接器(Connector):

  • Connector:是外部客户端与Tomcat之间的通信接口,处理客户端的HTTP、HTTPS或AJP协议的请求,并将请求转交给相应的Service中的Engine。Connector会侦听端口并接收来自网络的请求,同时将请求包装成一个Request对象交给后续的容器处理。
    • HTTP/HTTPS:用于处理Web服务器的请求。
    • AJP:Apache JServ Protocol,用于与其他Web服务器通信(例如Apache HTTP Server)。

容器(Container):

容器是Tomcat中的核心部分,负责处理Connector接收到的请求并生成响应。容器有层次结构,层级关系为:Engine -> Host -> Context

  1. Engine:是最顶层的容器,处理Service中的所有请求。它负责将请求分配给正确的Host。一个Engine只能与一个Service绑定。
  2. Host:是Engine中的子容器,代表虚拟主机。一个虚拟主机可以处理一个域名下的所有请求,并将这些请求转发给特定的Web应用(Context)。Host可以配置多个,每个Host对应一个虚拟主机。
  3. Context:是Host中的子容器,代表一个具体的Web应用。它负责管理应用的Servlet、Filter等组件,并处理特定Web应用下的请求。每个Context对应一个Web应用程序。

组件功能的总结:

  • Server:整个Tomcat服务器实例的根容器,管理多个Service。
  • Service:负责将请求连接(Connector)与请求处理(Engine)关联起来。
  • Connector:负责接受客户端的HTTP、HTTPS等请求并交给Engine处理。
  • Engine:是容器层级的顶层,负责管理多个虚拟主机(Host)。
  • Host:负责管理多个Web应用(Context),每个Host可以代表一个虚拟主机。
  • Context:代表具体的Web应用,负责处理Web应用中的请求。

核心组件的工作流程:

  1. 请求接收:客户端发出的请求(HTTP/HTTPS/AJP)首先被Connector接收,Connector负责将请求封装为Request对象。
  2. 请求分发:Connector将请求传递给Service中的Engine,Engine根据请求的虚拟主机(Host)来处理请求。
  3. 虚拟主机选择:Engine选择正确的Host(虚拟主机)处理请求。
  4. Web应用处理:Host将请求交给Context(Web应用),Context内的Servlet、Filter等组件进一步处理该请求。
  5. 生成响应:Context处理请求后生成响应,通过同样的路径返回给客户端。

当客户端提交一个对应请求后相关的核心组件的处理流程如下:

当然上面还有一些其他组件:

Executor:线程池

Manger:管理器【Session管理】

Valve:拦截器

Listener:监听器

Realm:数据库权限

换个角度看架构

Connector

Connector连接器接收外界请求,然后转换为对应的ServletRequest对象。

涉及到的几个对象的作用:

在有多线程处理的情况下,通过Executor线程池来处理:

官网的流程:https://tomcat.apache.org/tomcat-8.5-doc/architecture/requestProcess/request-process.png

Container

Container容器是在Connector处理完请求后获取到ServletRequest后内部处理请求的统一管理对象。

而需要把上面这个图的内容搞清楚,直接看代码的话还是比较头晕的,这时我们可以结合Tomcat的运行过程来分析

1. Bootstrap

Bootstrap 是Tomcat的入口类,负责初始化和启动Tomcat服务器。它通过以下核心方法完成相关操作:

  • init():负责自定义类加载器的初始化和创建Catalina实例。这个方法打破了Java的双亲委派模型,通过自定义的类加载器,优先尝试加载本地类,找不到时再委派给父类加载器。它通过覆盖findClass()loadClass()来实现自定义加载顺序。
  • load():此方法负责完成相关对象的初始化,例如Catalina对象的初始化等。它加载并解析配置文件(如server.xml),并实例化Tomcat的核心组件。
  • start():调用各个组件的start()方法,逐步启动Tomcat的各个部分,确保所有服务和连接器启动并准备好接收请求。
类加载器机制的自定义

在Java的类加载机制中,通常使用双亲委派模型,即先向父类加载器请求加载类,如果父类加载器找不到,再由当前类加载器加载。但在Tomcat中,由于需要加载Web应用中的类和Tomcat自身的类,自定义了类加载器,使得Tomcat能够优先加载Web应用程序相关的类,避免与JVM或其他库中的类冲突。

2. Catalina

Catalina 是Tomcat的核心类之一,主要负责:

  • 解析server.xml:读取Tomcat的配置文件,实例化配置的各个组件,如Server、Service、Connector等。
  • 管理组件生命周期:Catalina通过调用相关组件的init()start()方法,确保Tomcat的各个模块在正确的顺序中启动。

当Catalina解析配置后,它会实例化Server,并调用Server管理的所有Service及其组件的生命周期方法。

3. Lifecycle

Lifecycle 接口定义了Tomcat中所有组件的生命周期管理逻辑,具体包括:

  • init():初始化组件。
  • start():启动组件,准备好接收请求。
  • stop():停止组件,关闭服务。
  • destroy():销毁组件,释放资源。

在Tomcat的实现中,LifecycleBase 提供了生命周期的基本逻辑,它是一个基础类,具体组件可以继承它来实现自己的生命周期管理。

Tomcat使用 模板设计模式 来管理组件的生命周期。在模板设计模式中,父类定义了整体的流程和顺序,具体的步骤由子类实现。通过这种模式,可以将生命周期的逻辑集中管理,同时允许不同组件拥有各自的初始化和启动步骤。

4. Server

Server 是整个Tomcat服务器的顶级组件,负责管理多个Service。Server的职责主要包括:

  • 管理Service组件:它负责调用Service组件的init()start()方法,确保所有服务启动并运行。
  • 监听关闭事件:Server监听特定的端口,当接收到关闭命令时,它会触发关闭过程,依次停止Service和其他相关组件。

5. Service

Service 是Server中的一个重要组件,它负责将外部请求通过Connector传递给内部的Engine进行处理。Service的作用可以概括为:

  • 管理Connector和Engine:每个Service包含多个Connector(处理不同协议的请求),但只有一个Engine(处理请求的核心组件)。它将请求从Connector传递给Engine。
  • 生命周期管理:Service也遵循Lifecycle接口,管理其包含的所有组件的生命周期。

6. Connector

Connector 是Tomcat中负责处理客户端请求的组件。它的职责包括:

  • 接收请求:Connector负责监听特定端口(如HTTP或AJP),接收外部的请求。
  • 协议处理:它处理HTTP、HTTPS或AJP等协议,将请求解析为ServletRequest对象。
  • 将请求传递给Engine:Connector不会直接处理请求,而是将解析后的请求交给Engine(容器)处理。

7. Container

Container 是Tomcat中处理请求的核心部分。在Connector接收并解析请求后,Container负责处理这些请求并生成响应。Container包含以下子组件:

  • Engine:处理所有Service中的请求,是顶层容器。
  • Host:代表一个虚拟主机,处理特定域名下的所有请求。
  • Context:处理具体的Web应用中的请求,每个Context代表一个Web应用程序。

Container的工作流程

  1. 请求到达Connector:Connector解析请求,并将其转交给Engine。
  2. Engine分配请求:Engine根据请求中的Host信息将请求分配给对应的Host容器。
  3. Host选择应用:Host根据请求的路径选择具体的Web应用(Context)。
  4. Context处理请求:Context负责处理Web应用中的请求,通过Servlet或Filter生成响应。

Apache Tomcat 8 Architecture (8.5.100) - Startup,可以看看官网介绍

init方法

start方法

Container的处理过程

最后看看StandardHost是如何来实现Web项目部署的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值