[TOC]
Spring Boot/Spring的启动过程分析
一、Spring Boot启动
先来一段众所周知的spring boot的启动入口代码。
1 |
|
2 | public class Application { |
3 | public static void main(String[] args) { |
4 | SpringApplication.run(Application.class, args); |
5 | } |
6 | } |
里面的核心代码如下,各步骤已添加注释:
1 | /** |
2 | * Run the Spring application, creating and refreshing a new |
3 | * {@link ApplicationContext}. |
4 | * @param args the application arguments (usually passed from a Java main method) |
5 | * @return a running {@link ApplicationContext} |
6 | */ |
7 | public ConfigurableApplicationContext run(String... args) { |
8 | StopWatch stopWatch = new StopWatch(); |
9 | stopWatch.start(); |
10 | ConfigurableApplicationContext context = null; |
11 | Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); |
12 | configureHeadlessProperty(); |
13 | /** |
14 | * 获取META-INF/spring.factories中配置的SpringApplicationRunListener类型的监听器 |
15 | * 默认只有一个listener是org.springframework.boot.context.event.EventPublishingRunListener |
16 | **/ |
17 | SpringApplicationRunListeners listeners = getRunListeners(args); |
18 | //EventPublishingRunListener发布ApplicationStartingEvent事件 |
19 | listeners.starting(); |
20 | try { |
21 | ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); |
22 | /** |
23 | * 创建并配置Environment(主要是PropertySource和Profile) |
24 | *之后调用监听器的environmentPrepared方法,触发EventPublishingRunListener发布ApplicationEnvironmentPreparedEvent事件 |
25 | * 将Environment与Application绑定 |
26 | **/ |
27 | ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); |
28 | //设置spring.beaninfo.ignore系统属性 |
29 | configureIgnoreBeanInfo(environment); |
30 | //打印banner |
31 | Banner printedBanner = printBanner(environment); |
32 | /** |
33 | * 根据应用类型创建ApplicationContext |
34 | * 例如web应用创建的是AnnotationConfigServletWebServerApplicationContext |
35 | **/ |
36 | context = createApplicationContext(); |
37 | //获取配置的异常上报类,用于后续异常上报 |
38 | exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, |
39 | new Class[] { ConfigurableApplicationContext.class }, context); |
40 | /** |
41 | * prepareContext内部逻辑: |
42 | * 1. postProcessApplicationContext:配置bean工厂的beanNameGenerator,ConversionService及context的resourceLoader |
43 | * 2. applyInitializers使用META-INF/spring.factories中配置的ApplicationContextInitializer初始化ApplicationContext,默认配了7个 |
44 | * 3. 调用监听器的contextPrepared方法,触发EventPublishingRunListener发布ApplicationContextInitializedEvent |
45 | * 4. 获取beanFactory并配置(默认是DefaultListableBeanFactory,此时已有几个spring内部的bean已注册),如注册springApplicationArguments单例类,设置allowBeanDefinitionOverriding参数等,配置懒加载LazyInitializationBeanFactoryPostProcessor |
46 | * 5. 加载bean,看源码是支持xml读取,annotation扫描,甚至groovy,实际上只加载了入口类也就是上面的Application类,将其注册为单例并获取了其注解原数据,即@SpringBootApplication注解 |
47 | * 6. 调用监听器的contextLoaded方法,触发EventPublishingRunListener发布ApplicationPreparedEvent |
48 | **/ |
49 | prepareContext(context, environment, listeners, applicationArguments, printedBanner); |
50 | /** |
51 | * 调用AbstractApplicationContext的refresh方法,然后注册shutdownhook,这一步核心其实就是spring的启动了,这个在第3节展开 |
52 | **/ |
53 | refreshContext(context); |
54 | afterRefresh(context, applicationArguments); |
55 | stopWatch.stop(); |
56 | if (this.logStartupInfo) { |
57 | new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); |
58 | } |
59 | //调用监听器的started方法,触发EventPublishingRunListener发布ApplicationStartedEvent |
60 | listeners.started(context); |
61 | //调用ApplicationRunner,然后调用CommandLineRunner |
62 | callRunners(context, applicationArguments); |
63 | } catch (Throwable ex) { |
64 | //里面有一步是:调用监听器的failed方法,触发EventPublishingRunListener发布ApplicationFailedEvent |
65 | handleRunFailure(context, ex, exceptionReporters, listeners); |
66 | throw new IllegalStateException(ex); |
67 | } |
68 | try { |
69 | //调用监听器的running方法,触发EventPublishingRunListener发布ApplicationReadyEvent |
70 | listeners.running(context); |
71 | } catch (Throwable ex) { |
72 | handleRunFailure(context, ex, exceptionReporters, null); |
73 | throw new IllegalStateException(ex); |
74 | } |
75 | return context; |
76 | } |
可以看到spring boot的启动是在spring启动的基础上增加了一些特性,例如根据应用类型推断并指定ApplicationContext的具体实现,各阶段事件的发布(提供了一个扩展点),调用ApplicationRunner,CommandLineRunner(又一个扩展点)等。下面来一张详细的流程图,个人觉得这个图画的很好^1,可以参考该图一步步看源码。
PS:推荐先粗读一遍源码,然后对应用进行debug,一步步看执行逻辑以及每个步骤执行完毕后的数据以及状态,可以对细节有更好的理解。例如,有些变量是在构造器初始化时加载的配置,这个光看源码很难看到。
二、Spring Boot自动化配置
1. @EnableAutoConfiguration
spring boot提供了自动配置,@SpringBootApplication注解中的@EnableAutoConfiguration注解是spring boot自动配置的关键,原理可阅读文章【2】^2,以后有机会再展开详细描述。
2. META-INF/spring.factories
上面说到@EnableAutoConfiguration注解通过读取META-INF/spring.factories配置文件实现自动配置,这里说一下META-INF/spring.factories配置文件。
- 配置是K/V对,key是接口的全限定名,value是该接口的实现类的名字,可以是逗号分隔的多个值,示例如下:
1 | example.MyService=example.MyServiceImpl1,example.MyServiceImpl2 |
META-INF/spring.factories配置文件可在多个jar中配置,基于这个我们可以实现自己的自动配置类,并将其配置在自己的jar的META-INF/spring.factories配置文件中,spring boot会帮我们完成自动配置。
spring boot自带的META-INF/spring.factories配置文件里已经配置了许多这样的配置项。
借助spring框架的SpringFactoriesLoader可以加载META-INF/spring.factories配置文件,SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。
是不是有点像java的SPI加载机制
三、Spring的启动
上面提到调用了AbstractApplicationContext的refresh()方法,其实就是spring启动时执行的方法,里面的执行顺序是固定的。
1. AbstractApplicationContext的refresh()方法
1 | public void refresh() throws BeansException, IllegalStateException { |
2 | synchronized (this.startupShutdownMonitor) { |
3 | // Prepare this context for refreshing. |
4 | prepareRefresh(); |
5 | // Tell the subclass to refresh the internal bean factory. |
6 | /** |
7 | * obtainFreshBeanFactory()方法内部调用refreshBeanFactory(),其默认实现由AbstractRefreshableApplicationContext提供,逻辑是:销毁原来的bean,关闭原来的bean factory,创建新的DefaultListableBeanFactory,调用loadBeanDefinitions()方法加载bean,这部分在后面详细展开; |
8 | 不过在spring boot的情况下这里的实现是AnnotationConfigServletWebServerApplicationContext类,并不会销毁原来的bean以及bean factory。 |
9 | **/ |
10 | ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); |
11 | // Prepare the bean factory for use in this context. |
12 | /** |
13 | * 摘自该方法的注释,主要是配置bean factory |
14 | * Configure the factory's standard context characteristics, |
15 | * such as the context's ClassLoader and post-processors. |
16 | **/ |
17 | prepareBeanFactory(beanFactory); |
18 | try { |
19 | // Allows post-processing of the bean factory in context subclasses. |
20 | /** |
21 | * 摘自该方法的注释,主要是注册BeanPostProcessors |
22 | * Modify the application context's internal bean factory after its standard |
23 | * initialization. All bean definitions will have been loaded, but no beans |
24 | * will have been instantiated yet. This allows for registering special |
25 | * BeanPostProcessors etc in certain ApplicationContext implementations. |
26 | * @param beanFactory the bean factory used by the application context |
27 | */ |
28 | postProcessBeanFactory(beanFactory); |
29 | // Invoke factory processors registered as beans in the context. |
30 | // 这一步会调用BeanFactoryPostProcessor,其中有一个是ConfigurationClassPostProcessor,扫描@Configuration注解标注的类,并完成相应的bean definition的注册 |
31 | invokeBeanFactoryPostProcessors(beanFactory); |
32 | // Register bean processors that intercept bean creation. |
33 | registerBeanPostProcessors(beanFactory); |
34 | // Initialize message source for this context. |
35 | initMessageSource(); |
36 | // Initialize event multicaster for this context. |
37 | initApplicationEventMulticaster(); |
38 | // Initialize other special beans in specific context subclasses. |
39 | // web应用的情况下会检查是否有WebServer,若没有则新建一个WebServer,spring boot默认是tomcat |
40 | onRefresh(); |
41 | // Check for listener beans and register them. |
42 | // 注册ApplicationListener类型的bean,并将之前发过的ApplicationEvent再发一遍 |
43 | registerListeners(); |
44 | // Instantiate all remaining (non-lazy-init) singletons. |
45 | // 完成bean factory的初始化,并将剩余的未实例化的单例bean实例化 |
46 | finishBeanFactoryInitialization(beanFactory); |
47 | // Last step: publish corresponding event. |
48 | /* |
49 | * Finish the refresh of this context, invoking the LifecycleProcessor's |
50 | * onRefresh() method and publishing the |
51 | * {@link org.springframework.context.event.ContextRefreshedEvent}. |
52 | */ |
53 | finishRefresh(); |
54 | } catch (BeansException ex) { |
55 | if (logger.isWarnEnabled()) { |
56 | logger.warn("Exception encountered during context initialization - " + |
57 | "cancelling refresh attempt: " + ex); |
58 | } |
59 | // Destroy already created singletons to avoid dangling resources. |
60 | destroyBeans(); |
61 | // Reset 'active' flag. |
62 | cancelRefresh(ex); |
63 | // Propagate exception to caller. |
64 | throw ex; |
65 | } finally { |
66 | // Reset common introspection caches in Spring's core, since we |
67 | // might not ever need metadata for singleton beans anymore... |
68 | resetCommonCaches(); |
69 | } |
70 | } |
71 | } |
这块每一步的逻辑都相当复杂,涉及spring bean容器的很多特性以及扩展点,这些都需要具体去了解,后续再补上每一步的详情,先mark一下。
小结
本文基于spring-boot:2.2.1.RELEASE版本,只是粗略过了一遍启动流程,有些部分可能理解有误,内部细节还有待继续研究。另外不得不说,看spring源码是真的累。