spring-boot-mvc启动流程

1、前言

在web应用开发中,我们比较常用的是spring boot+spring mvc的组合。该组合的核心点就是内嵌tomcat和mvc的dispatcherServlet,今天,我们就来探究一下其启动流程。

前置知识点:spring boot原理、spring mvc原理

源码版本:spring boot 2.1.7

2、流程

此处先给出结论,抓住主线,便于后面的学习。

核心步骤分四步:

  • 容器类型确定
  • 创建web容器
  • 启动内嵌tomcat
  • 启动DispatcherServlet

大致时序图如下:

mvn springboot run指定配置文件 mvn启动springboot_spring boot

3、容器类型

容器类型,由WebApplicationType枚举类定义:

  • web容器
  • mvc容器
  • reactive容器
  • 非web容器

SpringApplication的构造方法中设置

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
  。。。。
   //调用方法设置容器类型
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   。。。。
   this.mainApplicationClass = deduceMainApplicationClass();
}

WebApplicationType.deduceFromClasspath()获取容器类型

static WebApplicationType deduceFromClasspath() {
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
       //reactive
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
          //普通容器
         return WebApplicationType.NONE;
      }
   }
    //返回servlet容器,即mvc
   return WebApplicationType.SERVLET;
}
  • WebApplicationType.REACTIVE - 当类路径中存在REACTIVE_WEB_ENVIRONMENT_CLASS并且不存在MVC_WEB_ENVIRONMENT_CLASS时
  • WebApplicationType.NONE - 也就是非Web型应用(Standard型),此时类路径中不包含WEB_ENVIRONMENT_CLASSES中定义的任何一个类时
  • WebApplicationType.SERVLET - 类路径中包含了WEB_ENVIRONMENT_CLASSES中定义的所有类型时

4、创建web容器

在上步,我们已经确定了容器的类型,接下来就是创建容器了。核心步骤:

  • 根据类型获取定义的Class
  • 通过反射创建容器

源码如下:

**SpringApplication.run()方法中的createApplicationContext()**执行

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
	...
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
       //准备环境,确认activefile,确认配置的文件的内容全部可用,此处会准备bootstarpapplication,
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  .	  ...
      //创建容器
      context = createApplicationContext();
     ...
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //刷新容器,关键点,tomcat在这里面的onrefresh中创建
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
 	 ...
   return context;
}

createApplicationContext()方法。根据容器类型创建容器,mvc的容器为ServletWebServerApplicationContext

protected ConfigurableApplicationContext createApplicationContext() { 
   Class<?> contextClass = this.applicationContextClass;
    //根据容器type加载class
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
             //需要的servletWeb容器的Class,mvc
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
         ...
      }
   }
    //根据class创建并返回web容器
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

5、 启动tomcat

我们观察项目启动的日志,会有tomcat的相关日志。debug找到相关的类,便于确定调用堆栈。

mvn springboot run指定配置文件 mvn启动springboot_tomcat_02

Debug的调用堆栈如下:


mvn springboot run指定配置文件 mvn启动springboot_spring_03

我们跟着调用堆栈走,先看看onRefrensh()方法。

5.1、onRefresh

ServletWebServerApplicationContext实现了onRefresh的方法,该方法在AbstractApplicationContext无实现,交由子类扩展。

实现逻辑如下:

protected void onRefresh() {
   super.onRefresh();
   try {
       //创建webServer
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}
5.2、createWebServer

该方法中通过工厂获取webServer,根据不同的类型,servlet容器是tomcat。

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
       //通过工厂获取,先加载工厂,此处是tomcatServletWebServerFactory
      ServletWebServerFactory factory = getWebServerFactory();
      
       //工厂生产产品,tomcatServer
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
   }
   initPropertySources();
}
5.2、 getWebServerFactory

然后通过容器getBean获取工厂bean,如果还没有,就会创建,进入getBean---->createBean的逻辑。在获取tomcatServletWebServerFactory中,引入了DispatcherServlet

protected ServletWebServerFactory getWebServerFactory() {
    //名字是tomcatServletWebServerFactory
   String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
  ....
    //先获取工厂,然后通过getBean获取工厂bean,此处是tomcatServletWebServerFactory
   return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
5.2.1 tomcatServletWebServerFactory注入

上图,获取tomcatServletWebServerFactory工厂,肯定需要该工厂的bean定义,那么定义在哪呢?

是在ServletWebServerFactoryConfiguration.EmbeddedTomcat中注入的,源码如下:

class ServletWebServerFactoryConfiguration {

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
   public static class EmbeddedTomcat {

      //配置TomcatServletWebServerFactory的bean,用于创建tomcatServer
      @Bean
      public TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
         TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
         ......
         return factory;
      }

   }
}
5.2.2、ServletWebServerFactoryConfiguration.EmbeddedTomcat

ServletWebServerFactoryConfiguration.EmbeddedTomcat是由ServletWebServerFactoryAutoConfiguration注入的。

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
//是在auto-configuation中注入的该类
@EnableConfigurationProperties(ServerProperties.class)
//注入了tomcat的工厂
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
         //tomcat相关
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

   @Bean
   public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
      return new ServletWebServerFactoryCustomizer(serverProperties);
   }

   //tomcat的自定义工厂,
   @Bean
   @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
   public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
         ServerProperties serverProperties) {
      return new TomcatServletWebServerFactoryCustomizer(serverProperties);
   }

    //注册后置处理器
   public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

      private ConfigurableListableBeanFactory beanFactory;

      @Override
      public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
         if (beanFactory instanceof ConfigurableListableBeanFactory) {
            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
         }
      }

      //bean的后置处理器,errorPageRegistrarBeanPostProcessor最后挂到了DispatchServlet上
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
         if (this.beanFactory == null) {
            return;
         }
         registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
               WebServerFactoryCustomizerBeanPostProcessor.class);
         registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
               ErrorPageRegistrarBeanPostProcessor.class);
      }

   }

}

ServletWebServerFactoryAutoConfiguration是在auto-configuration的配置文件中注入的。

mvn springboot run指定配置文件 mvn启动springboot_tomcat_04

可以看见图片中,大多servlet相关的自动注入都在此处。

5.2.3、errorPageRegistrarBeanPostProcessor

创建tomcatServletWebServerFactory后对其进行后置处理时,用到了errorPageRegistrarBeanPostProcessor,该后置处理器初始化时最后调用了DispatchServlet

mvn springboot run指定配置文件 mvn启动springboot_源码_05

errorPageRegistrarBeanPostProcessor会获取register

private Collection<ErrorPageRegistrar> getRegistrars() {
		if (this.registrars == null) {
			// Look up does not include the parent context
			this.registrars = new ArrayList<>(
                //引入了ErrorPageRegistrar.class,关键点
					this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
			this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);
			this.registrars = Collections.unmodifiableList(this.registrars);
		}
		return this.registrars;
	}
5.2.4、ErrorPageRegistrar

唯一实现ErrorPageCustomizer,构造方法需要DispatcherServletPath的类

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

   private final ServerProperties properties;

   //DispatcherServletPath
   private final DispatcherServletPath dispatcherServletPath;

   //引入DispatcherServletPath,关键点
   protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
      this.properties = properties;
      this.dispatcherServletPath = dispatcherServletPath;
   }
}
3.2.5、DispatcherServletPath

DispatcherServletRegistrationBeanDispatcherServletPath的实现,构造方法引入了DispatcherServlet

/**
 * ServletRegistrationBean for the auto-configured DispatcherServlet. Both registers the servlet and exposes   * DispatcherServletPath information.
*/
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
      implements DispatcherServletPath {

   private final String path;

    //DispatcherServlet,关键类
   public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
      super(servlet);
      Assert.notNull(path, "Path must not be null");
      this.path = path;
      super.addUrlMappings(getServletUrlMapping());
   }

}
5.2.6 DispatcherServlet

DispatcherServlet的由DispatcherServletAutoConfiguration注入如下:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

   public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

   public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

   @Configuration(proxyBeanMethods = false)
   @Conditional(DefaultDispatcherServletCondition.class)
   @ConditionalOnClass(ServletRegistration.class)
   @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
   protected static class DispatcherServletConfiguration {

       //关键
       //注入了dispatcherServlet
      @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
      public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
         DispatcherServlet dispatcherServlet = new DispatcherServlet();
         ......
         return dispatcherServlet;
      }
   }
}

DispatcherServletAutoConfiguration也是由spring.factories的auto-configura注入

5.3、getWebServer

获取webServer,在该方法中,tomcat出场了。

public WebServer getWebServer(ServletContextInitializer... initializers) {
   if (this.disableMBeanRegistry) {
      Registry.disableRegistry();
   }
    //tomcat出场
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
    //连接器
   Connector connector = new Connector(this.protocol);
   connector.setThrowOnFailure(true);
    //service
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
    //engine
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   prepareContext(tomcat.getHost(), initializers);
    //创建tomcatwebserver
   return getTomcatWebServer(tomcat);
}
5.3.1 getTomcatWebServer

获取TomcatWebServer,原生的tomcat启动有些类似。

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   Assert.notNull(tomcat, "Tomcat Server must not be null");
   this.tomcat = tomcat;
   this.autoStart = autoStart;
    //初始化
   initialize();
}
//初始化tomcat
private void initialize() throws WebServerException {
   logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
   synchronized (this.monitor) {
      try {
         addInstanceIdToEngineName();

         Context context = findContext();
         context.addLifecycleListener((event) -> {
            if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
               // Remove service connectors so that protocol binding doesn't
               // happen when the service is started.
               removeServiceConnectors();
            }
         });

          //熟悉的地方
         // Start the server to trigger initialization listeners
         this.tomcat.start();

         // We can re-throw failure exception directly in the main thread
         rethrowDeferredStartupExceptions();

         try {
            ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
         }
         catch (NamingException ex) {
            // Naming is not enabled. Continue
         }

         // Unlike Jetty, all Tomcat threads are daemon threads. We create a
         // blocking non-daemon to stop immediate shutdown
         startDaemonAwaitThread();
      }
      catch (Exception ex) {
         stopSilently();
         destroySilently();
         throw new WebServerException("Unable to start embedded Tomcat", ex);
      }
   }
}

6、 初始化DispatcherServlet

tomcatServer启动时还没有初始化DispatcherServlet,9大组件都未初始化,等到有请求进来了,再去初始化DispatcherServletDispatcherServlet的初始化在onRefresh中:

//刷新
protected void onRefresh(ApplicationContext context) {
    //初始化
   initStrategies(context);
}

//初始化
protected void initStrategies(ApplicationContext context) {
   //9大组件
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

6.1、onRefrsh

onRefresh由父类FrameworkServletinitWebApplicationContext()调用,初始化逻辑此处不介绍。

protected WebApplicationContext initWebApplicationContext() {
   //web容器
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      ....
      }
   
   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         //关键刷新
         onRefresh(wac);
      }
   }
   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}

6.2、调用栈

具体的调用堆栈如下:

mvn springboot run指定配置文件 mvn启动springboot_spring_06

由下至上可以看出:

  • NioEndpoint接受到请求,交由Processor处理。
  • 不同的协议是不同的Processor处理。此处是Http11Processor,处理请求,封装Request对象,然后交由Engine引擎
  • Engine解析Host
  • Host确定容器
  • 容器确定Wrappper,Wrapper就是Servlet的包装。此处就是DispatchServlet
  • 然后DispatchServlet初始化并分发请求。