版本信息

Spring Boot 2.5.4

Tomcat顶层结构图

xxx

前期准备

创建一个启动类,继承 SpringBootServletInitializer 类,并实现 configure 方法:

  1. package com.imooc;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.builder.SpringApplicationBuilder;
  4. import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
  5. /**
  6. * Wen(Joan) Zhao <withzhaowen@126.com>
  7. * 2021/12/13
  8. */
  9. public class MySpringBootTomcatStarter extends SpringBootServletInitializer {
  10. public static void main(String[] args) {
  11. Long time = System.currentTimeMillis();
  12. SpringApplication.run(MySpringBootTomcatStarter.class);
  13. System.out.println("===应用启动耗时:"+(System.currentTimeMillis()-time)+"===");
  14. }
  15. @Override
  16. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
  17. return builder.sources(this.getClass());
  18. }
  19. }

源码流程解析

  1. public static void main(String[] args) {
  2. Long time = System.currentTimeMillis();
  3. SpringApplication.run(MySpringBootTomcatStarter.class);
  4. System.out.println("===应用启动耗时:"+(System.currentTimeMillis()-time)+"===");
  5. }

进入 EventPublishingRunListener#EventPublishingRunListener:
image.png
不过这里目前没有监听器。

进入 SpringApplication#run:

  1. public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  2. return run(new Class<?>[] { primarySource }, args);
  3. }

调用另一个同名的重载方法 SpringApplication#run。

进入 SpringApplication#run,创建一个SpringApplication的实例对象:

  1. public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  2. return new SpringApplication(primarySources).run(args);
  3. }

进入其构造方法 SpringApplicaion#SpringApplication。

进入 SpringApplicaion#SpringApplication:

  1. @SuppressWarnings({ "unchecked", "rawtypes" })
  2. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  3. this.resourceLoader = resourceLoader;
  4. Assert.notNull(primarySources, "PrimarySources must not be null");
  5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  6. // 初始化了webApplicationType ,比如此处是“SERVLET”
  7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  8. setInitializers((Collection) getSpringFactoriesInstances(
  9. ApplicationContextInitializer.class));
  10. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  11. this.mainApplicationClass = deduceMainApplicationClass();
  12. }

上面这个过程,主要是初始化成员变量。

进入 SpringApplication#run:

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  5. ConfigurableApplicationContext context = null;
  6. configureHeadlessProperty();
  7. SpringApplicationRunListeners listeners = getRunListeners(args);
  8. listeners.starting(bootstrapContext, this.mainApplicationClass);
  9. try {
  10. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  11. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  12. configureIgnoreBeanInfo(environment);
  13. Banner printedBanner = printBanner(environment);
  14. // 创建应用上下文
  15. context = createApplicationContext();
  16. context.setApplicationStartup(this.applicationStartup);
  17. // 预处理上下文
  18. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  19. // 刷新上下文
  20. refreshContext(context);
  21. // 再刷新上下文
  22. afterRefresh(context, applicationArguments);
  23. stopWatch.stop();
  24. if (this.logStartupInfo) {
  25. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  26. }
  27. listeners.started(context);
  28. callRunners(context, applicationArguments);
  29. }
  30. catch (Throwable ex) {
  31. handleRunFailure(context, ex, listeners);
  32. throw new IllegalStateException(ex);
  33. }
  34. try {
  35. listeners.running(context);
  36. }
  37. catch (Throwable ex) {
  38. handleRunFailure(context, ex, null);
  39. throw new IllegalStateException(ex);
  40. }
  41. return context;
  42. }

在这里,有两个方法可以让我知道tomca在SpringBoot中是怎么启动的:创建应用上下文(createApplicationContext)和刷新上下文(refreshContext)。

进入 SpringApplication#createApplicationContext :

  1. protected ConfigurableApplicationContext createApplicationContext() {
  2. Class<?> contextClass = this.applicationContextClass;
  3. if (contextClass == null) {
  4. try {
  5. switch (this.webApplicationType) {
  6. case SERVLET:
  7. //创建AnnotationConfigServletWebServerApplicationContext
  8. contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
  9. break;
  10. case REACTIVE:
  11. contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
  12. break;
  13. default:
  14. contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
  15. }
  16. }
  17. catch (ClassNotFoundException ex) {
  18. throw new IllegalStateException(
  19. "Unable create a default ApplicationContext, "
  20. + "please specify an ApplicationContextClass",
  21. ex);
  22. }
  23. }
  24. return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
  25. }

此方法作用是创建上下文。

这里会创建AnnotationConfigServletWebServerApplicationContext类。
image.png

而AnnotationConfigServletWebServerApplicationContext类继承了ServletWebServerApplicationContext:
image.png
所以这个类是最终集成了AbstractApplicationContext。
image.png

进入 SpringApplication#refreshContext :

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2. refresh(context);
  3. if (this.registerShutdownHook) {
  4. try {
  5. context.registerShutdownHook();
  6. }
  7. catch (AccessControlException ex) {
  8. // Not allowed in some environments.
  9. }
  10. }
  11. }

此方法作用是刷新上下文。

进入 SpringApplication#refresh:

  1. protected void refresh(ApplicationContext applicationContext) {
  2. Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  3. ((AbstractApplicationContext) applicationContext).refresh();
  4. }

这里直接调用最终父类AbstractApplicationContext#refresh方法:
image.png

进入 AbstractApplicationContext#refresh:

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized(this.startupShutdownMonitor) {
  3. this.prepareRefresh();
  4. ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
  5. this.prepareBeanFactory(beanFactory);
  6. try {
  7. this.postProcessBeanFactory(beanFactory);
  8. this.invokeBeanFactoryPostProcessors(beanFactory);
  9. this.registerBeanPostProcessors(beanFactory);
  10. this.initMessageSource();
  11. this.initApplicationEventMulticaster();
  12. // 调用各个子类的onRefresh()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
  13. this.onRefresh();
  14. this.registerListeners();
  15. this.finishBeanFactoryInitialization(beanFactory);
  16. this.finishRefresh();
  17. } catch (BeansException var9) {
  18. if (this.logger.isWarnEnabled()) {
  19. this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
  20. }
  21. this.destroyBeans();
  22. this.cancelRefresh(var9);
  23. throw var9;
  24. } finally {
  25. this.resetCommonCaches();
  26. }
  27. }
  28. }

进入 ServletWebServerApplicationContext#onRefresh :

  1. protected void onRefresh() {
  2. super.onRefresh();
  3. try {
  4. createWebServer();
  5. }
  6. catch (Throwable ex) {
  7. throw new ApplicationContextException("Unable to start web server", ex);
  8. }
  9. }

进入 ServletWebServerApplicationContext#createWebServer:

  1. private void createWebServer() {
  2. WebServer webServer = this.webServer;
  3. ServletContext servletContext = getServletContext();
  4. if (webServer == null && servletContext == null) {
  5. ServletWebServerFactory factory = getWebServerFactory();
  6. this.webServer = factory.getWebServer(getSelfInitializer());
  7. }
  8. else if (servletContext != null) {
  9. try {
  10. getSelfInitializer().onStartup(servletContext);
  11. }
  12. catch (ServletException ex) {
  13. throw new ApplicationContextException("Cannot initialize servlet context",
  14. ex);
  15. }
  16. }
  17. initPropertySources();
  18. }

这里是创建webServer,但是还没有启动tomcat,这里是通过ServletWebServerFactory创建。

进入 接口 ServletWebServerFactory:

  1. @FunctionalInterface
  2. public interface ServletWebServerFactory {
  3. /**
  4. * Gets a new fully configured but paused {@link WebServer} instance. Clients should
  5. * not be able to connect to the returned server until {@link WebServer#start()} is
  6. * called (which happens when the {@link ApplicationContext} has been fully
  7. * refreshed).
  8. * @param initializers {@link ServletContextInitializer}s that should be applied as
  9. * the server starts
  10. * @return a fully configured and started {@link WebServer}
  11. * @see WebServer#stop()
  12. */
  13. WebServer getWebServer(ServletContextInitializer... initializers);
  14. }

此接口有3个实现类:
image.png
这里我们使用的是Tomcat,所以只查看TomcatServletWebServerFactory。

进入 TomcatServletWebServerFactory#getWebServer :

  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2. if (this.disableMBeanRegistry) {
  3. Registry.disableRegistry();
  4. }
  5. Tomcat tomcat = new Tomcat();
  6. File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
  7. tomcat.setBaseDir(baseDir.getAbsolutePath());
  8. // 创建Connector对象
  9. Connector connector = new Connector(this.protocol);
  10. connector.setThrowOnFailure(true);
  11. tomcat.getService().addConnector(connector);
  12. customizeConnector(connector);
  13. tomcat.setConnector(connector);
  14. tomcat.getHost().setAutoDeploy(false);
  15. configureEngine(tomcat.getEngine());
  16. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  17. tomcat.getService().addConnector(additionalConnector);
  18. }
  19. prepareContext(tomcat.getHost(), initializers);
  20. return getTomcatWebServer(tomcat);
  21. }

此方法创建了Tomcat对象,并且做了两件重要的事情:

  1. 把Connector对象添加到tomcat中:

    1. tomcat.getService().addConnector(connector);
  2. configureEngine(tomcat.getEngine):配置engine容器

    1. private void configureEngine(Engine engine) {
    2. engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
    3. for (Valve valve : this.engineValves) {
    4. engine.getPipeline().addValve(valve);
    5. }
    6. }

    image.png

进入 Tomcat#getEngine :

  1. public Engine getEngine() {
  2. Service service = this.getServer().findServices()[0];
  3. if (service.getContainer() != null) {
  4. return service.getContainer();
  5. } else {
  6. Engine engine = new StandardEngine();
  7. engine.setName("Tomcat");
  8. engine.setDefaultHost(this.hostname);
  9. engine.setRealm(this.createDefaultRealm());
  10. service.setContainer(engine);
  11. return engine;
  12. }
  13. }

此方法的作用是返回Engine容器;Engine是最高级别容器,Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器。

进入 TomcatServletWebServerFactory#getTomcatWebServer:

  1. protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
  2. return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
  3. }

调用构造函数实例化TomcatWebServer。
image.png
进入构造函数后,就是执行初始化方法。

进入 TomcatWebServer#initialize:

  1. private void initialize() throws WebServerException {
  2. // 控制台打印这条日志
  3. logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
  4. synchronized (this.monitor) {
  5. try {
  6. addInstanceIdToEngineName();
  7. Context context = findContext();
  8. context.addLifecycleListener((event) -> {
  9. if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
  10. // Remove service connectors so that protocol binding doesn't
  11. // happen when the service is started.
  12. removeServiceConnectors();
  13. }
  14. });
  15. // Start the server to trigger initialization listeners
  16. //===启动tomcat服务===
  17. this.tomcat.start();
  18. // We can re-throw failure exception directly in the main thread
  19. rethrowDeferredStartupExceptions();
  20. try {
  21. ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
  22. }
  23. catch (NamingException ex) {
  24. // Naming is not enabled. Continue
  25. }
  26. // Unlike Jetty, all Tomcat threads are daemon threads. We create a
  27. // blocking non-daemon to stop immediate shutdown
  28. //开启阻塞非守护进程
  29. startDaemonAwaitThread();
  30. }
  31. catch (Exception ex) {
  32. stopSilently();
  33. destroySilently();
  34. throw new WebServerException("Unable to start embedded Tomcat", ex);
  35. }
  36. }
  37. }

进入 Tomcat#start:

  1. public void start() throws LifecycleException {
  2. this.getServer();
  3. this.server.start();
  4. }

进入 TomcatWebServer#start:

  1. public void start() throws WebServerException {
  2. synchronized (this.monitor) {
  3. if (this.started) {
  4. return;
  5. }
  6. try {
  7. addPreviouslyRemovedConnectors();
  8. Connector connector = this.tomcat.getConnector();
  9. if (connector != null && this.autoStart) {
  10. performDeferredLoadOnStartup();
  11. }
  12. checkThatConnectorsHaveStarted();
  13. this.started = true;
  14. //在控制台打印这句日志,如果在yml设置了上下文,这里会打印
  15. logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
  16. + getContextPath() + "'");
  17. }
  18. catch (ConnectorStartFailedException ex) {
  19. stopSilently();
  20. throw ex;
  21. }
  22. catch (Exception ex) {
  23. PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
  24. throw new WebServerException("Unable to start embedded Tomcat server", ex);
  25. }
  26. finally {
  27. Context context = findContext();
  28. ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
  29. }
  30. }
  31. }

启动tomcat服务。

总结

SpringBoot的启动主要是通过实例化SpringApplication来启动,启动过程中主要做了以下几件事:

  1. 配置属性;
  2. 获取监听器;
  3. 发布应用开始启动时间初始化输入参数;
  4. 配置输入参数;
  5. 配置环境;
  6. 输出banner;
  7. 创建上下文;
  8. 预处理上下文;
  9. 刷新上下文;
  10. 再刷新上下文;
  11. 发布应用已启动事件;
  12. 发布应用启动完成事件。

而tomcat启动工作再刷新上下文这一步。而tomcat的启动主要是实例化两个租金啊:Connector,Container。一个tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个Connector和一个Container,而一个Container下又包含多个子容器。

参考文献

SpringBoot内置tomcat启动原理

https://www.cnblogs.com/sword-successful/p/11383723.html

【从入门到放弃-SpringBoot】SpringBoot源码分析-WebServer

https://developer.aliyun.com/article/704822

SpringBoot | SpringBoot的启动流程

https://www.yuque.com/ybqdren/software-dev/qff8sa