开始前的准备
先到官网上去下载Tomcat源码,注意jdk版本,你用的哪个版本就下载哪个,如果你目前的环境是jdk8的话,直接用我下面的压缩包就可以了
📎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,然后依次启动,结果就如下
- ClientSocket
-
- 这个类创建了一个客户端,它尝试连接到本地服务器(localhost)的端口
8899
。 - 它通过
Socket
类与服务器建立连接,然后使用BufferedWriter
写入一段字符串"呼啦呼啦呼啦"
到服务器。 - 在写入完数据后,程序调用
flush()
方法确保数据发送到服务器,并且关闭了输出流和Socket。
- 这个类创建了一个客户端,它尝试连接到本地服务器(localhost)的端口
- 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表达式的 |
| 和邮件相关的规范 |
persistence | 持久化相关的 |
security | 和安全相关的内容 |
servlet | 这个指定的是Servlet的开发规范,Tomcat本质上就是一个实现了Servlet规范的一个容器,Servlet定义了服务端处理Http请求和响应的方式(规范) |
websocket | 定义了使用 websocket 协议的服务端和客户端 API |
定义了基于 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接口主要方法
init(ServletConfig config)
:
-
- 当Servlet被加载到服务器中时,容器会调用这个方法进行初始化。
- 它接收一个
ServletConfig
对象,包含Servlet的配置信息。 - 如果初始化失败,Servlet抛出
ServletException
,则无法加载该Servlet。
getServletConfig()
:
-
- 返回
ServletConfig
对象,通常在init()
中保存,以便在后续的操作中获取初始化参数。
- 返回
service(ServletRequest req, ServletResponse res)
:
-
- 这是处理客户端请求的核心方法。每次有请求到达时,容器都会调用这个方法。
- 它接收两个参数:
ServletRequest
(客户端的请求)和ServletResponse
(服务器的响应)。 - 方法可能会抛出
ServletException
或IOException
,如果请求处理过程中发生错误。
getServletInfo()
:
-
- 返回Servlet的相关信息(如版本、作者等),以便于识别。
destroy()
:
-
- 当Servlet被销毁时,容器会调用这个方法,通常用于释放资源。
Servlet生命周期
- 构造: 容器实例化Servlet。
- 初始化: 调用
init()
。 - 服务请求: 每次客户端请求都会调用
service()
。 - 销毁: 当Servlet不再需要时,调用
destroy()
,释放资源。
Tomcat源码解析
接下来我打开conf包下的server.xml配置文件
这个XML文件是Tomcat服务器的配置文件,展示了Tomcat的整体结构。以下是它的结构和每个主要部分的解释:
<Server>
:
-
- 表示整个Tomcat服务器的配置。
- 属性
port="8005"
定义了Tomcat监听关闭请求的端口。 shutdown="SHUTDOWN"
表示关闭命令的指令。
<Listener>
:
-
- 这些是Tomcat的监听器,用来监视和管理Tomcat的生命周期和性能。
- 每个
Listener
的className
属性指定了它的具体功能:
-
-
VersionLoggerListener
: 记录Tomcat启动时的版本信息。AprLifecycleListener
: 支持APR(Apache Portable Runtime)及SSL配置。JreMemoryLeakPreventionListener
: 防止JRE内存泄漏。GlobalResourcesLifecycleListener
: 监听全局资源的生命周期。ThreadLocalLeakPreventionListener
: 防止ThreadLocal
相关的内存泄漏。
-
<GlobalNamingResources>
:
-
- 全局资源的定义,如数据库连接池等资源。
- 这里定义了一个
UserDatabase
,用于管理用户信息,指向conf/tomcat-users.xml
文件。
<Service>
:
-
- 定义了Tomcat的服务,表示服务运行时的一些配置。
- 属性
name="Catalina"
是Tomcat默认的服务名称。
<Connector>
:
-
- 定义Tomcat的连接器,处理HTTP请求。
- 第一个
Connector
:
-
-
port="8080"
表示监听HTTP请求的端口。protocol="HTTP/1.1"
定义了使用的协议。connectionTimeout="20000"
设置了连接超时时间为20秒。redirectPort="8443"
指明在需要SSL时将请求重定向到8443端口。
-
-
- 第二个
Connector
(被注释掉):
- 第二个
-
-
- 这是一个SSL连接器,使用HTTPS协议和自定义证书(存储在
localhost-rsa.jks
文件中)。这段配置被注释掉了,可以根据需求启用。
- 这是一个SSL连接器,使用HTTPS协议和自定义证书(存储在
-
<Engine>
:
-
- Tomcat的请求处理引擎。每个
Service
中只有一个Engine
。 - **
name="Catalina"
**是引擎的名字,defaultHost="localhost"
是默认的主机。 <Realm>
:
- Tomcat的请求处理引擎。每个
-
-
- 安全领域(Realm)的配置,用于验证和授权用户。
LockOutRealm
: 防止用户连续失败的登录尝试。UserDatabaseRealm
: 用于从UserDatabase
资源中读取用户信息。
-
<Host>
:
-
- 表示一个虚拟主机,处理特定的Web应用程序。
name="localhost"
: 定义主机名为localhost。appBase="webapps"
: Web应用程序的基础目录。unpackWARs="true"
: 自动解压缩WAR文件。autoDeploy="true"
: 自动部署新的Web应用程序。
<Valve>
:
-
- 定义了Tomcat的日志记录组件。
<Context>
:
-
- 表示Web应用的上下文。每个
<Context>
标签可以配置不同的Web应用。
- 表示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_PREP
、STARTING
或STARTED
状态时,调用start()
是无效的。 - 当组件处于
STOPPING_PREP
、STOPPING
或STOPPED
状态时,调用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方法
- 状态检查:
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_PREP
、STARTING
或STARTED
,则说明组件已经在启动过程中,或者已经启动。这时,会记录日志,并返回,避免重复启动。
- 如果组件的状态已经是
- 状态处理:
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()
方法,表示状态转换无效。
- 如果当前状态是
- 状态设置和处理:
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_PREP
、STOPPING
或STOPPED
,则说明组件已经在停止过程中或已经停止。这时,记录日志并返回,避免重复停止。 - 状态处理:
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()
方法进行实际的停止操作。这个方法通常由子类实现。 - 检查状态是否符合预期(应该是
STOPPING
或FAILED
),如果不符合,调用invalidTransition()
方法。 - 如果状态符合预期,更新状态为
STOPPED
。 - 捕获所有
Throwable
异常,将组件状态设置为FAILED
并处理异常。 - 销毁处理:
finally {
if (this instanceof Lifecycle.SingleUse) {
setStateInternal(LifecycleState.STOPPED, null, false);
destroy();
}
}
- 如果组件是
Lifecycle.SingleUse
类型,意味着组件只能使用一次。完成停止过程后,将状态设置为STOPPED
并调用destroy()
方法销毁组件。
destroy方法
最后也就是destroy方法了
- 处理失败状态:
if (LifecycleState.FAILED.equals(state)) {
try {
// 触发清理操作
stop();
} catch (LifecycleException e) {
// 记录日志,但仍然进行销毁
log.error(sm.getString("lifecycleBase.destroyStopFail", toString()), e);
}
}
-
- 如果组件处于
FAILED
状态,尝试调用stop()
方法进行清理。如果stop()
失败,则记录错误日志,但仍然继续执行销毁操作。
- 如果组件处于
- 检查销毁状态:
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
,则说明组件已经处于销毁过程中或已销毁。这时记录相应的日志并返回,避免重复销毁。
- 如果组件已经在
- 状态验证和处理:
if (!state.equals(LifecycleState.STOPPED) && !state.equals(LifecycleState.FAILED) &&
!state.equals(LifecycleState.NEW) && !state.equals(LifecycleState.INITIALIZED)) {
invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
}
-
- 如果当前状态既不是
STOPPED
、FAILED
、NEW
也不是INITIALIZED
,则调用invalidTransition()
方法,表示状态转换无效。
- 如果当前状态既不是
- 设置状态和执行销毁:
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_PREP
、STARTING
或 STARTED
,以确保组件没有处于这些状态中,如果是,则记录调试或信息日志以避免重复启动,并直接返回。如果组件的状态为 NEW
,则执行初始化操作以准备启动;如果状态不符合 INITIALIZED
或 STOPPED
,则调用 invalidTransition()
方法处理无效的状态转换。随后,方法将状态设置为 STARTING_PREP
,表示组件正在准备启动,接着调用 startInternal()
方法,由子类实现具体的启动逻辑。启动完成后,方法检查状态是否变为 FAILED
,如是则调用 stop()
方法进行清理,若状态不符合 STARTING
,则处理无效的状态转换。如果一切正常,则将状态更新为 STARTED
。在过程中捕获任何异常,方法通过 handleSubClassException()
记录详细的异常信息,并将组件状态设置为 FAILED
,确保所有异常都得到妥善处理。
stop()
方法负责将组件从运行状态转换为停止状态。首先,它检查当前状态是否为 STOPPING_PREP
、STOPPING
或 STOPPED
,以避免重复停止。如果组件状态为 NEW
,直接将状态设置为 STOPPED
,跳过停止操作。若组件处于 STARTED
或 FAILED
状态,则执行状态验证,若状态不符合要求,则调用 invalidTransition()
处理无效的状态转换。接着,如果状态为 FAILED
,直接触发 BEFORE_STOP_EVENT
事件而不改变状态;否则,将状态设置为 STOPPING_PREP
,准备进入停止过程。然后,调用 stopInternal()
方法,由子类实现具体的停止逻辑。停止操作完成后,检查状态是否符合预期(应为 STOPPING
或 FAILED
),否则处理无效的状态转换,并将状态更新为 STOPPED
。方法在处理异常时,通过 handleSubClassException()
记录详细的异常信息,并将状态设置为 FAILED
。最后,如果组件是 Lifecycle.SingleUse
类型,完成停止操作后调用 destroy()
方法进行销毁。
在 destroy()
方法中,组件的销毁过程从检查失败状态开始。如果状态为 FAILED
,则尝试调用 stop()
进行清理,尽管 stop()
可能失败,但仍然继续销毁过程。接着,检查组件是否已经在 DESTROYING
或 DESTROYED
状态,如果是,则记录相应的日志并返回,避免重复销毁。如果状态不符合 STOPPED
、FAILED
、NEW
或 INITIALIZED
,则调用 invalidTransition()
处理无效的状态转换。然后,将状态设置为 DESTROYING
,表示组件正在进入销毁过程,并调用 destroyInternal()
方法,由子类实现具体的销毁逻辑。销毁操作完成后,状态更新为 DESTROYED
。在异常处理方面,destroy()
方法通过 handleSubClassException()
捕获并记录所有异常,确保销毁过程中的问题得到妥善处理。
handleSubClassException()
方法负责处理来自子类的异常,并记录详细的错误信息。方法通过指定的消息键和组件的 toString()
信息来记录异常,以提供丰富的上下文信息,帮助诊断问题。
我们再来看看bin目录下的catalina脚本
这段代码用于设置环境变量并配置 Java 应用程序的启动参数,特别是当需要启用 Java 调试代理(JPDA)时。具体来说,它执行以下步骤:
- 设置默认变量:初始化
_EXECJAVA
、MAINCLASS
、ACTION
、SECURITY_POLICY_FILE
、DEBUG_OPTS
和JPDA
环境变量。其中,_EXECJAVA
被设置为_RUNJAVA
,MAINCLASS
被设置为org.apache.catalina.startup.Bootstrap
,ACTION
被设置为start
,其余变量则保持空值。 - 检查调试参数:检查脚本的第一个参数(
%1
)是否为"jpda"
。如果是,则启用 JPDA 调试,并将JPDA
变量设置为"jpda"
。如果不是"jpda"
,则跳过后续的 JPDA 配置。 - 配置 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%
。
- 设置传输方式:如果
- 移除第一个参数:
shift
命令用于将参数向左移动一个位置,这样可以处理脚本中的其他参数。 - 跳过 JPDA 配置:
noJpda
标签下的代码块在检查完参数后被执行,确保仅在不需要 JPDA 调试时才执行。
第二行mainclass指向的就是Bootstrap中的main方法,这就是为什么我什么点击脚本就能启动项目,现在我们再回到Bootstrap中main方法,好好分析一下
- 初始化
daemon
:
-
- 使用
synchronized
对daemonLock
对象加锁,以确保线程安全。 - 检查
daemon
是否为null
。如果是,执行初始化操作:
- 使用
-
-
- 创建
Bootstrap
实例bootstrap
。 - 调用
bootstrap.init()
方法进行初始化。若发生异常,通过handleThrowable(t)
处理异常,打印堆栈信息,然后退出方法。 - 初始化成功后,将
daemon
设为bootstrap
实例。
- 创建
-
-
- 如果
daemon
已初始化,设置当前线程的上下文类加载器为daemon.catalinaLoader
。
- 如果
- 解析命令行参数并执行操作:
-
- 确定命令:
-
-
- 默认命令设置为
"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)
退出,表示配置测试通过。
- 加载配置并检查
-
-
-
-
- 其他命令:
-
-
-
-
- 记录警告信息,提示不支持的命令。
-
-
- 异常处理:
-
- 捕获
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.home
和catalina.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
实例的stop
或stopServer
方法。
主方法
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);
}
}
- 处理命令行参数:根据命令行参数调用相应的方法,如
start
、stop
、configtest
等。上面已经解释了,这里也就不再重复了。
从上面的代码我们可以看到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.1
或 null
情况:
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
}
- 当传入
protocol
为HTTP/1.1
或null
时:
-
- 如果支持 APR(即
aprConnector
为true
),则设置Http11AprProtocol
,这是使用本地库处理 HTTP/1.1 协议。 - 否则,使用
Http11NioProtocol
,这是一种基于 Java 的 NIO 实现,适用于没有 APR 的环境。
- 如果支持 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");
}
}
- 当传入
protocol
为AJP/1.3
时:
-
- 如果支持 APR,则设置为
AjpAprProtocol
,这是使用本地库处理 AJP/1.3 协议。 - 否则,设置为
AjpNioProtocol
,这是基于 Java NIO 实现的 AJP 协议。
- 如果支持 APR,则设置为
3. 处理其他协议:
} else {
setProtocolHandlerClassName(protocol);
}
- 当传入的
protocol
为其他值时,直接将该值作为协议处理类,允许用户自定义协议处理类名。
补充说明
- @Deprecated:表示此方法已过时,会在未来版本(Tomcat 9)中被移除。未来的协议配置将通过构造函数设置,而不是通过该方法。
- "HTTP/1.1" 协议示例:
-
- 如果 APR 可用且启用了 APR 连接器,
setProtocolHandlerClassName
将被设置为Http11AprProtocol
,表示使用 APR 实现的 HTTP/1.1。 - 如果 APR 不可用,则设置为
Http11NioProtocol
,使用 Java NIO 实现。
- 如果 APR 可用且启用了 APR 连接器,
- "AJP/1.3" 协议示例:
-
- 如果 APR 可用,则设置为
AjpAprProtocol
,表示使用 APR 实现的 AJP/1.3。 - 如果 APR 不可用,则使用
AjpNioProtocol
。
- 如果 APR 可用,则设置为
- 其他协议示例:
-
- 对于自定义的协议(非
HTTP/1.1
或AJP/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 的包装器)。如果Wrapper
的loadOnStartup
值大于等于0,则表示需要在应用启动时进行加载。将这些需要加载的Wrapper
按loadOnStartup
的顺序存储到TreeMap
中。 - 按启动顺序排序:由于
TreeMap
会根据键的大小自动排序,因此存入其中的Wrapper
按loadOnStartup
值排序。 - 加载Servlet:在收集完需要加载的
Wrapper
后,代码依次对其进行加载,调用每个Wrapper
的load()
方法。如果加载时发生ServletException
,则记录日志并判断是否需要中止应用启动。中止的条件是配置了failCtxIfServletStartFails
为true
。 - 返回加载结果:如果所有
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的引用。方法通过addLocalEjb
和addEjb
将这些引用信息添加到上下文命名资源中,确保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
。
- Engine:是最顶层的容器,处理Service中的所有请求。它负责将请求分配给正确的Host。一个Engine只能与一个Service绑定。
- Host:是Engine中的子容器,代表虚拟主机。一个虚拟主机可以处理一个域名下的所有请求,并将这些请求转发给特定的Web应用(Context)。Host可以配置多个,每个Host对应一个虚拟主机。
- 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应用中的请求。
核心组件的工作流程:
- 请求接收:客户端发出的请求(HTTP/HTTPS/AJP)首先被Connector接收,Connector负责将请求封装为
Request
对象。 - 请求分发:Connector将请求传递给Service中的Engine,Engine根据请求的虚拟主机(Host)来处理请求。
- 虚拟主机选择:Engine选择正确的Host(虚拟主机)处理请求。
- Web应用处理:Host将请求交给Context(Web应用),Context内的Servlet、Filter等组件进一步处理该请求。
- 生成响应: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的工作流程:
- 请求到达Connector:Connector解析请求,并将其转交给Engine。
- Engine分配请求:Engine根据请求中的Host信息将请求分配给对应的Host容器。
- Host选择应用:Host根据请求的路径选择具体的Web应用(Context)。
- Context处理请求:Context负责处理Web应用中的请求,通过Servlet或Filter生成响应。
Apache Tomcat 8 Architecture (8.5.100) - Startup,可以看看官网介绍