springboot 源码分析 内嵌式 tomcat 启动

springboot版本2.1.6

研究一下springboot 是如何启动内嵌的tomcat 的

首先找到springboot 自动配置时引入的类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

这里只截取了tomcat 相关的

首先看EmbeddedWebServerFactoryCustomizerAutoConfiguration.class

这个类做了几件事情,

1 引入ServerProperties 相当于加载配置文件中server相关的配置

2 注册TomcatWebServerFactoryCustomizer 类,这个customizer将来会将ServerProperties中的配置加载进 Tomcat

EmbeddedWebServerFactoryCustomizerAutoConfiguration.class
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

   /**
    * Nested configuration if Tomcat is being used.
    */
   @Configuration
   @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
   public static class TomcatWebServerFactoryCustomizerConfiguration {

      @Bean
      public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
            ServerProperties serverProperties) {
         return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
      }

   }
}   

看一下ServerProperties ,配置文件中server开头的配置都会加载到这里

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

在看一下TomcatWebServerFactoryCustomizer,这个customize 会将ServerProperties 中的部分配置添加进创建 tomcat 的 factory,工厂创建服务器的时候会使用这些 properties , 注意通过断点发现, 系统中相关的 Customizer 共有5个,

public class TomcatWebServerFactoryCustomizer
      implements WebServerFactoryCustomizer, Ordered { 
      
    private final Environment environment;

    private final ServerProperties serverProperties;
    
    public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
       this.environment = environment;
       this.serverProperties = serverProperties;
    }
          @Override
    public void customize(ConfigurableTomcatWebServerFactory factory) {
       ServerProperties properties = this.serverProperties;
       ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
       PropertyMapper propertyMapper = PropertyMapper.get();
       propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
       propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds)
             .as(Long::intValue).to(factory::setBackgroundProcessorDelay);
       customizeRemoteIpValve(factory);
       propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive)
             .to((maxThreads) -> customizeMaxThreads(factory, tomcatProperties.getMaxThreads()));
       propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive)
             .to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
       propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes)
             .when(this::isPositive)
             .to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize));
       propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
             .to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
       propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes)
             .when((maxHttpPostSize) -> maxHttpPostSize != 0)
             .to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory, maxHttpPostSize));
       propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled)
             .to((enabled) -> customizeAccessLog(factory));
       propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
       propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
             .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
       propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
             .to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
       propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
             .to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
       customizeStaticResources(factory);
       customizeErrorReportValve(properties.getError(), factory);
    }
}

再回过头来看一下 ServletWebServerFactoryAutoConfiguration

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

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

   @Bean
   @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
   public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
         ServerProperties serverProperties) {
      return new TomcatServletWebServerFactoryCustomizer(serverProperties);
   }

   /**
    * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
    * {@link ImportBeanDefinitionRegistrar} for early registration.
    */
   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;
         }
      }

      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
         if (this.beanFactory == null) {
            return;
         }
         registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
               WebServerFactoryCustomizerBeanPostProcessor.class);
         registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
               ErrorPageRegistrarBeanPostProcessor.class);
      }

      private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class beanClass) {
         if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(name, beanDefinition);
         }
      }
   }
}

这个类主要干了三件事

1 引入ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class

2 引入TomcatServletWebServerFactory

3 注册ServletWebServerFactoryCustomizer

4 注册TomcatServletWebServerFactoryCustomizer

 

这个BeanPostProcessorsRegistrar主要作用是向容器中添加初始化器, 其中WebServerFactoryCustomizerBeanPostProcessor 比较重要, 他是tomcatserver 容器创建时进行后置处理的处理器, 通过他, 多个Customizer得以执行, 比如前边提到的 TomcatWebServerFactoryCustomizer , 这个类中的 ServletWebServerFactoryCustomizer,TomcatServletWebServerFactoryCustomizer 等, 创建WebServerFactory时 一共有5个 Customizer 需要执行 , 他们都对WebServerFactory 进行了增强操作;

 

TomcatServletWebServerFactory 是WebServerFactory 的一个实现, 当 tomcat 作为内嵌容器时加载到容器, 他实际上是通过EmbeddedTomcat 引入的;

 

接下来看一下内嵌 tomcat 的启动过程

容器的启动在 上下文的刷新过程中, 具体在 ServletWebServerApplicationContext 的 onRefresh 方法

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

createWebServer 就是创建 tomcat 容器的方法;

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      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();
}

ServletWebServerFactory factory = getWebServerFactory(); 这里获取ServletWebServerFactory 的实现,实际上是TomcatServletWebServerFactory 对象,这个获取factory 的过程包含着初始化的动作,也就是factory 被 WebServerFactoryCustomizerBeanPostProcessor 加强过,已经完成了各种初始化配置。

this.webServer = factory.getWebServer(getSelfInitializer()); getSelfInitializer() 方法返回的是一个初始化器,在tomcat启动时会执行初始化器的onStartup方法,刚开始看的时候,这个 this::selfInitialize 有点儿难以理解,实际上相当于定义了一个onStartup方法 ,这是一个关键点。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
   return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

具体看一下 getWebServer 这个方法。

来到TomcatServletWebServerFactory 类

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}

这里直接创建了一个tomcat 对象,然后进行配置

prepareContext方法开始处理上下文,看代码

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
   File documentRoot = getValidDocumentRoot();
   TomcatEmbeddedContext context = new TomcatEmbeddedContext();
   if (documentRoot != null) {
      context.setResources(new LoaderHidingResourceRoot(context));
   }
   context.setName(getContextPath());
   context.setDisplayName(getDisplayName());
   context.setPath(getContextPath());
   File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
   context.setDocBase(docBase.getAbsolutePath());
   context.addLifecycleListener(new FixContextListener());
   context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
         : ClassUtils.getDefaultClassLoader());
   resetDefaultLocaleMapping(context);
   addLocaleMappings(context);
   context.setUseRelativeRedirects(false);
   try {
      context.setCreateUploadTargets(true);
   }
   catch (NoSuchMethodError ex) {
      // Tomcat is < 8.5.39. Continue.
   }
   configureTldSkipPatterns(context);
   WebappLoader loader = new WebappLoader(context.getParentClassLoader());
   loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
   loader.setDelegate(true);
   context.setLoader(loader);
   if (isRegisterDefaultServlet()) {
      addDefaultServlet(context);
   }
   if (shouldRegisterJspServlet()) {
      addJspServlet(context);
      addJasperInitializer(context);
   }
   context.addLifecycleListener(new StaticResourceConfigurer(context));
   ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
   host.addChild(context);
   configureContext(context, initializersToUse);
   postProcessContext(context);
}

这里创建了TomcatEmbeddedContext 对象,这个对象可以看做内嵌容器的上下文对象,随后将其添加进Host,prepareContext方法和 configureContext方法都在对其进行配置。

看下configureContext 的代码

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
   TomcatStarter starter = new TomcatStarter(initializers);
   if (context instanceof TomcatEmbeddedContext) {
      TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
      embeddedContext.setStarter(starter);
      embeddedContext.setFailCtxIfServletStartFails(true);
   }
   context.addServletContainerInitializer(starter, NO_CLASSES);
   for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
      context.addLifecycleListener(lifecycleListener);
   }
   for (Valve valve : this.contextValves) {
      context.getPipeline().addValve(valve);
   }
   for (ErrorPage errorPage : getErrorPages()) {
      new TomcatErrorPage(errorPage).addToContext(context);
   }
   for (MimeMappings.Mapping mapping : getMimeMappings()) {
      context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
   }
   configureSession(context);
   new DisableReferenceClearingContextCustomizer().customize(context);
   for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
      customizer.customize(context);
   }
}

TomcatStarter 是一个 服务器初始化器,当tomcat 启动是会调用他的onStartup方法, 进而调用了ServletContextInitializer类型的初始化器,联想之前的 getSelfInitializer() 方法。TomcatStarter 将ServletContextInitializer与ServletContainerInitializer连接起来,这里用到了桥接模式。

 

回到getWebServer方法

在prepareContext 结束后调用了getTomcatWebServer(tomcat)方法,这里就是创建并返回TomcatWebServer的地方。

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   Assert.notNull(tomcat, "Tomcat Server must not be null");
   this.tomcat = tomcat;
   this.autoStart = autoStart;
   initialize();
}

initialize();中进行了大量的初始化

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   Assert.notNull(tomcat, "Tomcat Server must not be null");
   this.tomcat = tomcat;
   this.autoStart = autoStart;
   initialize();
}

观察initialize();

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);
      }
   }
}

可以看到this.tomcat.start(); 这一句执行了tomcat 的启动,至此,到了此次源码分析的末尾。

 

之前埋了个坑,tomcat 在初始化时会通过桥接执行ServletContextInitializer类型的初始化器,那么这个初始化器做了什么工作呢?

private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

prepareWebApplicationContext 方法创建WebApplicationContext 上下文跟对象,并设置为其设置ServletContext属性。

registerApplicationScope(servletContext); 设置scope等;

 

WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

重点来了这个方法翻译过来是注册环境对象,主要的工作就是将servlet容器中的一些属性参数注入到springboot 容器中。观察WebApplicationContextUtils中的方法。

public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf,
      @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {

   if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
      bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
   }

   if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
      bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
   }

   if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
      Map parameterMap = new HashMap<>();
      if (servletContext != null) {
         Enumeration paramNameEnum = servletContext.getInitParameterNames();
         while (paramNameEnum.hasMoreElements()) {
            String paramName = (String) paramNameEnum.nextElement();
            parameterMap.put(paramName, servletContext.getInitParameter(paramName));
         }
      }
      if (servletConfig != null) {
         Enumeration paramNameEnum = servletConfig.getInitParameterNames();
         while (paramNameEnum.hasMoreElements()) {
            String paramName = (String) paramNameEnum.nextElement();
            parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
         }
      }
      bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
            Collections.unmodifiableMap(parameterMap));
   }

   if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
      Map attributeMap = new HashMap<>();
      if (servletContext != null) {
         Enumeration attrNameEnum = servletContext.getAttributeNames();
         while (attrNameEnum.hasMoreElements()) {
            String attrName = (String) attrNameEnum.nextElement();
            attributeMap.put(attrName, servletContext.getAttribute(attrName));
         }
      }
      bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
            Collections.unmodifiableMap(attributeMap));
   }
}

下一个重点,

for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
   beans.onStartup(servletContext);
}

这个getServletContextInitializerBeans中包含了很多东西

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
      Class... initializerTypes) {
   this.initializers = new LinkedMultiValueMap<>();
   this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
         : Collections.singletonList(ServletContextInitializer.class);
   addServletContextInitializerBeans(beanFactory);
   addAdaptableBeans(beanFactory);
   List sortedInitializers = this.initializers.values().stream()
         .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
         .collect(Collectors.toList());
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
   logMappings(this.initializers);
}

首先观察addServletContextInitializerBeans 方法

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
   for (Class initializerType : this.initializerTypes) {
      for (Entry initializerBean : getOrderedBeansOfType(beanFactory,
            initializerType)) {
         addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
      }
   }
}
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
      ListableBeanFactory beanFactory) {
   if (initializer instanceof ServletRegistrationBean) {
      Servlet source = ((ServletRegistrationBean) initializer).getServlet();
      addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
   }
   else if (initializer instanceof FilterRegistrationBean) {
      Filter source = ((FilterRegistrationBean) initializer).getFilter();
      addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
   }
   else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
      String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
      addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
   }
   else if (initializer instanceof ServletListenerRegistrationBean) {
      EventListener source = ((ServletListenerRegistrationBean) initializer).getListener();
      addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
   }
   else {
      addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
            initializer);
   }
}
private void addServletContextInitializerBean(Class type, String beanName, ServletContextInitializer initializer,
      ListableBeanFactory beanFactory, Object source) {
   this.initializers.add(type, initializer);
   if (source != null) {
      // Mark the underlying source as seen in case it wraps an existing bean
      this.seen.add(source);
   }
   if (logger.isTraceEnabled()) {
      String resourceDescription = getResourceDescription(beanName, beanFactory);
      int order = getOrder(initializer);
      logger.trace("Added existing " + type.getSimpleName() + " initializer bean '" + beanName + "'; order="
            + order + ", resource=" + resourceDescription);
   }
}

从容器中分别获取 Servlet ,Filter,EventListener等类型的初始化器,之后循环调用中执行onStartup方法,将spring容器中的 Filter 等注入到 servlet 容器中。至此,springboot 内嵌式tomcat 启动过程的源码分析全部结束,虽然流程上很熟悉了,但是很多细节还需要好好的消化吸收,很多设计的思想还要反复琢磨,革命尚未成功,同志仍需努力。

 

 

你可能感兴趣的:(java,springboot)