所有调试均使用SpringBoot 2.4.5版本。
这一章首先来理解下SpringBoot的spring.factories中的 org.springframework.context.ApplicationListener 配置。这个部分实现比较简单,权当热热身。
一、初始化加载器使用
ApplicationListener接口允许在Spring刷新IOC容器(大名鼎鼎的applicationContext.refresh方法)之前,进行自定义的初始化工作。我们可以创建一个SpringBoot工程来简单测试一下:(注意版本用2.4.5)
1、创建一个自定义启动器。这里只打印出SpringBoot的配置信息
package com.roy.applicationContextInitializer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
//获取系统属性
System.out.println("==== systemProperties ===========");
context.getEnvironment().getSystemProperties().forEach((key,value) -> {
System.out.println(key +"==="+value);
});
System.out.println("==== systemProperties end ===========");
//获取系统环境变量
// context.getEnvironment().getSystemEnvironment().forEach((key,value) -> {
// System.out.println(key +"==="+value);
// });
}
}
2、在启动类中添加这个初始化器
@SpringBootApplication
public class P1Application implements CommandLineRunner {
public static void main(String[] args) {
final SpringApplication application = new SpringApplication(P1Application.class);
//添加应用初始化器
application.addInitializers(new MyApplicationContextInitializer());
application.run(args);
}
@Autowired
private ApplicationContext applicationContext;
@Override
public void run(String... args) throws Exception {
System.out.println(applicationContext.getId());
}
}
3、直接执行这个main方法。然后就可以在控制台看到打印出来的系统属性。 --注意他的执行时间是非常靠前的。
这样就添加了一个自定义的初始化器。
可以自己尝试下在这一步可以添加一些什么东西。通常可以添加一些ApplicationListener(事件监听器),BeanFactoryPostProcessor(Bean后处理器),ProtocolResolver(协议处理器,可以用来加载自定义配置文件)。
另外,卖个小关子。示例中main方法中打印出了applicationContext的ID,结果是在application.properties配置文件中的spring.application.name属性。可是这个时候还没有进行统一配置加载,想一下是怎么来的?
4、这里了解了初始化器是什么东西后,就可以回到我们的正题,使用spring.factories扩展机制来配置ApplicationContextInitializer。
在classpath下添加META-INF/spring.factories文件,在文件中添加配置内容:
org.springframework.context.ApplicationContextInitializer=\
com.roy.applicationContextInitializer.MyApplicationContextInitializer
然后就可以把main方法中application.addInitializers这一行代码给注释掉,再启动main方法。
可以看到依然会打印出所有的系统属性。其实也就是说我们自定义的这个MyApplicationContextInitializer已经通过spring.factories文件配置的方式添加到了applicationContext当中。
二、核心机制解读
了解这个机制了之后再来看看源码中是怎么来处理spring.factories文件当中的ApplicationContextInitializer配置的。这个代码跟踪比较简单,就不再去画图什么的了。
1、加载spring.factories的配置:
在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 = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); <=======重点在这里
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
跟踪这个getSpringFactoriesInstances方法,就可以回到我们第一节中介绍了的SpringBoot的属性加载机制了。
2、执行初始化器
这个初始化器的执行顺序是相当靠前的,在SpringApplication的run方法中就直接执行了。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // <==== 重点在这里
this.refreshContext(context); //刷新Spring容器
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, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
在prepareContext方法中直接就执行了这些初始化器的initialize方法
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context); //<=====重点在这里
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
从源码中可以看到,Initializer的执行是在打印Banner之后,而Spring的refresh方法之前。这也跟我们之前观察到的日志打印是吻合的。
这个执行顺序也说明,在初始化阶段,Spring的IOC容器是还没有创建的,所以在这个阶段,不能引用IOC容器中的Bean。
三、SpringBoot中的扩展实现
现在再来看看SpringBoot依赖中给我们提供的默认实现:
#spring-boot
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
#spring-boot-autoconfigure
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
在这其中找几个简单的实现机制来看看,例如ContextIdApplicationContextInitializer。
public class ContextIdApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
//加载顺序
private int order = Ordered.LOWEST_PRECEDENCE - 10;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {// <========入口方法
ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
ApplicationContext parent = applicationContext.getParent();
if (parent != null && parent.containsBean(ContextId.class.getName())) {
return parent.getBean(ContextId.class).createChildId();
}
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.getProperty("spring.application.name");// <========
return StringUtils.hasText(name) ? name : "application";
}
/**
* The ID of a context.
*/
static class ContextId {
private final AtomicLong children = new AtomicLong();
private final String id;
ContextId(String id) {
this.id = id;
}
ContextId createChildId() {
return new ContextId(this.id + "-" + this.children.incrementAndGet());
}
String getId() {
return this.id;
}
}
}
可以看到,这个初始化器中做的事情并不复杂,就是解析SpringBoot的应用名。然后将携带应用名信息的ContextId对象注入到IOC容器当中。前面卖的那个小关子是不是就有了答案?
再看一个ConditionEvaluationReportLoggingListener。这个是用来处理SpringBoot的启动错误报告的。每次SpringBoot应用启动失败会打印出一大堆的堆栈错误信息,就会经过他处理的(当然,完整的处理机制不是这么简单)。
public class ConditionEvaluationReportLoggingListener
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final Log logger = LogFactory.getLog(getClass());
private ConfigurableApplicationContext applicationContext;
private ConditionEvaluationReport report;
private final LogLevel logLevelForReport;
public ConditionEvaluationReportLoggingListener() {
this(LogLevel.DEBUG);
}
public ConditionEvaluationReportLoggingListener(LogLevel logLevelForReport) {
Assert.isTrue(isInfoOrDebug(logLevelForReport), "LogLevel must be INFO or DEBUG");
this.logLevelForReport = logLevelForReport;
}
private boolean isInfoOrDebug(LogLevel logLevelForReport) {
return LogLevel.INFO.equals(logLevelForReport) || LogLevel.DEBUG.equals(logLevelForReport);
}
public LogLevel getLogLevelForReport() {
return this.logLevelForReport;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) { //<=====加载入口
this.applicationContext = applicationContext;
applicationContext.addApplicationListener(new ConditionEvaluationReportListener());//注册事件监听器
if (applicationContext instanceof GenericApplicationContext) {
// Get the report early in case the context fails to load
this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
}
}
//监听事件的处理方法
protected void onApplicationEvent(ApplicationEvent event) {
ConfigurableApplicationContext initializerApplicationContext = this.applicationContext;
if (event instanceof ContextRefreshedEvent) {
if (((ApplicationContextEvent) event).getApplicationContext() == initializerApplicationContext) {
logAutoConfigurationReport();
}
}
else if (event instanceof ApplicationFailedEvent
&& ((ApplicationFailedEvent) event).getApplicationContext() == initializerApplicationContext) {
logAutoConfigurationReport(true);
}
}
private void logAutoConfigurationReport() {
logAutoConfigurationReport(!this.applicationContext.isActive());
}
public void logAutoConfigurationReport(boolean isCrashReport) {
if (this.report == null) {
if (this.applicationContext == null) {
this.logger.info("Unable to provide the conditions report due to missing ApplicationContext");
return;
}
this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
}
if (!this.report.getConditionAndOutcomesBySource().isEmpty()) {
if (getLogLevelForReport().equals(LogLevel.INFO)) {
if (this.logger.isInfoEnabled()) {
this.logger.info(new ConditionEvaluationReportMessage(this.report));
}
else if (isCrashReport) {
logMessage("info");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug(new ConditionEvaluationReportMessage(this.report));
}
else if (isCrashReport) {
logMessage("debug");
}
}
}
}
private void logMessage(String logLevel) {
this.logger.info(String.format("%n%nError starting ApplicationContext. To display the "
+ "conditions report re-run your application with '" + logLevel + "' enabled."));
}
private class ConditionEvaluationReportListener implements GenericApplicationListener {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
Class<?> type = resolvableType.getRawClass();
if (type == null) {
return false;
}
return ContextRefreshedEvent.class.isAssignableFrom(type)
|| ApplicationFailedEvent.class.isAssignableFrom(type);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
ConditionEvaluationReportLoggingListener.this.onApplicationEvent(event);
}
}
}
从核心的initialize方法入手,就能看到他的处理逻辑主要是在初始化阶段注册了一个事件监听器,监听所有的ApplicationEvent。
关于事件监听器,其实是属于Spring的功能了。后面也会有一个章节来讲解SpringBoot针对事件监听器的处理。
其他的一些实现,就不再多贴了,有兴趣大家可以自己去看看。
通过这个章节,也希望能够理解SpringBoot的这种"无业务微核心+拔插式扩展"的设计思路。这种思路能够帮助我们更全面能深入的理解庞大的SpringBoot。另外,了解这些设计思路,也能作为应用开发的扩展点。