不积跬步无以至千里, 量变才是质量的基础。
一、Spring SPI二、伴随事件的生命周期1.前期准备(SPI)2.事件3.启动过程三、总结
理解Springboot原理,个人认为可以从两个点来入手
- Spring SPI
- 伴随事件的生命周期
版本说明:springboot 项目下直接阅读:Spring Boot:2.2.2.RELEASE
一、Spring SPI
你如果知道JAVA SPI ,或者Dubbo SPI 。那么你的脑海里一定对Spring SPI 的机制有了一个大概的轮廓了。
SPI: 为某个接口寻找服务实现的机制
,按约定,从指定文件寻找配置的接口实现
SpringFactoriesLoader
我们来看看Springboot中SPI的工具类的SpringFactoriesLoader
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { // 【1】取得资源文件的URL Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); //【2】 将资源文件解析为`Properties Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); //【3】 取出配置的value值 String propertyValue = properties.getProperty(factoryClassName); for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) { result.add(factoryName.trim()); } } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
META-INF/spring.factories
文件内容格式
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
工作原理:
- 获取搜寻CLASSPATH下所有的
META-INF/spring.factories
资源文件的URL - 遍历URL,将资源文件解析为
Properties
- 以传入的
factoryClass
为KEY,获取资源文件里配置的Value值
总结为:根据传入的factoryClass,从资源文件META-INF/spring.factories
中获取对应的value的过程。
例如:
- 当我们调用
SpringFactoriesLoader.loadFactoryNames(ApplicationContextInitializer.class, this.beanClassLoader);
时 - 就是以
ApplicationContextInitializer
为key从资源文件META-INF/spring.factories
获得SharedMetadataReaderFactoryContextInitializer
与AutoConfigurationReportLoggingInitializer
本质是一种从文件加载配置的机制。
Springboot之所以,以减少开发人员的配置出名,其功劳有Spring SPI
的一部分。因为其很多默认的配置,都是配置在资源文件META-INF/spring.factories
中,并以EnableAutoConfiguration
为KEY ,经由SPI工具类SpringFactoriesLoader
加载到上下文中。
所以说:理解Spring SPI也就明白了Springboot自动化配置原理了。
二、伴随事件的生命周期
Springboot生命起源于SpringApplication.run方法
1.前期准备(SPI)
- 判断当前环境是不是web环境
- 通过SPI机制将资源文件中配置
ApplicationContextInitializer
接口(扩展点)与ApplicationListener
接口(监听器)的实现类全限定名,获取到。 - 通过SPI机制 获取
SpringApplicationRunListener
接口的实现,封装成一个SpringApplicationRunListeners
(有s,组的概念)。其中一个实现EventPublishingRunListener
最常用,一句话说明白:他就是一个广播器,持有准备阶段获取的监听器,用于发布广播阶段事件到事件监听器。
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
2.事件
伴随着生命周期有哪些事件呢?监听器监听的是哪些事件?
- ApplicationStartingEvent : 应用启动中
- ApplicationEnvironmentPreparedEvent:应用环境准备好
- ApplicationContextInitializedEvent:应用上下文已实例化
- ApplicationPreparedEvent :上下文准备好
- ApplicationStartedEvent:应用成功启动
- ApplicationReadyEvent:应用已准备好
- ApplicationFailedEvent :应用启动失败
完整的什么周期,清晰指出了每个重要的时间点。从事件我们也大致可以看出Springboot的原理了。
顺便提一句,在低版本的springboot里其实没有这么多事件,比如一些版本在
listeners.starting();
看起方法应该是启动中,但是发布的事件竟然是ApplicationStartedEvent
,当时我就觉得别扭。直到看了高版本,发现listeners.starting();
发布的就是ApplicationStartingEvent
,非常的舒服。不别扭了。
3.启动过程
都在run方法里了。
3.1、应用开始启动
发布应用启动中【1】ApplicationStartingEvent
事件,表示应用开始启动了,所有监听此事件的监听器,做出反应
listeners.starting(); =============== public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } }
3.2、创建一个ConfigurableEnvironment
实例,表示当前环境。
发送【2】ApplicationEnvironmentPreparedEvent
事件,触发对应的监听器的执行
此处提一个监听器:BootstrapApplicationListener监听器,此监听器是来自SpringCloud ,用于启动/创建Spring Cloud的应用上下文。SpirngCloud的上下文优先SpringBoot的上下文创建。
这对于我们理解SpringCloud,非常关键。
3.3、打印Banner
Banner printedBanner = printBanner(environment);
我们各种Banner就是在这打印出来的
3.4、创建上下文实例。
根据当前环境,创建出一个上下文实例对象出来。
context = createApplicationContext(); ===== public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS); 。。。。 }
3.5、设置上下文实例,并执行ApplicationContextInitializer(准备阶段获取的)
,单例Bean是仍旧还没有初始化,发布【3】ApplicationContextInitializedEvent
事件,表示上下文准备好了。
3.6、加载早期BeanDefinition,发送【4】ApplicationPreparedEvent
事件,触发对应的监听器的执行
load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context);
3.7、refreshContext,这里最重要的是执行了refresh(context);
实例化了Bean。这个方法执行完成,基本上主要工作就完成了。
发送【5】ApplicationStartedEvent
事件,触发对应的监听器的执行
refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context);
3.8、Springboot留的扩展点
callRunners(context, applicationArguments);
3.9、至此Springboot基本上就完成了,发布【6】ApplicationReadyEvent
事件。应用准备好了
try { listeners.running(context); }catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;
3.10、中间出错了会发布ApplicationFailedEvent
应用启动失败事件。
handleRunFailure(context, ex, exceptionReporters, null);
Springboot的启动过程大致流程就是这样。
总结为:这是一个应用启动,准备,创建上下文,完成上下文设置,应用完成,应用完成收尾的过程,伴随着不同阶段的事件发布
三、总结
Springboot抓住SPI与伴随事件的生命周期,这两点,理解起来,就很方便了
Springboot本身又是对Spring的一次封装,其工作原理无非就是围绕Spring的核心做工作。伴随的事件,给我留下了无限扩扩展点。