从哪入手?
相信很多人尝试读过 Spring Boot 的源码,但是始终没有找到合适的方法。那是因为你对 Spring Boot 的各个组件、机制不是很了解,研究起来就像大海捞针。
至于从哪入手不是很简单的问题吗,当然主启动类了,即是标注着 @SpringBootApplication
注解并且有着 main()
方法的类,如下一段代码:
1 2 3 4 5 6 7
| @SpringBootApplication public class BootApplication {
public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); } }
|
源码如何切分?
SpringApplication
中的静态run()
方法并不是一步完成的,最终执行的源码如下:
1 2 3 4
| public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
|
很显然分为两个步骤,分别是创建 SpringApplication
和执行 run()
方法,下面将分为这两个部分介绍。
如何创建SpringApplication?
创建即是new
对象了,DEBUG
跟进代码,最终执行的SpringApplication
构造方法如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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(); this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }
|
如上图中标注的注释,创建过程重用的其实分为 2 、 3 、 4 这三个阶段,下面将会一一介绍每个阶段 做了什么事。
设置应用类型
这个过程非常重要,直接决定了项目的类型,应用类型分为三种,都在WebApplicationType
这个枚举类中,如下:
NONE
:顾名思义,什么都没有,正常流程走,不额外的启动web容器
,比如Tomcat
。
SERVLET
:基于servlet
的web程序,需要启动内嵌的servlet
web容器,比如Tomcat
。
REACTIVE
:基于reactive
的web程序,需要启动内嵌reactive
web容器,作者不是很了解,不便多说。
判断的依据很简单,就是加载对应的类,比如加载了DispatcherServlet
等则会判断是Servlet
的web程序。源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
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; }
|
这里我引入了spring-boot-starter-web
,肯定是Servlet
的web程序。
设置初始化器(Initializer)
初始化器ApplicationContextInitializer
是个好东西,用于IOC
容器刷新之前初始化一些组件,比如ServletContextApplicationContextInitializer
。
那么如何获取初始化器呢?跟着上图中的代码进入,在SpringApplication
中的如下图中的方法:
1 2 3 4 5 6 7 8
| private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
|
相对重要的就是第一步获取初始化器的名称了,这个肯定是全类名
了,详细源码肯定在loadFactoryNames()
方法中了,跟着源码进入,最终调用的是#SpringFactoriesLoader.loadSpringFactories()
方法。
loadSpringFactories()
方法就不再详细解释了,其实就是从类路径META-INF/spring.factories
中加载ApplicationContextInitializer
的值。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下图:
1 2 3 4
| org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
|
上面的只是一部分初始化器,因为spring.factories
文件不止一个。
这也告诉我们自定义一个ApplicationContextInitializer
只需要实现接口,在spring.factories
文件中设置即可。
设置监听器(Listener)
监听器(ApplicationListener
)这个概念在Spring
中就已经存在,主要用于监听特定的事件(ApplicationEvent
),比如IOC容器刷新、容器关闭等。
Spring Boot
扩展了ApplicationEvent
构建了SpringApplicationEvent
这个抽象类,主要用于Spring Boot
启动过程中触发的事件,比如程序启动中、程序启动完成等。如下图:
监听器如何获取?从源码中知道其实和初始化器(ApplicationContextInitializer
)执行的是同一个方法,同样是从META-INF/spring.factories
文件中获取。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下图:
1 2 3
| org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
|
spring.factories
文件不止一个,同样监听器也不止以上这些。
总结
SpringApplication
的构建都是为了run()
方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。
「注意」:初始化器和这里的监听器都要放置在spring.factories
文件中才能在这一步骤加载,否则不会生效,因此此时IOC容器
还未创建,即使将其注入到IOC容器
中也是不会生效的。
作者简单的画了张执行流程图,仅供参考,如下:
执行run()方法
上面分析了SpringApplication
的构建过程,一切都做好了铺垫,现在到了启动的过程了。
作者根据源码将启动过程分为了「8步」,下面将会一一介绍。
1. 获取、启动运行过程监听器
SpringApplicationRunListener
这个监听器和ApplicationListener
不同,它是用来监听应用程序启动过程的,接口的各个方法含义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public interface SpringApplicationRunListener {
void starting(); void environmentPrepared(ConfigurableEnvironment environment); void contextPrepared(ConfigurableApplicationContext context); void contextLoaded(ConfigurableApplicationContext context); void started(ConfigurableApplicationContext context); void running(ConfigurableApplicationContext context); void failed(ConfigurableApplicationContext context, Throwable exception); }
|
如何获取运行监听器?
在SpringApplication#run()
方法中,源码如下:
1 2
| SpringApplicationRunListeners listeners = getRunListeners(args);
|
跟进getRunListeners()
方法,其实还是调用了loadFactoryNames()
方法从spring.factories
文件中获取值,如下:
1 2
| org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
|
最终注入的是EventPublishingRunListener
这个实现类,创建实例过程肯定是通过反射了,因此我们看看它的构造方法,如下图:
1 2 3 4 5 6 7 8 9 10 11
| public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } }
|
这个运行监听器内部有一个事件广播器(SimpleApplicationEventMulticaster
),主要用来广播特定的事件(SpringApplicationEvent
)来触发特定的监听器ApplicationListener
。
EventPublishingRunListener
中的每个方法用来触发SpringApplicationEvent
中的不同子类。
如何启动运行监听器?
在SpringApplication#run()
方法中,源码如下:
1 2
| //执行starting()方法 listeners.starting(bootstrapContext, this.mainApplicationClass);
|
执行SpringApplicationRunListeners
的starting()
方法,跟进去其实很简单,遍历执行上面获取的运行监听器,这里只有一个EventPublishingRunListener
。因此执行的是它的starting()
方法,源码如下图:
1 2 3 4 5
| @Override public void starting(ConfigurableBootstrapContext bootstrapContext) { this.initialMulticaster .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args)); }
|
上述源码中逻辑很简单,其实只是执行了multicastEvent()
方法,广播了ApplicationStartingEvent
事件。至于multicastEvent()
内部方法感兴趣的可以看看,其实就是遍历ApplicationListener
的实现类,找到监听ApplicationStartingEvent
这个事件的监听器,执行onApplicationEvent()
方法。
总结
这一步其实就是广播了ApplicationStartingEvent
事件来触发监听这个事件的ApplicationListener
。
因此如果自定义了ApplicationListener
并且监听了ApplicationStartingEvent
(应用程序开始启动)事件,则这个监听器将会被触发。
2. 环境构建
这一步主要用于加载系统配置以及用户的自定义配置(application.properties
),源码如下,在run()
方法中:
1
| ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
|
prepareEnvironment
方法内部广播了ApplicationEnvironmentPreparedEvent
事件,源码如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader()); environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
|
环境构建这一步加载了系统环境配置、用户自定义配置并且广播了ApplicationEnvironmentPreparedEvent
事件,触发监听器。
3. 创建IOC容器
源码在run()
方法中,如下:
1
| context = createApplicationContext();
|
跟进代码,真正执行的是ApplicationContextFactory
方法:
1 2 3 4
| protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); }
|
根据webApplicationType
决定创建的类型,很显然,我这里的是servlet
,因此创建的是AnnotationConfigServletWebServerApplicationContext
。
这一步仅仅是创建了IOC容器
,未有其他操作。
4. IOC容器的前置处理
这一步真是精华了,在刷新容器之前做准备,其中有一个非常关键的操作:将启动类注入容器,为后续的自动化配置奠定基础。源码如下:
1
| prepareContext(context, environment, listeners, applicationArguments,printedBanner);
|
prepareContext()
源码解析如下图,内容还是挺多的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences); if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } context.addBeanFactoryPostProcessor(new SpringApplication.PropertySourceOrderingBeanFactoryPostProcessor(context)); Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
|
从上述源码可以看出步骤很多,下面将会详细介绍几个重点的内容。
调用初始化器
在SpringApplication
构建过程中设置的初始化器,从spring.factories
取值的。执行的流程很简单,遍历执行,源码如下:
1 2 3 4 5 6 7 8 9
| protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }
|
将自定义的ApplicationContextInitializer
放在META-INF/spring.factories
中,在此时也是会被调用。
加载启动类,注入容器
这一步是将主启动类加载到IOC容器
中,作为后续自动配置的入口。
在SpringApplication
构建过程中将主启动类放置在primarySources
这个集合中,此时的getAllSources()
即是从其中取值,如下:
1 2 3 4 5 6 7 8 9 10
| public Set<Object> getAllSources() { Set<Object> allSources = new LinkedHashSet<>(); if (!CollectionUtils.isEmpty(this.primarySources)) { allSources.addAll(this.primarySources); } if (!CollectionUtils.isEmpty(this.sources)) { allSources.addAll(this.sources); } return Collections.unmodifiableSet(allSources); }
|
这里取出的就是主启动类,当然你的项目中可能不止一个,接下来就是将其加载到IOC容器中了,源码如下:
1
| load(context, sources.toArray(new Object[0]));
|
跟着代码进去,其实主要逻辑都在BeanDefinitionLoader.load()
方法,如下:
1 2 3 4 5 6 7 8 9 10 11
| private void load(Class<?> source) { if (isGroovyPresent() && BeanDefinitionLoader.GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { BeanDefinitionLoader.GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, BeanDefinitionLoader.GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); } if (isEligible(source)) { this.annotatedReader.register(source); } }
|
将主启动类加载到beanDefinitionMap
,后续该启动类将作为开启自动配置化的入口,后续章节详细介绍。
两次广播事件
这一步涉及到了两次事件广播,分别是ApplicationContextInitializedEvent
和ApplicationPreparedEvent
,对应的源码如下:
1 2
| listeners.contextPrepared(context); load(context, sources.toArray(new Object[0]));
|
5. 刷新容器
刷新容器完全是Spring
的功能了,比如初始化资源,初始化上下文广播器等,这个就不再详细介绍,有兴趣可以看看Spring
的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext)applicationContext).refresh(); } public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh(); }
finally { resetCommonCaches(); } } }
|
6. IOC容器的后置处理
一个扩展方法,源码如下:
1
| afterRefresh(context, applicationArguments);
|
默认为空,如果有自定义需求可以重写,比如打印一些启动结束日志等。
7. 发出结束执行的事件
同样是EventPublishingRunListener
这个监听器,广播ApplicationStartedEvent
事件。
但是这里广播事件和前几次不同,并不是广播给SpringApplication
中的监听器(在构建过程中从spring.factories
文件获取的监听器)。因此在IOC容器
中注入的监听器(使用@Component
等方式注入的)也能够生效。前面几个事件只有在spring.factories
文件中设置的监听器才会生效。
跟着代码进入,可以看到started()
方法源码如下:
1 2 3 4 5
| @Override public void started(ConfigurableApplicationContext context, Duration timeTaken) { context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken)); AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); }
|
这里并没有用事件广播器SimpleApplicationEventMulticaster
广播事件,而是使用ConfigurableApplicationContext
直接在IOC容器
中发布事件。
8. 执行Runners
Spring Boot
提供了两种Runner
让我们定制一些额外的操作,分别是CommandLineRunner
和ApplicationRunner
,关于这两个的区别,后面文章详细介绍。
调用的源码如下:
1
| callRunners(context, applicationArguments);
|
跟进代码,其实真正执行的是如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
|
逻辑很简单,从IOC容器
中获取,遍历调用。
总结
Spring Boot
启动流程相对简单些,作者将其细分了以上八个步骤,希望能够帮助读者理解,流程图如下: