碰巧最近在对接压力测试,在 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