前言

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. 其他情况,返回不匹配.
  1. 如果有@ConditionalOnWebApplication 注解,但是不是WebApplication环境,则返回不匹配
  2. 如果没有被@ConditionalOnWebApplication 注解,但是是WebApplication环境,则返回不匹配
  3. 如果被@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中,那么该类何时被调用呢?如下:

springboot 创建model springboot 创建容器_源码

其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.
  1. 还是在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方法
  1. 内置Servlet容器已经初始化但是ServletContext还没初始化,则进行初始化.一般不会到这里
  2. 初始化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

  1. 接下来我们分析创建容器的第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,进行个性化配置,默认是不存在的
  1. 配置引擎
  2. 添加Connector,一般情况下是没有的
  3. 准备上下文
  4. 实例化TomcatEmbeddedServletContainer
  1. 接着在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容器