spring boot 源码解析12-servlet容器的建立

前言

spring boot 一般都会加入如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

加入后,就会启动一个嵌入式容器,其默认启动的是tomcat.那么他是如何启动的,我们接下来就分析下.

解析

  1. 通过之前的文章我们知道了在SpringApplication#run方法的第9步会调用AbstractApplicationContext#refresh方法,而在该方法的第5步中会调用invokeBeanFactoryPostProcessors方法,在该方法会依次调用BeanFactoryPostProcessors的postProcessBeanDefinitionRegistry 进行处理.其中ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 会依次的扫描配置类,然后进行注册.当我们加入spring-boot-starter-web 依赖时,就会加入EmbeddedServletContainerAutoConfiguration这么一个配置类.代码如下:

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication
    @Import(BeanPostProcessorsRegistrar.class)
    public class EmbeddedServletContainerAutoConfiguration {
    
    /** * Nested configuration if Tomcat is being used. */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {
    
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }
    
    }
    
    /** * Nested configuration if Jetty is being used. */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {
    
        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }
    
    }
    
    /** * Nested configuration if Undertow is being used. */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {
    
        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }
    
    }
    
    /** * Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered * via {@link ImportBeanDefinitionRegistrar} for early registration. * 在EmbeddedServletContainerAutoConfiguration自动化配置类中被导入, * 实现了BeanFactoryAware接口(BeanFactory会被自动注入进来)和ImportBeanDefinitionRegistrar接口 * (会被ConfigurationClassBeanDefinitionReader解析并注册到Spring容器中) */
    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;
            }
            // 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的bean
            // 那么就注册一个
            registerSyntheticBeanIfMissing(registry,
                    "embeddedServletContainerCustomizerBeanPostProcessor",
                    EmbeddedServletContainerCustomizerBeanPostProcessor.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);
            }
        }
    
    }
    }
    

    首先该类有@Configuration注解,因此会被ConfigurationClassPostProcessor处理,又因为有@ConditionalOnWebApplication注解.该注解如下:

        @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnWebApplicationCondition.class)
    public @interface ConditionalOnWebApplication {
    
    }

    引入了OnWebApplicationCondition,代码如下:

    class OnWebApplicationCondition extends SpringBootCondition {
    
    private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
            + "support.GenericWebApplicationContext";
    
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        // 1. 检查是否被@ConditionalOnWebApplication 注解
        boolean required = metadata
                .isAnnotated(ConditionalOnWebApplication.class.getName());
        // 2. 判断是否是WebApplication
        ConditionOutcome outcome = isWebApplication(context, metadata, required);
        if (required && !outcome.isMatch()) {
            // 3. 如果有@ConditionalOnWebApplication 注解,但是不是WebApplication环境,则返回不匹配
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        if (!required && outcome.isMatch()) {
            // 4. 如果没有被@ConditionalOnWebApplication 注解,但是是WebApplication环境,则返回不匹配
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        // 5. 如果被@ConditionalOnWebApplication 注解,并且是WebApplication环境,则返回不匹配
        return ConditionOutcome.match(outcome.getConditionMessage());
    }
    
    private ConditionOutcome isWebApplication(ConditionContext context,
            AnnotatedTypeMetadata metadata, boolean required) {
        ConditionMessage.Builder message = ConditionMessage.forCondition(
                ConditionalOnWebApplication.class, required ? "(required)" : "");
        // 1. 判断GenericWebApplicationContext是否在类路径中,如果不存在,则返回不匹配
        if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
            return ConditionOutcome
                    .noMatch(message.didNotFind("web application classes").atAll());
        }
        // 2. 容器里是否有名为session的scope,如果存在,则返回匹配
        if (context.getBeanFactory() != null) {
            String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
            if (ObjectUtils.containsElement(scopes, "session")) {
                return ConditionOutcome.match(message.foundExactly("'session' scope"));
            }
        }
        // 3. Environment是否为StandardServletEnvironment,如果是的话,则返回匹配
        if (context.getEnvironment() instanceof StandardServletEnvironment) {
            return ConditionOutcome
                    .match(message.foundExactly("StandardServletEnvironment"));
        }
        // 4. 当前ResourceLoader是否为WebApplicationContext,如果是,则返回匹配
        if (context.getResourceLoader() instanceof WebApplicationContext) {
            return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
        }
        // 5. 其他情况,返回不匹配.
        return ConditionOutcome.noMatch(message.because("not a web application"));
    }
    }

    其中getMatchOutcome是判断逻辑,做了5件事:

    1. 检查是否被@ConditionalOnWebApplication 注解
    2. 通过调用isWebApplication判断是否是Web环境,

      1. 判断GenericWebApplicationContext是否在类路径中,如果不存在,则返回不匹配
      2. 容器里是否有名为session的scope,如果存在,则返回匹配
      3. Environment是否为StandardServletEnvironment,如果是的话,则返回匹配
      4. 当前ResourceLoader是否为WebApplicationContext,如果是,则返回匹配
      5. 其他情况,返回不匹配.
    3. 如果有@ConditionalOnWebApplication 注解,但是不是WebApplication环境,则返回不匹配

    4. 如果没有被@ConditionalOnWebApplication 注解,但是是WebApplication环境,则返回不匹配
    5. 如果被@ConditionalOnWebApplication 注解,并且是WebApplication环境,则返回不匹配

    那么该类是在什么时候调用的呢?调用链如下:

    org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry-->
    org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions-->
    org.springframework.context.annotation.ConfigurationClassParser#parse-->
    org.springframework.context.annotation.ConfigurationClassParser#parse-->
    org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass-->
    org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass-->
    org.springframework.context.annotation.ConditionEvaluator#shouldSkip-->
    org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches-->
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition#getMatchOutcome 

    接下来就开始对该配置类进行解析了,通过上篇文章可以知道,会首先处理内部类,那么由于该类有4个内部类.如下:

    EmbeddedTomcat
    EmbeddedJetty
    EmbeddedUndertow
    BeanPostProcessorsRegistrar

    其中BeanPostProcessorsRegistrar 没有@Configuration注解,因此该类不会被处理.

    还是由之前可知,会因此判断该类是否能够被扫描,那么它们能够被扫描的条件分别为:

    1. EmbeddedTomcat –> 当前类路径下存在 Servlet.class, Tomcat.class,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.
    2. EmbeddedJetty –> 当前类路径下存在 Servlet.class, Server.class, Loader.class,WebAppContext.class ,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.
    3. EmbeddedUndertow–> 当前类路径下存在 Servlet.class, Undertow.class, SslClientAuthMode.class ,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.

    那么当我们加入spring-boot-starter-web依赖时,默认加入了spring-boot-starter-tomcat 依赖,因此 EmbeddedTomcat 会被扫描处理.

    还是由之前可知,会依次扫描EmbeddedTomcat 中被@Bean 注解的方法,因此tomcatEmbeddedServletContainerFactory方法会被处理,向BeanDefinitionRegistry注册一个id 为tomcatEmbeddedServletContainerFactory class 为 TomcatEmbeddedServletContainerFactory的bean.

    还是由之前的内容可知,当EmbeddedServletContainerAutoConfiguration中的内部类处理完后,接下来会处理@Import 注解, 会依次扫描@Import所引入的类,由于此时只有一个–>BeanPostProcessorsRegistrar, 由于此类是ImportBeanDefinitionRegistrar的子类,因此为加入到ConfigurationClass的ImportBeanDefinitionRegistrar中,那么该类何时被调用呢?如下:

    《spring boot 源码解析12-servlet容器的建立》

    其registerBeanDefinitions代码如下:

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            // 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的bean
            // 那么就注册一个
            registerSyntheticBeanIfMissing(registry,
                    "embeddedServletContainerCustomizerBeanPostProcessor",
                    EmbeddedServletContainerCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry,
                    "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }
    1. 如果beanFactory等于null,则直接return.此处是不会发生的.因为在实例化时,就会向其注入beanFactory,因为其实现了BeanFactoryAware.
    2. 向容器注册id 为 embeddedServletContainerCustomizerBeanPostProcessor,类型为EmbeddedServletContainerCustomizerBeanPostProcessor的bean
    3. 向容器注册id 为 errorPageRegistrarBeanPostProcessor,类型为ErrorPageRegistrarBeanPostProcessor的bean.
  2. 还是在org.springframework.context.support.AbstractApplicationContext#refresh中的第9步,会调用onRefresh 这个扩展点,此时调用的是org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh,如下:

    protected void onRefresh() {
        super.onRefresh();
        try {
            createEmbeddedServletContainer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start embedded container",
                    ex);
        }
    }
    

    其中调用createEmbeddedServletContainer 进行创建容器.如下:

    private void createEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        // 1. 获得ServletContext
        ServletContext localServletContext = getServletContext();
        if (localContainer == null && localServletContext == null) { // 2 内置Servlet容器和ServletContext都还没初始化的时候执行
            // 2.1 获取自动加载的工厂
            EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
            // 2.2 获取Servlet初始化器并创建Servlet容器,依次调用Servlet初始化器中的onStartup方法
            this.embeddedServletContainer = containerFactory
                    .getEmbeddedServletContainer(getSelfInitializer());
        }
        else if (localServletContext != null) { // 3. 内置Servlet容器已经初始化但是ServletContext还没初始化,则进行初始化.一般不会到这里
            try {
                getSelfInitializer().onStartup(localServletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
        // 4. 初始化PropertySources
        initPropertySources();
    }

    4件事

    1. 获得ServletContext
    2. 如果内置Servlet容器和ServletContext都还没初始化

      1. 获取EmbeddedServletContainerFactory. 此时获得的是TomcatEmbeddedServletContainerFactory
      2. 获取Servlet初始化器并创建Servlet容器,依次调用Servlet初始化器中的onStartup方法
    3. 内置Servlet容器已经初始化但是ServletContext还没初始化,则进行初始化.一般不会到这里
    4. 初始化PropertySources,最终调用WebApplicationContextUtils#initServletPropertySources.代码如下:

          public static void initServletPropertySources(
          MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {
      
      Assert.notNull(propertySources, "'propertySources' must not be null");
      if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
              propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
          propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
                  new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
      }
      if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
              propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
          propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
                  new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
      }
      }

    其中2.1 获得TomcatEmbeddedServletContainerFactory方法如下:

    protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
        // Use bean names so that we don't consider the hierarchy
        // 1. 从BeanFactory中获得EmbeddedServletContainerFactory 类型的容器
        String[] beanNames = getBeanFactory()
                .getBeanNamesForType(EmbeddedServletContainerFactory.class);
        // 2. 如果没有的话,或者如果有大于1个的话,抛出异常
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to missing "
                            + "EmbeddedServletContainerFactory bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to multiple "
                            + "EmbeddedServletContainerFactory beans : "
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        // 3. 获得实例
        return getBeanFactory().getBean(beanNames[0],
                EmbeddedServletContainerFactory.class);
    }
    

    最终调用了AbstractBeanFactory#getBean,该bean触发了bean的实例化,在实例化的过程中,会触发一系列的扩展点的调用.其中,在AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization中,会调用一系列的BeanPostProcessor.在当前场景有12个,如下:

    org.springframework.context.support.ApplicationContextAwareProcessor,
    org.springframework.boot.context.embedded.WebApplicationContextServletContextAwareProcessor, 
    org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor,
    org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker, 
    org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, 
    org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor, 
    org.springframework.boot.web.servlet.ErrorPageRegistrarBeanPostProcessor, 
    org.springframework.context.annotation.CommonAnnotationBeanPostProcessor, 
    org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor, 
    org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor, 
    org.springframework.context.support.ApplicationListenerDetector

    其中真正发挥作用的有EmbeddedServletContainerCustomizerBeanPostProcessor,ErrorPageRegistrarBeanPostProcessor.其实现分别如下:

    EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization

    代码如下:

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        // 在Spring容器中寻找ConfigurableEmbeddedServletContainer类型的bean,SpringBoot内部的3种内置Servlet容器工厂都实现了这个接口,该接口的作用就是进行Servlet容器的配置
        // 比如添加Servlet初始化器addInitializers、添加错误页addErrorPages、设置session超时时间setSessionTimeout、设置端口setPort等等
        if (bean instanceof ConfigurableEmbeddedServletContainer) {
            postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
        }
        return bean;
    }

    调用

    private void postProcessBeforeInitialization(
            ConfigurableEmbeddedServletContainer bean) {
        for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
            customizer.customize(bean);
        }
    }

    通过beanFactory 获得EmbeddedServletContainerCustomizer类型的bean,对于当前场景,有4个.如下:

    org.springframework.boot.autoconfigure.websocket.TomcatWebSocketContainerCustomizer,
    org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer, 
    org.springframework.boot.autoconfigure.web.ServerProperties,
    org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration$DuplicateServerPropertiesDetector

    TomcatWebSocketContainerCustomizer#customize

    代码如下:

    public void customize(ConfigurableEmbeddedServletContainer container) {
        if (getContainerType().isAssignableFrom(container.getClass())) {
            doCustomize((T) container);
        }
    }

    调用

    public void doCustomize(TomcatEmbeddedServletContainerFactory tomcatContainer) {
        tomcatContainer.addContextCustomizers(new TomcatContextCustomizer() {
            @Override
            public void customize(Context context) {
                addListener(context, findListenerType());
            }
        });
    }

    向tomcatContainer 添加了2个ApplicationListener:

    1. org.apache.tomcat.websocket.server.WsContextListener
    2. org.apache.catalina.deploy.ApplicationListener(tomcat7)或者org.apache.tomcat.util.descriptor.web.ApplicationListener(tomcat8)

    LocaleCharsetMappingsCustomizer#customize

    代码如下:

    public void customize(ConfigurableEmbeddedServletContainer container) {
            if (this.properties.getMapping() != null) {
                container.setLocaleCharsetMappings(this.properties.getMapping());
            }
        }

    没有执行

    ServerProperties#customize

    public void customize(ConfigurableEmbeddedServletContainer container) {
        if (getPort() != null) {
            container.setPort(getPort());
        }
        if (getAddress() != null) {
            container.setAddress(getAddress());
        }
        if (getContextPath() != null) {
            container.setContextPath(getContextPath());
        }
        if (getDisplayName() != null) {
            container.setDisplayName(getDisplayName());
        }
        if (getSession().getTimeout() != null) {
            container.setSessionTimeout(getSession().getTimeout());
        }
        container.setPersistSession(getSession().isPersistent());
        container.setSessionStoreDir(getSession().getStoreDir());
        if (getSsl() != null) {
            container.setSsl(getSsl());
        }
        if (getJspServlet() != null) {
            container.setJspServlet(getJspServlet());
        }
        if (getCompression() != null) {
            container.setCompression(getCompression());
        }
        container.setServerHeader(getServerHeader());
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            getTomcat().customizeTomcat(this,
                    (TomcatEmbeddedServletContainerFactory) container);
        }
        if (container instanceof JettyEmbeddedServletContainerFactory) {
            getJetty().customizeJetty(this,
                    (JettyEmbeddedServletContainerFactory) container);
        }
    
        if (container instanceof UndertowEmbeddedServletContainerFactory) {
            getUndertow().customizeUndertow(this,
                    (UndertowEmbeddedServletContainerFactory) container);
        }
        // 添加SessionConfiguringInitializer这个Servlet初始化器
        // SessionConfiguringInitializer初始化器的作用是基于ServerProperties的内部静态类Session设置Servlet中session和cookie的配置
        container.addInitializers(new SessionConfiguringInitializer(this.session));
        // 添加InitParameterConfiguringServletContextInitializer初始化器
        // InitParameterConfiguringServletContextInitializer初始化器的作用是基于ServerProperties的contextParameters配置设置到ServletContext的init param中
        container.addInitializers(new InitParameterConfiguringServletContextInitializer(
                getContextParameters()));
    }
    1. 设置属性
    2. 添加SessionConfiguringInitializer这个Servlet初始化器,SessionConfiguringInitializer初始化器的作用是基于ServerProperties的内部静态类Session设置Servlet中session和cookie的配置。
    3. 添加InitParameterConfiguringServletContextInitializer初始化器,InitParameterConfiguringServletContextInitializer初始化器的作用是基于ServerProperties的contextParameters配置设置到ServletContext的init param中

    那么有个问题,这些属性是何时注入的呢? 在调用org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory,获得EmbeddedServletContainerFactory时,会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization,进行扩展点的执行,其中EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization执行时,会触发 加载 EmbeddedServletContainerCustomizer 类型的bean, ServerProperties实现了EmbeddedServletContainerCustomizer接口,因此会在此时被加载.
    同样在加载过程中,会调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization.因此会触发ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization的调用,由于该类有@ConfigurationProperties 注解,因此会最终调用org.springframework.boot.bind.PropertiesConfigurationFactory#bindPropertiesToTarget,其后就开始真正的属性绑定,调用链如下:

    org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget()-->
    org.springframework.validation.DataBinder.bind(PropertyValues)-->
    org.springframework.validation.DataBinder.doBind(MutablePropertyValues)-->
    org.springframework.boot.bind.RelaxedDataBinder.doBind(MutablePropertyValues)-->
    org.springframework.validation.DataBinder.doBind(MutablePropertyValues)-->
    org.springframework.validation.DataBinder.applyPropertyValues(MutablePropertyValues)-->
    org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(PropertyValues, boolean, boolean)-->
    org.springframework.boot.bind.RelaxedDataBinder.RelaxedBeanWrapper.setPropertyValue(PropertyValue)-->
    org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(PropertyValue)-->
    org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(PropertyTokenHolder, PropertyValue)-->
    org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(PropertyTokenHolder, PropertyValue)-->
    org.springframework.beans.BeanWrapperImpl.BeanPropertyHandler.setValue(Object, Object)-->
    java.lang.reflect.Method.invoke(Object, Object...)-->
    org.springframework.boot.autoconfigure.web.ServerProperties.setPort(Integer)    

    DuplicateServerPropertiesDetector#customize

    代码如下:

        public void customize(ConfigurableEmbeddedServletContainer container) {
            // ServerProperties handles customization, this just checks we only have
            // a single bean
            String[] serverPropertiesBeans = this.applicationContext
                    .getBeanNamesForType(ServerProperties.class);
            Assert.state(serverPropertiesBeans.length == 1,
                    "Multiple ServerProperties beans registered " + StringUtils
                            .arrayToCommaDelimitedString(serverPropertiesBeans));
        }

    只是做检查,ServerProperties类型的bean 是否为1个

    ErrorPageRegistrarBeanPostProcessor#postProcessBeforeInitialization

    代码如下:

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof ErrorPageRegistry) {
            postProcessBeforeInitialization((ErrorPageRegistry) bean);
        }
        return bean;
    }

    调用

    private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
        for (ErrorPageRegistrar registrar : getRegistrars()) {
            registrar.registerErrorPages(registry);
        }
    }

    在此处获得类型为ErrorPageRegistrar的bean,然后依次调用ErrorPageRegistry#registerErrorPages进行注册,默认情况下,只有ErrorPageCustomizer一个.
    代码如下:

        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
                    + this.properties.getError().getPath());
            errorPageRegistry.addErrorPages(errorPage);
        }

    path为/error

  3. 接下来我们分析创建容器的第2.2步. TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer代码如下:

    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        // 1. 实例化Tomcat
        Tomcat tomcat = new Tomcat();
        // 2. 设置临时目录
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        // 3. 添加Connector
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        // 4. 个性化设置
        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 getTomcatEmbeddedServletContainer(tomcat);
    }

    8件事:

    1. 实例化Tomcat
    2. 设置临时目录
    3. 添加Connector,protocol 为 org.apache.coyote.http11.Http11NioProtocol
    4. 个性化设置.代码如下:

      protected void customizeConnector(Connector connector) {
      int port = (getPort() >= 0 ? getPort() : 0);
      connector.setPort(port);
      if (StringUtils.hasText(this.getServerHeader())) {
          connector.setAttribute("server", this.getServerHeader());
      }
      if (connector.getProtocolHandler() instanceof AbstractProtocol) {
          customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
      }
      if (getUriEncoding() != null) {
          connector.setURIEncoding(getUriEncoding().name());
      }
      
      // If ApplicationContext is slow to start we want Tomcat not to bind to the socket
      // prematurely...
      connector.setProperty("bindOnInit", "false");
      
      if (getSsl() != null && getSsl().isEnabled()) {
          customizeSsl(connector);
      }
      if (getCompression() != null && getCompression().getEnabled()) {
          customizeCompression(connector);
      }
      for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
          customizer.customize(connector);
      }
      }
      1. 设置端口号 默认是 8080
      2. 设置server
      3. 此时ProtocolHandler 为Http11NioProtocol,是AbstractProtocol 的子类,因此会执行该步骤,设置地址.
      4. 设置编码,默认是 UTF-8
      5. 如果配置了ssl,则进行ssl的设置,默认情况下不会执行
      6. 如果配置了压缩,则进行压缩的配置,默认不会执行
      7. 遍历tomcatConnectorCustomizers,进行个性化配置,默认是不存在的
    5. 配置引擎
    6. 添加Connector,一般情况下是没有的
    7. 准备上下文
    8. 实例化TomcatEmbeddedServletContainer
  4. 接着在org.springframework.context.support.AbstractApplicationContext#refresh中的第12步.会执行如下代码:

    protected void finishRefresh() {
        super.finishRefresh();
        EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
        if (localContainer != null) {
            // 发布EmbeddedServletContainerInitializedEvent事件
            publishEvent(
                    new EmbeddedServletContainerInitializedEvent(this, localContainer));
        }
    }

    3件事:

    1. 调用AbstractApplicationContext#finishRefresh
    2. 启动容器
    3. 发布EmbeddedServletContainerInitializedEvent事件

    其中第2步,代码如下:

    private EmbeddedServletContainer startEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        if (localContainer != null) {
            localContainer.start();
        }
        return localContainer;
    }

    最终执行TomcatEmbeddedServletContainer#start,代码如下:

    public void start() throws EmbeddedServletContainerException {
        synchronized (this.monitor) {
            if (this.started) {
                return;
            }
            try {
                addPreviouslyRemovedConnectors();
                Connector connector = this.tomcat.getConnector();
                if (connector != null && this.autoStart) {
                    startConnector(connector);
                }
                checkThatConnectorsHaveStarted();
                this.started = true;
                TomcatEmbeddedServletContainer.logger
                        .info("Tomcat started on port(s): " + getPortsDescription(true));
            }
            catch (ConnectorStartFailedException ex) {
                stopSilently();
                throw ex;
            }
            catch (Exception ex) {
                throw new EmbeddedServletContainerException(
                        "Unable to start embedded Tomcat servlet container", ex);
            }
            finally {
                Context context = findContext();
                ContextBindings.unbindClassLoader(context, getNamingToken(context),
                        getClass().getClassLoader());
            }
        }
    }

    启动后,会打印如下日志:

    Tomcat started on port(s):8080 (http)

    第3步,对EmbeddedServletContainerInitializedEvent 感兴趣的Listener 有如下2个:

    org.springframework.boot.context.config.DelegatingApplicationListener,
    org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

    其中DelegatingApplicationListener 没有做任何事.

    ServerPortInfoApplicationContextInitializer#onApplicationEvent.代码如下:

        protected void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
        String propertyName = getPropertyName(event.getApplicationContext());
        setPortProperty(event.getApplicationContext(), propertyName,
                event.getEmbeddedServletContainer().getPort());
    }
    1. 生成属性名–>local.server.port
    2. 向environment 添加了一个名为server.ports,值为配置的端口号的MapPropertySource.

参考链接

SpringBoot源码分析之内置Servlet容器

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/78917684
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞