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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

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中,那么该类何时被调用呢?如下:

    20191017100470\_1.png

    其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容器


来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析12-servlet容器的建立

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏