【源码分析-Spring Boot】-1.Spring Boot 的启动流程和加载原理


Spring Boot 的初步使用: 【从零开始学Spring Boot】-1.第一个Spring Boot应用

1.Spring Boot 启动流程是怎样的?

从 main 方法开始

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

SpringApplication.run -> ConfigurableApplicationContext

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置 “java.awt.headless” 属性
    configureHeadlessProperty();
    // 使用 SpringFactoryLoader 获取 SpringApplicationRunListener 实例的 listeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 逐个启动 SpringApplicationRunListener,应用开始启动事件
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 获取 Environment,根据 webType 获取不同类型;并配置 propertySource 和 profiles
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印 banner
        Banner printedBanner = printBanner(environment);
        // 创建 spring 应用上下文,类型和 webType 有关
        context = createApplicationContext();
        // 使用 SpringFactoryLoader 获取 SpringBootExceptionReporter 实例的 exceptionReporters
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                         new Class[] { ConfigurableApplicationContext.class }, context);
        // 上下文预处理,spring boot
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新上下文,spring context
        refreshContext(context);
        // 上下文后置处理,暂为空
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // listeners 启动完成事件
        listeners.started(context);
        // 触发 ApplicationRunner 和 CommandLineRunner
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 引用运行事件,开始监听
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

2.Spring Boot 如何加载 Tomcat?

SpringApplication.run -> ConfigurableApplicationContext ,跟踪 tomcat 的创建过程,主要看 createApplicationContext() 和 refreshContext() 方法

public ConfigurableApplicationContext run(String... args) {
    // ...
    try {
        // ...
        context = createApplicationContext();
        // ...
        refreshContext(context);
        // ...
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    // ...
    return context;
}

createApplicationContext() 创建 spring 应用

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

最终创建的应用类型和 webApplicationType 有关,webApplicationType 在 SpringApplication 的构造函数中进行实例化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

默认类型为 WebApplicationType.SERVLET

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

在 refreshContext 中调用了 refresh(context) 方法,这里的 applicationContext 为 AnnotationConfigServletWebServerApplicationContext

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

然后调用 AbstractApplicationContext.refresh() 方法

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        // ...

        try {
            // ...
            this.onRefresh();
            // ...
        } catch (BeansException var9) {
            // ...
        } finally {
            this.resetCommonCaches();
        }
    }
}

onRefresh() 是一个钩子方法,根据上面分析,这里使用的是 servlet,所以会调用到

ServletWebServerApplicationContext.onRefresh()

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

在 ServletWebServerApplicationContext 中调用 createWebServer() 创建 web 服务

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}

这里的 factory 通过 ServletWebServerFactory 来实例化,所以创建 ServletWebServer

最终在 TomcatServletWebServerFactory 中创建了 Tomcat、Connector、Engine、Host 等,这里可以结合 apache-tomcat 的配置文件 server.xml 来分析几者之间的层级关系

3.Spring Boot 默认容器为何是 tomcat?

先看下 spring-boot-starter-web 的依赖结构

spring-boot-starter-web 依赖了 spring-boot-starter-tomcat,又依赖了 tomcat-embed-core。但是只凭这个,并不能说明默认容器为 tomca 的原因。

要弄清这个问题,就要涉及到 Spring Boot 的自动装配,以及 WebServer 的装配。

@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
    // ...
}

其中 @EnableAutoConfiguration 和自动装配相关,@EnableAutoConfiguration 声明如下

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

@Import 导入一个 AutoConfigurationImportSelector,这个 Selector 中 selectImports() 实现如下

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

其中 getAutoConfigurationEntry 获取自动装配类型,它的实现如下

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                           AnnotationMetadata annotationMetadata) {
    // 是否开启自动装配,默认开启,可通过 spring.boot.enableautoconfiguration 进行配置
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取元注解中的属性,它是一个 LinkedHashMap
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取要自动装配类的类名
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重
    configurations = removeDuplicates(configurations);
    // 要排除的装配类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 过滤
    configurations = filter(configurations, autoConfigurationMetadata);
    // 触发自动装配导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

在 getCandidateConfigurations 获取所有自动装配类,这个方法通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中的内容,在 spring-boot-autoconfigure 的 spring.factories 中有如下内容

其中有一条配置为 EmbeddedWebServerFactoryCustomizerAutoConfiguration,这个即为内容 webServer 的自动装配类

它的实现如下

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

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

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

    }

    /**
     * Nested configuration if Jetty is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
    public static class JettyWebServerFactoryCustomizerConfiguration {

        @Bean
        public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
                                                                               ServerProperties serverProperties) {
            return new JettyWebServerFactoryCustomizer(environment, serverProperties);
        }

    }

    /**
     * Nested configuration if Undertow is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
    public static class UndertowWebServerFactoryCustomizerConfiguration {

        @Bean
        public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
                                                                                     ServerProperties serverProperties) {
            return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
        }

    }

    /**
     * Nested configuration if Netty is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(HttpServer.class)
    public static class NettyWebServerFactoryCustomizerConfiguration {

        @Bean
        public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
                                                                               ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties);
        }

    }

}

结合上面 spring-boot-starter-web 中引入了 tomcat-embed-core 依赖,可以发现,默认装配的类型即为

TomcatWebServerFactoryCustomizerConfiguration


文章作者: Soulballad
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Soulballad !
评论
 上一篇
【从零开始学Spring Boot】-2.Spring Boot ConfigurationProperties 配置 【从零开始学Spring Boot】-2.Spring Boot ConfigurationProperties 配置
1.简介1.1 概述 Annotation for externalized configuration. Add this to a class definition or a @Bean method in a @Configurati
下一篇 
【从零开始学Spring Boot】-1.第一个Spring Boot应用 【从零开始学Spring Boot】-1.第一个Spring Boot应用
1.简介1.1 概述 Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “jus
  目录