前言
前文已述,Tomcat 的初始化由 Bootstrap 反射调用 Catalina 的 load 方法完成,包括解析 server.xml、实例化各组件、初始化组件等步骤。此番,我们将深入探究 Tomcat 如何启动 Web 应用,并解析其加载 ServletContextListener 及 Servlet 的机制。
前文参考文章:
【Tomcat 源码分析】揭秘 Tomcat 启动-初篇
Tomcat 启动逻辑层层递进,各部件协同运作。其启动流程自上而下,依次启动各个组件,如图:
承接前文,我们已解析了 Catalina.load() 方法,接下来将深入探讨 daemon.start() 方法的执行过程。
Bootstrap
daemon.start()
启动过程与初始化类似,均由 Bootstrap 反射调用 Catalina 的 start 方法。
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
复制代码
Catalina
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
//调用Server的start方法,启动Server组件
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
// 注册勾子,用于安全关闭tomcat
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
// Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
if (await) {
await();
stop();
}
}
复制代码
Server
在先前的 Lifecycle 文章中,我们已阐述 StandardServer 重写了 startInternal 方法,并在此基础上实现了自身的启动逻辑。
StandardServer.startInternal
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
复制代码
在启动过程中,LifecycleBase 首先发出 STARTING_PREP 事件,StandardServer 额外还会发出 CONFIGURE_START_EVENT 和 STARTING 事件,通知 LifecycleListener 在启动前进行准备工作。例如,NamingContextListener 会处理 CONFIGURE_START_EVENT 事件,实例化 Tomcat 相关的上下文以及 ContextResource 资源。
随后,启动 Service 组件,这部分逻辑将在后续文章中详细分析。最后,LifecycleBase 发出 STARTED 事件,完成启动流程。
Service
StandardService 的 start 代码如下所示:
启动 Engine,Engine 的子容器也会被启动,Web 应用的部署将在这一步骤完成;
启动 Executor,这是 Tomcat 使用 Lifecycle 封装的线程池,继承自 java.util.concurrent.Executor 以及 Tomcat 的 Lifecycle 接口;
启动 Connector 组件,Connector 完成 Endpoint 的启动,此时意味着 Tomcat 可以对外提供请求服务。
StandardService.startInternal
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// 启动Engine
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 启动Executor线程池
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 启动MapperListener
mapperListener.start();
// 启动Connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
// logger......
}
}
}
}
复制代码
Engine
StandardEngine 的标准实现为 org.apache.catalina.core.StandardEngine。其构造函数的主要职责是使用默认的基础阀门创建 StandardEngine 组件。
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
复制代码
让我们来深入分析 StandardEngine.startInternal 方法。
StandardEngine.startInternal
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
复制代码
StandardEngine、StandardHost、StandardContext、StandardWrapper 这几个容器之间存在着父子关系。一个父容器可以包含多个子容器,并且每个子容器都对应一个父容器。Engine 是顶层父容器,它没有父容器。各个组件的包含关系如下图所示:
默认情况下,StandardEngine 只有一个子容器 StandardHost,一个 StandardContext 对应一个 Web 应用,而一个 StandardWrapper 对应一个 Web 应用里面的一个 Servlet。
StandardEngine、StandardHost、StandardContext、StandardWrapper 都是继承自 ContainerBase,各个容器的启动是由父容器调用子容器的 start 方法完成的,也就是说由 StandardEngine 启动 StandardHost,再由 StandardHost 启动 StandardContext,以此类推。
由于它们都继承自 ContainerBase,当调用 start 启动 Container 容器时,首先会执行 ContainerBase 的 start 方法。ContainerBase 会寻找子容器,并在线程池中启动子容器。StandardEngine 也不例外。
<font style="color:rgb(51, 51, 51);">ContainerBase</font>
ContainerBase 的 startInternal 方法如下所示,主要分为以下三个步骤:
<font style="color:rgb(51, 51, 51);">启动子容器</font>
<font style="color:rgb(51, 51, 51);">启动 Pipeline,并发出 STARTING 事件</font>
<font style="color:rgb(51, 51, 51);">如果 backgroundProcessorDelay 参数 >= 0,则开启 ContainerBackgroundProcessor 线程,用于调用子容器的 backgroundProcess 方法</font>
<font style="color:rgb(51, 51, 51);"></font>
protected synchronized void startInternal() throws LifecycleException {
// 省略若干代码......
// 把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
// 阻塞当前线程,直到子容器start完成
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
// 启用Pipeline
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
// 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,默认情况下backgroundProcessorDelay=-1,不会启用该线程
threadStart();
}
复制代码
ContainerBase 会将 StartChild 任务丢给线程池处理,并获取 Future 对象。然后,它会遍历所有 Future,并进行阻塞式的 result.get() 操作,这将异步启动转换为同步启动,只有所有子容器都启动完成才会继续运行。
我们再来看看提交到线程池的 StartChild 任务,它实现了 java.util.concurrent.Callable 接口,并在 call 方法中完成子容器的 start 动作。
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
复制代码
启动 Pipeline
默认使用 StandardPipeline 实现类,它也是一个 Lifecycle。在容器启动时,StandardPipeline 会遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,代码如下:
public class StandardPipeline extends LifecycleBase
implements Pipeline, Contained {
protected synchronized void startInternal() throws LifecycleException {
Valve current = first;
if (current == null) {
current = basic;
}
while (current != null) {
if (current instanceof Lifecycle)
((Lifecycle) current).start();
current = current.getNext();
}
setState(LifecycleState.STARTING);
}
}
复制代码
Host
在分析 Host 时,我们可以从 Host 的构造函数入手,该方法主要负责设置基础阀门。
ublic StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
复制代码
StandardEngine.startInternal
protected synchronized void startInternal() throws LifecycleException {
// errorValve默认使用org.apache.catalina.valves.ErrorReportValve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
// 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到 Pipeline 中
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
// 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
// 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
// 处理异常,省略......
}
}
// 调用父类 ContainerBase,完成统一的启动动作
super.startInternal();
}
复制代码
StandardHost Pipeline 包含的 Valve 组件:
basic:org.apache.catalina.core.StandardHostValve
first:org.apache.catalina.valves.AccessLogValve
需要注意的是,在往 Pipeline 中添加 Valve 阀门时,是添加到 first 后面,basic 前面。
Context
接下来,让我们深入分析 Context 的实现类 org.apache.catalina.core.StandardContext。
首先,我们来看看构造方法,该方法用于设置 Context.pipeline 的基础阀门。
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
复制代码
启动方法与之前分析的容器启动方法类似,这里不再赘述。
Wrapper
Wrapper 是一个 Servlet 的包装,我们先来看看构造方法。其主要作用是设置基础阀门 StandardWrapperValve
。
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
复制代码
这里每个容器中的 pipeline 设置的 StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve 都是非常重要的,它们在 HTTP 请求处理过程中扮演着关键角色,我们将在后续的文章中详细讲解。
结语
至此,整个启动过程便告一段落。整个启动过程由父组件控制子组件的启动,一层层往下传递,直到最后全部启动完成。
如有问题,欢迎微信搜索【码上遇见你】。
免费的Chat GPT可微信搜索【AI贝塔】进行体验,无限使用。
好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。
评论