上篇文章分析了SpringBoot事件发布机制的大致原理,并且提到了SpringBoot启动过程中的第一个事件ApplicationStartingEvent,以及监听了该事件的四个监听器:
- DelegatingApplicationListener
- LoggingApplicationListener
- LiquibaseServiceLocatorApplicationListener
- BackgroundPreinitializer
首先还是先简单说下为什么这四个监听器能监听到ApplicationStartingEvent事件,权当是对上篇文章的一个回顾
监听器能够监听到当前事件,分三种情况:
- 监听器实现了GenericApplicationListener接口,并且在supportsEventType方法中指定了当前事件的类型
- 监听器实现了SmartApplicationListener接口,并且在supportsEventType方法中指定了当前事件的类型
- 监听器没有实现上述接口,但其上层接口ApplicationListener< T >中的事件类型 T 是当前事件的父类
在上述四个监听器中,LoggingApplicationListener属于类型1,它实现了GenericApplicationListener接口
public class LoggingApplicationListener implements GenericApplicationListener {
其supportsEventType方法如下
public boolean supportsEventType(ResolvableType resolvableType) {
return this.isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}
EVENT_TYPES在静态代码块中做了初始化,其中指定了ApplicationStartingEvent
static {
......
......
EVENT_TYPES = new Class[]{ApplicationStartingEvent.class, ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class, ContextClosedEvent.class, ApplicationFailedEvent.class};
......
......
}
实际上LoggingApplicationListener还重写了supportsSourceType方法,只不过其中指定了关注的事件源包含了SpringApplication,之前有提到过这个方法默认是true,大多数类是没有重写的,这里就一笔带过了
public boolean supportsSourceType(Class<?> sourceType) {
return this.isAssignableFrom(sourceType, SOURCE_TYPES);
}
......
......
SOURCE_TYPES = new Class[]{SpringApplication.class, ApplicationContext.class};
另外三个监听器都属于类型3
public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener<ApplicationStartingEvent> {
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
ApplicationStartingEvent的类型参数为ApplicationEvent
BackgroundPreinitializer 的类型参数为SpringApplicationEvent
它们都是ApplicationStartingEvent的父类
而LiquibaseServiceLocatorApplicationListener 的类型参数正是ApplicationStartingEvent
ApplicationStartingEvent的继承结构:
接下来逐个分析下这四个监听器在这个启动事件中作了什么处理,看下他们的onApplicationEvent方法
DelegatingApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = this.getListeners(((ApplicationEnvironmentPreparedEvent)event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
Iterator var3 = delegates.iterator();
while(var3.hasNext()) {
ApplicationListener<ApplicationEvent> listener = (ApplicationListener)var3.next();
this.multicaster.addApplicationListener(listener);
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
这个方法中,先判断事件是否是ApplicationEnvironmentPreparedEvent,是的话才会做处理,并且将自己内部的事件多播器初始化
ApplicationEnvironmentPreparedEvent和ApplicationStartingEvent都派生于SpringApplicationEvent
所以此时并不会进它的if,那么下面的multicaster也是null的,虽然此时接收到了事件,但是它并没有做任何处理
既然这个监听器只处理ApplicationEnvironmentPreparedEvent,为什么不让它像其他监听器一样,派生自
GenericApplicationListener或者SmartApplicationListener接口,然后在supportsEventType方法中指定监听的事件类型呢?
因为它需要在ApplicationEnvironmentPreparedEvent事件到达时做一个初始化,当初始化完成后,也就是内部的事件多播器初始化完成后,后续就可能会处理其他类型的事件了
LoggingApplicationListener
熟悉SpringBoot的朋友可能都知道,SpringBoot的默认日志体系是locback,而这个Listener就是确定要使用的日志体系
它的onApplicationEvent监听了很多事件,我们进入第一个分支onApplicationStartingEvent
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
this.onApplicationStartingEvent((ApplicationStartingEvent)event);
} else if (event instanceof ApplicationEnvironmentPreparedEvent) {
this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
} else if (event instanceof ApplicationPreparedEvent) {
this.onApplicationPreparedEvent((ApplicationPreparedEvent)event);
} else if (event instanceof ContextClosedEvent && ((ContextClosedEvent)event).getApplicationContext().getParent() == null) {
this.onContextClosedEvent();
} else if (event instanceof ApplicationFailedEvent) {
this.onApplicationFailedEvent();
}
}
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
从get方法中获取了一个日志体系
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
} else {
return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
return ClassUtils.isPresent((String)entry.getKey(), classLoader);
}).map((entry) -> {
return get(classLoader, (String)entry.getValue());
}).findFirst().orElseThrow(() -> {
return new IllegalStateException("No suitable logging system located");
});
}
}
SYSTEM_PROPERTY是取的自身的全限定名,即org.springframework.boot.logging.LoggingSystem
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
一般是不配置这个属性的,进入else分支
SYSTEMS是一个map,在静态代码块中做了初始化
private static final Map<String, String> SYSTEMS;
......
......
static {
Map<String, String> systems = new LinkedHashMap();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
大概逻辑就是遍历这个map,过滤出存在于classpath下的key,然后实例化value,返回找到的第一个作为系统的日志策略
ch.qos.logback.core.Appender是logback的类,在SpringBoot中是默认集成进来的,我们SpringBoot项目会引入一个spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
其内部引入了一个logging
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
而在这个logging内部就引入了logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>compile</scope>
</dependency>
第二个org.apache.logging.log4j.core.impl.Log4jContextFactory,需要引入log4j的依赖才会有
第三个java.util.logging.LogManager是JDK中的日志框架
所以此时能加载到的是第一个和第三个,然后最终返回的就是logback,SpringBoot默认使用logback就是在这里确定的
如果在spring-boot-starter中排除掉logging的依赖,就会采用java自带的java.util.logging
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
如果想用log4j,就需要引入log4j的依赖,同时在spring-boot-starter中排掉logback
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
确定好日志框架后,再调用beforeInitialize做一些预初始化工作
总结下SpringBoot的默认日志策略
- 如果引入了logback,采用logback
- 如果没有logback,但有log4j,采用log4j
- 如果以上都没有,就使用jdk自带的log
LiquibaseServiceLocatorApplicationListener
public void onApplicationEvent(ApplicationStartingEvent event) {
if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator", event.getSpringApplication().getClassLoader())) {
(new LiquibaseServiceLocatorApplicationListener.LiquibasePresent()).replaceServiceLocator();
}
}
当引入了liquibase的依赖才会触发事件,这是一个管理数据库脚本的开源组件,老实说我从来没有见过哪个项目用它,不是很了解,感兴趣的朋友可以自己找找资料
BackgroundPreinitializer
这个监听器主要在ApplicationStartingEvent 事件中提前做一些后台的初始化工作
public void onApplicationEvent(SpringApplicationEvent event) {
if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore") && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
this.performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) && preinitializationStarted.get()) {
try {
preinitializationComplete.await();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
}
}
先通过Boolean.getBoolean查看是否配置了spring.backgroundpreinitializer.ignore属性
public static boolean getBoolean(String name) {
boolean result = false;
try {
result = parseBoolean(System.getProperty(name));
} catch (IllegalArgumentException | NullPointerException e) {
}
return result;
}
所以可以通过在启动参数中添加配置-Dspring.backgroundpreinitializer.ignore=true来关闭这个后台加载的过程
也可以在启动类的main方法中显示的配置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
System.setProperty("spring.backgroundpreinitializer.ignore", "true");
SpringApplication.run(Application.class, args);
}
}
但不能通过配置文件来设置,因为此时配置文件还没有被加载
一般不会去关闭这个流程,看下performPreinitialization方法做了哪些事情
private void performPreinitialization() {
try {
Thread thread = new Thread(new Runnable() {
public void run() {
this.runSafely(new BackgroundPreinitializer.ConversionServiceInitializer());
this.runSafely(new BackgroundPreinitializer.ValidationInitializer());
this.runSafely(new BackgroundPreinitializer.MessageConverterInitializer());
this.runSafely(new BackgroundPreinitializer.MBeanFactoryInitializer());
this.runSafely(new BackgroundPreinitializer.JacksonInitializer());
this.runSafely(new BackgroundPreinitializer.CharsetInitializer());
BackgroundPreinitializer.preinitializationComplete.countDown();
}
public void runSafely(Runnable runnable) {
try {
runnable.run();
} catch (Throwable var3) {
;
}
}
}, "background-preinit");
thread.start();
} catch (Exception var2) {
preinitializationComplete.countDown();
}
}
启动了一个线程,然后在这个线程里又启动了其他线程去做一些初始化,比如ConversionServiceInitializer
private static class ConversionServiceInitializer implements Runnable {
private ConversionServiceInitializer() {
}
public void run() {
new DefaultFormattingConversionService();
}
}
run方法中new了一个DefaultFormattingConversionService的实例,这个主要是跟类型转换有关
其他线程也差不多,都是做一些基础的配置,诸如校验、消息转换、默认编码等等
至此对ApplicationStartingEvent的分析就告一段落,接下来就回到SpringApplication的run方法中分析后面的流程