SpringBoot 啟動原理

LZC發表於2020-03-20
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@SpringBootApplication

@SpringBootApplication標註在某個類,說明這個類是SpringBoot的主配置類。

檢視@SpringBootApplication註解程式碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration 
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = { 
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ......
}

@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

就是一個@Configuration

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ......
}

@EnableAutoConfiguration由下面兩個註解組成

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

用來自動配置包,透過@Import註解,指定使用AutoConfigurationPackages.Registrar.class往容器中註冊元件。

// AutoConfigurationPackages
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    ......
    // AnnotationMetadata metadata為主配置類的註解資訊
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 該方法主要是往容器中注入了一個元件, 元件儲存了主配置類所在的包名, 這個包名為自動掃描的包名
        register(registry, new PackageImport(metadata).getPackageName());
    }
    ......
}

@AutoConfigurationImportSelector

@AutoConfigurationImportSelector註解也是往容器中註冊元件。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    /**
     * 返回值就是匯入到容器中的全類名, 這些類就會被註冊到容器中
     */        
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        // 這一步會獲取到所有的自動配置類
        // 它會從類路徑中拿到所有名為META-INF/spring.factories的配置檔案,獲取EnableAutoConfiguration指定的值。
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        // 返回獲取到的自動配置類
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

例如,檢視spring-boot-autoconfigure包下的META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
......

此時SpringApplicationAdminJmxAutoConfigurationAopAutoConfiguration等元件就會被註冊到容器中。

這些XxxAutoConfiguration是用來自動往容器中注入元件的。

自動配置類

從上面的分析我們可以知道,SpringBoot會收集META-INF/spring.factories中的自動配置類,下面來分析一下自動配置類的作用。

自動配置常用註解

@Configuration :指定這個類是一個配置類

@ConditionalOnClass :當類路徑classpath下有指定的類的情況下進行自動配置

@ConditionalOnMissingClass:當類路徑下沒有指定的類的條件下

@ConditionalOnBean:當容器(Spring Context)中有指定的Bean的條件下

@ConditionalOnMissingBean:當容器(Spring Context)中沒有指定Bean的情況下進行自動配置

@ConfigurationProperties :結合相關XxxProperties來繫結相關配置, 主要用來把properties配置檔案轉化為對應的XxxProperties來使用的, 並不會把該類放入到IOC容器中

@EnableConfigurationProperties(XxxProperties.class) :註解的作用是使@ConfigurationProperties註解生效並且將XxxProperties注入到IOC容器中。

如果在每個Properties上都使用@Component來標註,那麼也不需要使用@EnableConfigurationProperties({XxxProperties.class})來標註了,同樣也可以在Spring上下文容器中也能獲取到XxxProperties對應的Bean。

@ConditionalOnProperty(prefix = “spring.person”, value = “enabled”, matchIfMissing = true)
當配置檔案中spring.person.enabled=true時進行自動配置,如果沒有設定此值就預設使用matchIfMissing對應的值。如果不設定matchIfMissing = true, 預設為false。

HttpEncodingAutoConfiguration為例,當滿足一些條件時,它會自動往ioc容器中注入元件。

// 表示這是一個配置類
@Configuration(proxyBeanMethods = false)
// 將HttpProperties中的屬性與配置檔案進行繫結, 並注入ioc容器中
@EnableConfigurationProperties(HttpProperties.class)
// web應用型別是SERVLET
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 當前類路徑不存在CharacterEncodingFilter.class
@ConditionalOnClass(CharacterEncodingFilter.class)
// 當配置檔案中spring.http.encoding.enabled=true時進行自動配置,如果沒有設定此值就預設使用matchIfMissing對應的值。如果不設定matchIfMissing = true, 預設為false。
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
    private final HttpProperties.Encoding properties;
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    @Bean
    // 容器中不存在CharacterEncodingFilter型別的bean時,這個bean才會生效
    @ConditionalOnMissingBean 
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
        return filter;
    }
}

run()

  • 準備環境

    • 執行ApplicationContextInitializer.initialize()方法
    • 監聽器回撥SpringApplicationRunListener.contextPrepared()方法
    • 載入主配置類定義資訊
    • 監聽器回撥SpringApplicationRunListener.contextLoaded()方法
  • 重新整理IOC容器

    • 掃描載入所有容器中的元件
    • 包括META-INF/spring.factories下的EnableAutoConfiguration元件
  • 回撥容器中所有的ApplicationRunner、CommandLineRunner的run方法

  • 回撥所有SpringApplicationRunListener.running(context)方法

建立SpringApplication物件

檢視啟動類的run方法,將會看到新建一個SpringApplication, 並將配置類傳遞進去

// SpringApplication.java
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

檢視建立SpringApplication物件過程

// SpringApplication.java
public SpringApplication(Class... primarySources) {
    this((ResourceLoader)null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 儲存配置類
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 獲取當前應用型別: NONE, SERVLET, REACTIVE;
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 從類路徑下尋找META-INF/spring.factories中的所有ApplicationContextInitializer並儲存起來
  this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
     // 從類路徑下尋找META-INF/spring.factories中的所有ApplicationListener並儲存起來
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 從多個配置類中找到有main方法的主配置類
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

執行SpringApplication.run()

// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    // 獲取SpringApplicationRunListener
    // 從類路徑下尋找META-INF/spring.factories中的所有SpringApplicationRunListener並儲存起來
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    // 回撥所有的SpringApplicationRunListener.starting()方法
    listeners.starting();

    Collection exceptionReporters;
    try {
        // 封裝命令列引數
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 根絕應用型別建立不同的ConfigurableEnvironment
        // 建立ConfigurableEnvironment後會回撥
        // SpringApplicationRunListener.environmentPrepared(ConfigurableEnvironment environment), 表示環境準備完成
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        // 列印控制檯Banner
        Banner printedBanner = this.printBanner(environment);
        // 建立ApplicationContext, 根據NONE,SERVLET,REACTIVE型別建立不同的ApplicationContext
        // 如果使用預設的Tomcat內嵌伺服器, web型別為SERVLET
        // SERVLET  => AnnotationConfigServletWebServerApplicationContext
        // REACTIVE => AnnotationConfigReactiveWebServerApplicationContext
        // NONE     => AnnotationConfigApplicationContext
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        // 1. 準備上下文環境
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 2. 重新整理IOC容器,
        // 載入所有容器中的元件, 
        // 包括META-INF/spring.factories下的EnableAutoConfiguration元件
        // 如果是web應用, 還會啟動web容器,後面將會分析web容器是如何啟動的
        this.refreshContext(context);
        // 這是一個空方法
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        // 回撥所有SpringApplicationRunListener.started(context)方法
        listeners.started(context);
        // 從ioc容器中獲取ApplicationRunner、CommandLineRunner, 並回撥相關方法
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        // 回撥所有SpringApplicationRunListener.running(context)方法
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

prepareContext

// SpringApplication.java
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 為ioc容器設定環境資訊
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 回撥所有 ApplicationContextInitializer.initialize(context)方法
    applyInitializers(context);
    // 回撥所有 SpringApplicationRunListener.contextPrepared(context)方法
    listeners.contextPrepared(context);
    // 列印一些日誌資訊  
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // 往ioc容器中註冊一些元件
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // 獲取配置類資訊
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0])); // 將配置類資訊註冊到容器中
    // 回撥所有SpringApplicationRunListener.contextLoaded(context)方法
    listeners.contextLoaded(context);
}

refreshContext

refreshContext方法最終會呼叫AbstractApplicationContext.refresh()方法,分析Spring原始碼的時候已經分析過該方法了,這個方法會載入所有元件到容器中並例項化,需要注意的是這個方法裡面還呼叫了onRefresh()方法,這個方法是留給子類實現的。根據前面的分析我們可以知道,若web應用型別為SERVLET,此時的應用上下文為AnnotationConfigServletWebServerApplicationContextAnnotationConfigServletWebServerApplicationContext繼承了ServletWebServerApplicationContext

ServletWebServerApplicationContext實現了AbstractApplicationContext.refresh()方法,web容器就是在這裡啟動的。

容器自動配置

SpringBoot預設使用Tomcat容器作為內嵌容器。檢視web容器的自動配置類ServletWebServerFactoryAutoConfiguration

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
// 僅在當前應用是 Servlet Web 應用時才生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 將配置檔案中字首為server的配置引數繫結到ServerProperties, 並將其匯入到容器中
@EnableConfigurationProperties(ServerProperties.class)
// 往容器中注入元件
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }
    // Tomcat 定製器,
    // WebServerFactoryCustomizerBeanPostProcessor會獲取TomcatServletWebServerFactoryCustomizer對 TomcatServletWebServerFactor 進行定製
    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    ......
}

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class

往容器中註冊了兩個BeanPostProcessor,分別是WebServerFactoryCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor,這兩個元件用來對WebServerFactory進行定製化。

1.ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
往容器中注入TomcatServletWebServerFactory
2.ServletWebServerFactoryConfiguration.EmbeddedJetty.class
往容器中注入JettyServletWebServerFactory
3.ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
往容器中注入UndertowServletWebServerFactory

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory繼承自抽象基類AbstractServletWebServerFactory,實現了介面WebServerFactoryErrorPageRegistry

這些類將分別會被WebServerFactoryCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor使用。由於程式碼比較簡單,只看WebServerFactoryCustomizerBeanPostProcessor

// WebServerFactoryCustomizerBeanPostProcessor.java
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    private ListableBeanFactory beanFactory;
    private List<WebServerFactoryCustomizer<?>> customizers;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
                "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory");
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 判斷bean的型別是否為WebServerFactory
        // 主要對 TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory進行處理定製化處理
        if (bean instanceof WebServerFactory) {
            postProcessBeforeInitialization((WebServerFactory) bean);
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @SuppressWarnings("unchecked")
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        // 對WebServerFactory進行定製化操作
        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
                .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
                .invoke((customizer) -> customizer.customize(webServerFactory));
    }
    // 獲取型別為WebServerFactoryCustomizer的元件
    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }
    // 從容器中獲取所有型別為WebServerFactoryCustomizer的元件
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
        return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }

}

建立容器

前面已經分析過,web容器的建立是在ServletWebServerApplicationContext.refresh()方法裡面。

// ServletWebServerApplicationContext.java
// AnnotationConfigServletWebServerApplicationContext繼承了ServletWebServerApplicationContext,ServletWebServerApplicationContext實現了AbstractApplicationContext.refresh()方法
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {    
    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    // 建立web服務
    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            // 1. 獲取 ServletWebServerFactory
            // 如果使用的是Tomcat, 那麼返回的是 TomcatServletWebServerFactory
            ServletWebServerFactory factory = getWebServerFactory();
            // 2. 建立web服務, 啟動Tomcat
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }
 }

1-獲取 ServletWebServerFactory

// ServletWebServerApplicationContext.java
// 獲取 ServletWebServerFactory
protected ServletWebServerFactory getWebServerFactory() {
    // 從容器中獲取型別為ServletWebServerFactory的元件名
    // 常見的ServletWebServerFactory的實現類有TomcatServletWebServerFactory、UndertowServletWebServerFactory、JettyServletWebServerFactory
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                                              + "ServletWebServerFactory bean.");
    }
    // 不能同時存在多個ServletWebServerFactory
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                                              + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    // 從容器中獲取ServletWebServerFactory例項
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

2-建立web服務

// ServletWebServerApplicationContext.java
this.webServer = factory.getWebServer(getSelfInitializer());

getSelfInitializer()

這裡需要注意的是,getSelfInitializer()這個方法返回了一個ServletContextInitializer,這裡使用了回撥的方式。

// ServletWebServerApplicationContext.java
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

ServletContainerInitializer 介面只有一個方法

// ServletContainerInitializer.java
public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

接著往下看

// 當ServletContextInitializer 類被 TomcatStarter 的#onStartup()方法呼叫時才會觸發這裡
private void selfInitialize(ServletContext servletContext) throws ServletException {
    // 1. 判斷 ServletContext 裡面是否存在 WebApplicationContext.class.getName() + ".ROOT"
    // 2. 如果不存在則往 ServletContext 裡面註冊一個根容器, 如果web型別為SERVLET,那就是註冊了一個AnnotationConfigServletWebServerApplicationContext
    prepareWebApplicationContext(servletContext);
    // 設定作用域
    registerApplicationScope(servletContext);
    // 往環境中註冊bean
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 從容器中獲取型別為ServletContextInitializer元件並執行他們的onStartup方法
    // 一般使用這個給容器動態地新增Servlet、Filter、Listener
    // 注意 ServletContextInitializer 和 servlet3.0 中的 ServletContainerInitializer長得特別像,因為SpringBoot使用內嵌web容器啟動的時候並沒有遵守 servlet 的規範,所以我們不能使用ServletContainerInitializer的來實現動態新增元件,但是可以透過實現ServletContextInitializer介面來動態新增Servlet、Filter、Listener
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

這裡主要做了兩件事情:

  1. ServletContext中注入當前的ApplicationContext,這個就相當於是一個ROOT容器。學習SpringMVC的時候我們知道,SpringMVC共有兩個ioc容器,一個是ServletContextListener 中的ROOT容器,一個是在DispatcherServlet中的WEB容器,WEB容器的parent為ROOT容器。
  2. 從ioc容器中獲取所有型別為ServletContextInitializer型別的元件,並執行他們的onStartup(ServletContext servletContext)方法。在SpringBoot中,如果想要實現動態新增ServletFilterListener元件,需要實現ServletContextInitializer介面來動態新增,例如:
@Component
public class MyServletContextInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("MyServlet",new MyServlet());
        dynamic.addMapping("/myServlet");
    }
}

跳過這裡繼續往後看就會發現,這裡返回的ServletContextInitializer 會在 TomcatStarter 的 #onStartup() 方法裡面呼叫它的ServletContextInitializer#onStartup(ServletContext servletContext),那時才會觸發這裡的selfInitialize(ServletContext servletContext) 方法。

getWebServer()

建立Tomcat

// TomcatServletWebServerFactory.java
// 這裡就是Tomcat的啟動
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 這裡傳入了 initializers(getSelfInitializer())
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
// 這裡只要為了看清楚 initializers 引數被 TomcatStarter 呼叫,因此省略了很多程式碼
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    // 省略很多程式碼........
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    // 省略......
    configureContext(context, initializersToUse);
    // 省略......
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // 建立 TomcatStarter 例項並傳入了 initializers
    TomcatStarter starter = new TomcatStarter(initializers);
}

檢視TomcatStarter.onStartup()方法

// TomcatStarter.java
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            // 這裡會觸發 前面提到的selfInitialize(ServletContext servletContext) 方法
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
        // ......
    }
}

DispatcherServlet是在自動配置類DispatcherServletAutoConfiguration中被建立的。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    // The bean name for a DispatcherServlet that will be mapped to the root URL "/"
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    // The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

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

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

    }
}

從上面的程式碼可以看出DispatcherServlet例項被封裝成DispatcherServletRegistrationBean注入到ioc容器的,而DispatcherServletRegistrationBean實現了ServletContextInitializer介面,因此最終這個DispatcherServlet會被注入到ServletContext中。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章