首页 > 编程笔记

Spring Boot启动流程

本文讲解有哪些 Spring Boot 的运行原理,启动一个 Spring Boot 工程都是从 SpringApplication.run() 方法开始的。这个方法具体完成了哪些工作?@SpringBootApplication 注解的作用是什么?

SpringApplication 启动类

通过查看 SpringApplication.run() 方法的源码可以看到,该方法首先生成 SpringApplication 的一个实例,然后调用实例的 run() 方法。下面来看一下SpringApplication 构造函数的源码:
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));
        this.webApplicationType = WebApplicationType.deduceFromClasspath(); // ③
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.calss)); //④
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));    //⑤
        this.mainApplicationClass = this.deduceMainApplicationClass();
}

具体配置代码如下:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplication
ContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplication
ContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplication
ContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplication
ContextInitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPost
Processor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplication
Listener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplication
Listener

通过上面的构造函数可以看到,SpringApplication 类的主要的工作是确定 Web 应用类型、加载上下文初始化器及监听器等。

接下来看重要的部分,即 SpringApplication 实例的 run() 方法,具体源代码如下:
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters =new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);    //①
        listeners.starting();
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);    //②
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();     //③
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners,applicationArguments, printedBanner);      //④
            this.refreshContext(context);     //⑤
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);    //⑥
            this.callRunners(context, applicationArguments);        //⑦
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters,listeners);
            throw new IllegalStateException(var10);
        }
        try {
            listeners.running(context);    //⑧
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters,(SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
}


通过分析源代码,总结出 Spring Boot 启动的主要流程如图 1 所示。

图1 Spring Boot的启动流程
图1 Spring Boot的启动流程

通过分析源代码还可以发现,事件监听是 Spring 框架中重要的一部分。Spring 提供了多种类型的事件,常用的如表 1 所示。

表1 Spring中常用的事件
事  件 说  明
ApplicationStartingEvent 应用启动事件
ApplicationEnvironmentPreparedEvent 环境准备事件
ApplicationContextInitializedEvent 上下文初始化事件
ApplicationPreparedEvent 应用上下文准备时触发事件
ApplicationStartedEvent 应用启动事件,在刷新上下文之后触发
ApplicationReadyEvent 应用上下文准备完成时触发事件
ApplicationFailedEvent 应用启动失败事件
WebServerInitializedEvent WebServer初始化事件
ContextRefreshedEvent 上下文刷新事件

@SpringBootApplication注解

在 Spring Boot 的入口类中,有一个重要的注解 @SpringBootApplication,下面我们就对该注解进行分析。
首先查看 @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 = AutoConfiguration
ExcludeFilter.class) })
public @interface SpringBootApplication {
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
        String[] scanBasePackages() default {};
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};
        @AliasFor(annotation = Configuration.class)
        boolean proxyBeanMethods() default true;
}
通过源码可以看到,@SpringBootApplication 是一个复合注解,包括 @ComponentScan、@EnableAutoConfiguration 和@SpringBootConfiguration 等。

下面具体分析这 3 个注解:

1) @ComponentScan注解

在 Bean 的注解,如@Service、@Repository、@Component 和 @Controller 等。@ComponentScan 注解可以自动扫描被以上注解描述的 Bean 并将其加载到 IoC 容器中。@ComponentScan 注解还有许多属性,通过这些属性可以更准确地指定哪些 Bean 被扫描并注入。

2) @EnableAutoConfiguration注解

@EnableAutoConfiguration 注解是 Spring Boot 实现自动化配置加载的核心注解。通过 @Import 注入一个名为 AutoConfigurationImportSelector 的类,Spring-FactoriesLoader 类加载类路径下的 META-INF/spring.factories 文件来实现自动配置加载的过程。其中,spring.factories 文件配置了org.springframework.boot.autoconfigure. EnableAutoConfiguration 属性值,可以加载配置了 @Configuration 注解的类到 IoC 容器中。

3) @SpringBootConfiguration注解

@SpringBootConfiguration 注解的功能类似于 @Configuration 注解,声明当前类是一个配置类,它会将当前类中有 @Bean 注解的实例加载到 IoC 容器中。

优秀文章