0%

Spring Boot/Spring的启动过程分析

[TOC]

Spring Boot/Spring的启动过程分析

一、Spring Boot启动

先来一段众所周知的spring boot的启动入口代码。

1
@SpringBootApplication
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,一步步看执行逻辑以及每个步骤执行完毕后的数据以及状态,可以对细节有更好的理解。例如,有些变量是在构造器初始化时加载的配置,这个光看源码很难看到。

img

二、Spring Boot自动化配置

1. @EnableAutoConfiguration

img

spring boot提供了自动配置,@SpringBootApplication注解中的@EnableAutoConfiguration注解是spring boot自动配置的关键,原理可阅读文章【2】^2,以后有机会再展开详细描述。

2. META-INF/spring.factories

上面说到@EnableAutoConfiguration注解通过读取META-INF/spring.factories配置文件实现自动配置,这里说一下META-INF/spring.factories配置文件。

  1. 配置是K/V对,key是接口的全限定名,value是该接口的实现类的名字,可以是逗号分隔的多个值,示例如下:
1
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
  1. META-INF/spring.factories配置文件可在多个jar中配置,基于这个我们可以实现自己的自动配置类,并将其配置在自己的jar的META-INF/spring.factories配置文件中,spring boot会帮我们完成自动配置。

  2. spring boot自带的META-INF/spring.factories配置文件里已经配置了许多这样的配置项。

  3. 借助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源码是真的累。

参考文献