碰巧最近在对接压力测试,在 HTTP 压测接口时还是产生了诸多疑问。考虑到网上大部分都仅解释从 DispatcherServlet 后开始的逻辑,这里只尽个人能力进行实践和整理,以便于日后使用 Arthas 分析应用时能够有更清晰的认知。
下面是 DEMO:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Data
public class HelloEntity {
private String key;
private String value;
}
@RequestMapping("/demo")
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping(value = "/hello")
public HelloEntity hello() {
return helloService.hello();
}
}
public interface HelloService {
HelloEntity hello();
}
@Service
public class HelloServiceImpl implements HelloService {
@Override
public HelloEntity hello() {
HelloEntity hello = new HelloEntity();
hello.setKey("hello");
hello.setValue("world!");
return hello;
}
}
服务启动
别动不动跟着面试八股去看 DispatcherServlet 了,Spring Boot 启动的时候也有很多信息需要关注的:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.12.RELEASE)
2023-02-24 15:39:00.270 INFO 21420 --- [ main] c.example.springMvcDemo.DemoApplication : Starting DemoApplication on DESKTOP-BSTI8OH with PID 21420 (D:\AWork\spring-mvc-demo\target\classes started by wujun in D:\AWork\spring-mvc-demo)
2023-02-24 15:39:00.274 INFO 21420 --- [ main] c.example.springMvcDemo.DemoApplication : No active profile set, falling back to default profiles: default
2023-02-24 15:39:01.057 INFO 21420 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http)
2023-02-24 15:39:01.065 INFO 21420 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-02-24 15:39:01.065 INFO 21420 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46]
2023-02-24 15:39:01.142 INFO 21420 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-02-24 15:39:01.142 INFO 21420 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 791 ms
2023-02-24 15:39:01.850 INFO 21420 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2023-02-24 15:39:02.087 INFO 21420 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path ''
2023-02-24 15:39:02.101 INFO 21420 --- [ main] c.example.springMvcDemo.DemoApplication : Started DemoApplication in 2.532 seconds (JVM running for 3.512)
2023-02-24 15:45:13.890 INFO 21420 --- [nio-8888-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-02-24 15:45:13.891 INFO 21420 --- [nio-8888-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-02-24 15:45:13.899 INFO 21420 --- [nio-8888-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
众所周知 Spring Boot 已内嵌了 Tomcat。在 INFO 中关注几个关键信息:
- TomcatWebServer
- StandardService
- StandardEngine
- ServletWebServerApplicationContext
- ThreadPoolTaskExecutor
- DemoApplication
- DispatcherServlet
既然方向都指明了,不妨一个一个都看看。
TomcatWebServer
WebServer,可用于控制 Tomcat Web 服务器。
通常应该使用 TomcatServletWebServerFactory 的 TomcatServletWebServerFactory 创建此类,不能直接创建。
可以在 initialize() 中看到日志来源,里面很多方法并不需要深入研究,仅通过命名和注释了解它的基本作用。
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
// 将实例Id添加到引擎名称
addInstanceIdToEngineName();
// 查找 Context
Context context = findContext();
// 增加生命周期监听器到此组件
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// 删除服务连接器,以便在服务启动时不会发生协议绑定
removeServiceConnectors();
}
});
// 启动 Server (注意不是启动 WebServer)以触发初始化监听器
this.tomcat.start();
// 我们可以直接在主线程中重新抛出失败异常
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// 与Jetty不同,所有 Tomcat 线程都是守护线程
// 我们创建一个阻塞的非守护线程来停止立即关闭
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
注意在当前类中除了 initialize() 方法外还有一个 start() 方法,对应第二条 TomcatWebServer 的 INFO 日志,在后面说明。
初识 StandardService
官方注释:
Service 接口的标准实现。关联的容器通常是 Engine 的实例,但不是必需的。
首先还是要找到 INFO 日志的打印位置作为切入点,观察 startInternal() 方法:
// 启动嵌套组件 Executors、Connectors 和 Containers 并实现
// org.apache.catalina.util.LifecycleBasestartInternal() 的要求
@Override
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// 第一步,启动 Container
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 启动 Executor
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// 第二步,启动 Connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
我们不妨在 log.info(sm.getString(“standardService.start.name”, this.name)); 处打断点,debug 可以看到,StandardService 的 INFO 日志即在此打印。
注意这里使用了 StringManager (sm) 去获取异常信息,这是 Tomcat 用于解耦异常信息所采取的方式。如果我们在此直接使用 log.info(“Exception String”),非常不利于信息内容的修改。此外,搭配 Resource Bundle 可以方便地实现信息内容的国际化。
StringManager 实例化后,能够获取 LocalString.properties 文件中的存储的异常信息。比如打开 org.apache.tomcat.embed:tomcat-embed-core:9.0.46.jar 包中的 org.apache.catalina.core.LocalString.properties 文件可以找到如下内容:

同理,后续的 standardEngine:start 也一样:

现在继续往下看代码,可以看到分别启动了 —— Container, Executor, Connector,又是一些非常陌生的类,但是我们可以找出一些共性方便我们去理解,比如说它们都调用了 start() 方法,所以它们是有实现一个共同的接口吗?
没错,我们在这里暂时跳出 StandardService,去看一下 org.apache.catalina.Lifecycle 的接口描述:
Common interface for component life cycle methods.
Catalina components may implement this interface (as well as the appropriate interface(s) for the functionality they support) in order to provide a consistent mechanism to start and stop the component.
组件生命周期方法的通用接口。
Catalina 组件可以实现该接口(以及它们支持的功能的适当接口),以便提供一致的启动和停止组件的机制。
Tomcat 的各个组件都遵循一套生命周期逻辑,因而抽出了一个通用的 Lifecycle 接口用于控制各个组件的活动。核心方法包括:
- init
- start
- stop
- destroy
Tomcat 的组件大多继承 Lifecycle 接口,当然也包括 startInternal() 方法中所见到的三个:

所以非常好理解,StandardService 的 startInternal() 方法按顺序启动了 Tomcat 中的一些组件。
这里还要再加点题外话,虽然我们切入是通过 StandardService 输出的 INFO 日志,但调用 StandardService 并非是 Tomcat 的启动流程的开始。Tomcat 启动的最开始在 org.apache.catalina.startup.Bootstrap,我们可以在此类的 main() 方法注释中看到:
Main method and entry point when starting Tomcat via the provided scripts.
通过提供的脚本启动 Tomcat 时的主要方法和入口点。
所以这里有必要把 StandardService 之前发生的事情也交代一下。
Bootstrap
主方法:
public static void main(String args[]) {
// 加锁
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 主要关注 init() 方法
// 该方法用于初始化守护线程
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// 当作为服务运行时,停止调用命令将在新线程上进行
// 因此确保使用了正确的类加载器来防止一系列类未找到的异常。
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
// 以下为根据 args 给的参数执行对应逻辑
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) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
main 方法下的 init() 方法
// 初始化守护线程
public void init() throws Exception {
// 初始化类加载器,包含 commonLoader, catalinaLoader, sharedLoader
initClassLoaders();
// 设置当前线程的类加载器为 catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
// 实例化一个 Catalina 对象
Object startupInstance = startupClass.getConstructor().newInstance();
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;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
// 使用反射调用 setParentClassLoader
// 等同于 startupInstance.setParentClassLoader(sharedLoader);
method.invoke(startupInstance, paramValues);
// 拿到实例化后的对象给 (ClassLoader) catalinaDaemon
catalinaDaemon = startupInstance;
}
当 command 为 start 时,会调用 load() 和 start() 方法,基本上就是通过反射调用 catalina 实例的对应方法:
// 加载 catalina 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);
}
// 使用反射调用 catalinaDaemon 的 load 方法,并传入参数
method.invoke(catalinaDaemon, param);
}
// 开始 catalina deamon
public void start() throws Exception {
// 确保 catalinaDaemon 不为空,不然重新调用 init()
if (catalinaDaemon == null) {
init();
}
// 使用反射调用 catalinaDaemon 的 start 方法,并传入参数
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
那么 Bootstrap 作为一个启动类就看完了。主要的疑问就转移到了 org.apache.catalina.startup.Catalina 下。
Catalina
Catalina 下 load 方法的主要内容:
// 开启一个新的服务实例
public void load() {
// 判断已加载标志
if (loaded) {
return;
}
loaded = true;
// 记录时间
long t1 = System.nanoTime();
// 此方法内容为空,已被移除
initDirs();
// 初始化名称(可能需要)
initNaming();
// 解析 server.xml
// 主要内容,下面会解释
parseServerXml(true);
// 拿到 server
Server s = getServer();
if (s == null) {
return;
}
// 将当前 catalina 实例赋给 server
getServer().setCatalina(this);
// 从 Bootsrap 中获取到 File 并设置给 server
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// 流重定向
initStreams();
// 开启新服务(调用 server 的 init 方法)
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
对于 parseServerXml 的代码如果嫌麻烦可以直接看后面
// 解析 server.xml 文件
protected void parseServerXml(boolean start) {
// 设置配置文件的路径
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// Digester 用于解析 server.xml 文件
// 如果选择使用生成代码替代配置文件,并且尚未生成 Loader
if (useGeneratedCode && !Digester.isGeneratedCodeLoaderSet()) {
// 加载 Loader
String loaderClassName = generatedCodePackage + ".DigesterGeneratedCodeLoader";
try {
// 使用反射获得一个实例,并配置到 Digester 中
Digester.GeneratedCodeLoader loader =
(Digester.GeneratedCodeLoader) Catalina.class.getClassLoader().loadClass(loaderClassName).newInstance();
Digester.setGeneratedCodeLoader(loader);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info(sm.getString("catalina.noLoader", loaderClassName), e);
} else {
log.info(sm.getString("catalina.noLoader", loaderClassName));
}
// 没有 Loader,因此无法进行代码替代
useGeneratedCode = false;
}
}
// 初始化
File serverXmlLocation = null;
String xmlClassName = null;
// 加载代码,需要用到 xmlClassName,而保存生成的代码,需要记录当前类已被生成
if (generateCode || useGeneratedCode) {
xmlClassName = start ? generatedCodePackage + ".ServerXml" : generatedCodePackage + ".ServerXmlStop";
}
// 要生成 Tomcat 内嵌代码,如果流程顺利会准备好 serverXmlLocation
if (generateCode) {
// 地址参数不为空
if (generatedCodeLocationParameter != null) {
// 读取文件
generatedCodeLocation = new File(generatedCodeLocationParameter);
// 非完全路径,表示相对路径,则根据 HomeFile 找到子文件
if (!generatedCodeLocation.isAbsolute()) {
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter);
}
} else {
// generatedCodeLocationParameter 为空,则直接使用 HomeFile
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), "work");
}
// 根据 generatedCodeLocation 找名称为 catalinaembedded 的文件
// 这里的 generatedCodePackage 值为 catalinaembedded
serverXmlLocation = new File(generatedCodeLocation, generatedCodePackage);
// 如果路径不为文件夹且无法创造文件夹,无法进行代码生成
if (!serverXmlLocation.isDirectory() && !serverXmlLocation.mkdirs()) {
log.warn(sm.getString("catalina.generatedCodeLocationError", generatedCodeLocation.getAbsolutePath()));
// Disable code generation
generateCode = false;
}
}
ServerXml serverXml = null;
// 如果选择用生成代码替代配置文件,则使用 Digester 加载之前的 xmlClassName,创建 serverXml 对象
if (useGeneratedCode) {
serverXml = (ServerXml) Digester.loadGeneratedClass(xmlClassName);
}
// 如果成功,则加载,不成功,则表示替代失败
if (serverXml != null) {
serverXml.load(this);
} else {
// 尝试从 ConfigFileLoader 的源路径获取 ServerXml 的资源
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// 创建并执行 Digester,设置解析规则
// createStartDigester() 定义了各个 XML 节点对应的接口的实现类
Digester digester = start ? createStartDigester() : createStopDigester();
// 读取流
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
// 将新对象推到对象堆栈的顶部
digester.push(this);
// 如果要生成 Tomcat 内嵌代码
if (generateCode) {
digester.startGeneratingCode();
generateClassHeader(digester, start);
}
// 使用 Digester 分析指定输入源的内容。返回对象堆栈中的根元素(如果有)
digester.parse(inputSource);
// 如果 从配置文件生成 Tomcat 嵌入代码
if (generateCode) {
// 保证 generateCode 的格式
generateClassFooter(digester);
// 写入 generateCode, 名称为 "ServerXml.java" 或 "ServerXmlStop.java"
// 存放的路径为前面拿到的 serverXmlLocation
try (FileWriter writer = new FileWriter(new File(serverXmlLocation,
start ? "ServerXml.java" : "ServerXmlStop.java"))) {
writer.write(digester.getGeneratedCode().toString());
}
// 结束生成代码
digester.endGeneratingCode();
// 标记已生成的 xmlClass
Digester.addGeneratedClass(xmlClassName);
}
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
}
}
}
这里还是简要的总结一下 load() 所做的工作。其实大致分成了两步,第一步是解析 server.xml 文件,第二步是调用了 server.init()。
比较繁琐的是 parseServerXml 的工作,大体来说有两个关键参数,generateCode 表示是否通过配置文件生成 Tomcat 的内嵌代码,useGeneratedCode 是否使用生成的代码替换配置文件(可以生成内嵌代码但不替换),默认值都为 false。
关于为什么要增加这两个配置,单纯的注释给的信息太少,我这里的猜测是生成代码后保存在文件夹内,再次启动时可以读取之前生成的代码快速加载。这里给出 ChatGPT 的回答:

首先此方法要去读取 server 文件,然后创建 Digester,设置解析规则,定义各个 XML 节点对应的接口的实现类。其实还可以更简单粗暴的理解这个方法,就是删除所有和 generateCode/useGeneratedCode 相关的逻辑:
// 解析 server.xml 文件
protected void parseServerXml(boolean start) {
// 设置配置文件的路径
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
// 初始化源
ServerXml serverXml = null;
// 没有使用生成代码或生成代码失败
if (serverXml != null) {
serverXml.load(this);
} else {
// 尝试从 ConfigFileLoader 的源路径获取 ServerXml 的资源
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// 创建并执行 Digester,设置解析规则
// createStartDigester() 定义了各个 XML 节点对应的接口的实现类
Digester digester = start ? createStartDigester() : createStopDigester();
// 读取流
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
// 将新对象推到对象堆栈的顶部
digester.push(this);
// 使用 Digester 分析指定输入源的内容。返回对象堆栈中的根元素(如果有)
digester.parse(inputSource);
} catch (Exception e) {
...
}
}
}
然后来看 Catalina#start() 方法:
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
// 调用 server.start() 方法
try {
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;
}
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
if (generateCode) {
// 生成加载程序,该加载程序将加载所有生成的类
generateLoader();
}
// 注册关闭挂钩
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// 如果正在使用 JULI,请禁用 JULI 的关闭挂钩,因为关闭挂钩并行运行
// 如果JULI的挂钩在 CatalinaShutdownHook() 之前完成,则日志消息可能会丢失
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
这里我画一个图来梳理一下到目前的逻辑:

StandardServer
我们能看到 Bootstrap 的 main() 方法如果刨去初始化过程和各种状态/异常判断,执行顺利的话,就是调用了 StandardServer 的 initInternal() 和 startInternal()。
// 启动前初始化。这用于在 Unix 操作环境下允许 Connector 绑定到受限端口
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// 初始化实用程序执行器
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// 注册全局 String 缓存
// 请注意,虽然缓存是全局的,但如果 JVM 中存在多个服务器(可能在嵌入时发生)
// 则同一缓存将以多个名称注册
onameStringCache = register(new StringCache(), "type=StringCache");
// 注册 MBeanFactory
// factory 的容器为当前 Server
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// 注册名称资源
globalNamingResources.init();
// 使用来自公共和共享类加载器的 JAR 填充扩展验证器
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// 遍历类加载器层次结构,在系统类加载器处停止。
// 这将添加共享(如果存在)和公共类加载器
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException | IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// 初始化定义的 Service
for (Service service : services) {
service.init();
}
}
这里完成了很多资源和信息的注册,最后调用 Service 的 init(),再看看 StandardServer 的 startInternal() 方法:
// 启动嵌套组件 Service 并实现 org.apache.catalina.util.LifecycleBase#startInternal() 的要求
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
// 保存和管理在 J2EE 企业命名上下文及其关联的 JNDI 上下文中定义的命名资源
globalNamingResources.start();
// 启动我们定义的服务
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
() -> startPeriodicLifecycleEvent(), 0, 60, TimeUnit.SECONDS);
}
}
同样的,主要是一些状态的处理,并调用了 Service 的 start() 。我们已经知道 Bootstrap 里面调用 Server 的 start(),实际上会去调用实现类 StandardServer 的 startInternal()。
初识 StandardService 时,StandardService 里面也有实现一个 startInternal()。
是不是开始感受到一些结构设计了。我们知道 Server 和 Service 的 start() 方法都是由 Lifecycle 定义的。而 Lifecycle 接口实现了一个抽象类 LifecycleBase,由 LifecycleBase 类在 start() 方法内对当前组件的状态进行判断,并在内部调用抽象方法 startInternal()。StandardServer 和 StandardService 都是继承了 LifecycleBase 的子类,因此也就都需要实现 startInternal() 这个抽象方法。
所以这里 startInternal() 方法最终由最底层的实现类 StandardService 实现。
这里我表达一下自己对这种设计的优点的理解,为了简化模型方便理解,我们只看 Server 和 Service,先来看一下接口和类之间的关系:

就只看 start() 方法,在 LifecycleBase 中,对 start() 进行了实现,实际将 start() 分成了两部分(外层和内层),startInternal() 部分是内层,依然是交由子类实现,而外层是状态控制逻辑,依据组件在调用 start() 方法时的当前状态,采取不同的处理逻辑。
比如当前组件的状态是 STARTING_PREP, STARTING, STARTED,则当前组件就不需要再次启动。如果当前组件的状态是 NEW,则应该在启动前补充一次初始化。

由于所有组件都遵循一致的生命周期模型,所以这里的状态控制逻辑可以被所有子类(对应组件的实现类)复用。
再看 StandardService
在 StandardServer 中,对多个 Service 进行了 start()。根据之前总结的规律我们知道,如果 start() 方法顺利调用,经过状态控制逻辑后,便会执行 startInternal(),而在初次看 StandardService 时我们已经知道其中的 startInternal() 方法内会分别启动 Engine(Container), Executor, Connector 的 start() 方法。
这个时候需要继续进入查看 Engine Executor 和 Connector 。
StandardEngine
我们先来到 StandardEngine 类,首先继续关注其 initInternal() 方法和 startInternal() 方法,因为对于一个新的 Engine,在其启动顺利的情况下,必然会先后执行这两个方法。首先来看 initInternal():
// StandardEngine
@Override
protected void initInternal() throws LifecycleException {
// 保证启动前存在 Realm,获取已配置的 Realm,如果没有也会提供默认实现
getRealm();
// 这里需要继续去看 ContainerBase 和 LifecycleMBeanBase 的 initInternal() 方法
super.initInternal();
}
这里需要解释一下 Realm:

// ContainerBase
@Override
protected void initInternal() throws LifecycleException {
// 通过 getStartStopThreads() 获得当前配置的用于启动停止与此容器关联的子级的线程数
// 重新配置 StartStopExecutor
// StartStopExecutor 是一个线程池,用于启动和停止容器
// 容器启动/停止时,StartStopExecutor 用于异步地启动/停止容器中的组件
reconfigureStartStopExecutor(getStartStopThreads());
// 进入 LifecycleMBeanBase 的 initInternal() 方法
super.initInternal();
}
// LifecycleMBeanBase
@Override
protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
if (oname == null) {
// getRegistry() 通过工厂类方法返回 Registry 实例
// getMBeanServer() 通过工厂类方法返回 MBeanServer 实例,如果不存在则创建
mserver = Registry.getRegistry(null, null).getMBeanServer();
// 创建 oname,该名称将作为该组件在 MBeanServer 中的唯一标识符
oname = register(this, getObjectNameKeyProperties());
}
}
稍作总结,StandardEngine 的 initInternal() 整个流程简要做了哪些工作:
- 获取(或创建) Realm
- 重新配置(或创建) StartStopExecutor
- 获取(或创建) MBeanServer 和 ObjectName
之后来关注 StandardEngine 的 startInternal() 方法,可以发现除了打印日志之外,直接调用了父类的方法实现:
// StandardEngine
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
// Standard container startup
super.startInternal();
}
之后来关注 StandardEngine 的 startInternal() 方法,可以发现除了打印日志之外,直接调用了父类的方法实现:
// StandardEngine
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
// Standard container startup
super.startInternal();
}
同理,super.startInternal() 方法会进入 ContainerBase 的 startInternal() 方法中:
// ContainerBase
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
// 如果存在,启动附属组件
logger = null;
// 获取日志组件
getLogger();
// 提供集群组件连接到当前容器的方式
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
// 如果存在,启动子容器
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
// 开放管道的阀门
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 设置当前生命周期状态
setState(LifecycleState.STARTING);
// Start our thread
// 启动线程
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}
在 ContainerBase 的 startInternal() 方法中,会启动相关的组件,例如:
- Logger
- Cluster
- Realm
- Pipeline