持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,[点击查看活动详情]
Spring里面有太多侦听器了,估计一时半会儿看不完,只能跟随着看SpringBoot的源码看点记点了。
EventListener
我们先看看侦听器的鼻祖:
package java.util;
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
复制代码
JDK协议里面规定了,所有的事件侦听器必须继承EventListener 接口。
ApplicationListener
再来看看Spring里面侦听器的鼻祖:
/**
* 由应用程序事件侦听器实现的接口。
* 基于Observer设计模式的标准java.util.EventListener接口。
* 从Spring 3.0开始, ApplicationListener可以一般性地声明其感兴趣的事件类型。在Spring ApplicationContext注册时,将相应地过滤事件,并且仅针对匹配事件对象调用侦听器。
*
* @author Rod Johnson
* @author Juergen Hoeller
* @param <E> the specific {@code ApplicationEvent} subclass to listen to
* @see org.springframework.context.ApplicationEvent
* @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.EventListener
*/
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
复制代码
只有一个onApplicationEvent接口; 再看看侦听器的实: 光spring里面的实现就有32个,如果每个都看那得看死个人哦,下面就分事件来看看侦听器吧,当然侦听器和事件的对应关系不是一一对应的,那就按阅读源码遇到的侦听器顺序来排列文章结构吧。
LoggingApplicationListener
/**
* An {@link ApplicationListener} that configures the {@link LoggingSystem}. If the
* environment contains a {@code logging.config} property it will be used to bootstrap the
* logging system, otherwise a default configuration is used. Regardless, logging levels
* will be customized if the environment contains {@code logging.level.*} entries and
* logging groups can be defined with {@code logging.group}.
* <p>
* Debug and trace logging for Spring, Tomcat, Jetty and Hibernate will be enabled when
* the environment contains {@code debug} or {@code trace} properties that aren't set to
* {@code "false"} (i.e. if you start your application using
* {@literal java -jar myapp.jar [--debug | --trace]}). If you prefer to ignore these
* properties you can set {@link #setParseArgs(boolean) parseArgs} to {@code false}.
* <p>
* By default, log output is only written to the console. If a log file is required, the
* {@code logging.file.path} and {@code logging.file.name} properties can be used.
* <p>
* Some system properties may be set as side effects, and these can be useful if the
* logging configuration supports placeholders (i.e. log4j or logback):
* <ul>
* <li>{@code LOG_FILE} is set to the value of path of the log file that should be written
* (if any).</li>
* <li>{@code PID} is set to the value of the current process ID if it can be determined.
* </li>
* </ul>
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author HaiTao Zhang
* @since 2.0.0
* @see LoggingSystem#get(ClassLoader)
*/
public class LoggingApplicationListener implements GenericApplicationListener {
复制代码
大意就是说: LoggingApplicationListener是一个LoggingSystem配置的侦听器,如果环境中有配置
logging.config
属性,那么这将用于引导启动日志系统,否则将采用默认配置。日志级别可以通过logging.level.*
进行自定义,日志组通过logging.group
进行配置。 如果环境中包含为被设置成false的debug或trace属性,那么Spring, Tomcat, Jetty 和 Hibernate 的Debug和trace日志将被启动。(i.e. 如果你启动你的应用以这种方式:java -jar myapp.jar [--debug | --trace],如果你想忽略这些属性,你可以设置parseArgs为false[setParseArgs(boolean)方法]) 默认情况下,日志只会被输出到控制台,如果需要一个日志文件,通过配置logging.file.path
和logging.file.name
来实现。 有些系统设置可能会被作为侧面效果进行设置,如果日志配置支持占位符(i.e. log4j or logback),那么他们可以被用到。
看看这个侦听器的onApplicationEvent方法:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
复制代码
ApplicationStartingEvent
先看下ApplicationStartingEvent事件的处理:
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
//先加载一个日志系统,然后用加载的日志系统执行一个beforeInitialize方法。
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
复制代码
初始化日志系统
来看看日志系统的加载方法:
/**
*一个System属性,可用于指示要使用的LoggingSystem 。
*/
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
/**
*检测并返回正在使用的日志系统。支持Logback和Java日志记录。
* @param classLoader the classloader
* @return the logging system
*/
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) -> get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}
复制代码
从这里可以看到,我们是可以在通过配置系统属性org.springframework.boot.logging.LoggingSystem
来指定采用哪个日志系统的,如果org.springframework.boot.logging.LoggingSystem
的值是none,则会构建一个空操作日志系统(该日志系统不会做任何事情)。 如果没有系统配置,则会SYSTEMS中去选取一个存在的日志系统进行实例化。 看下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);
}
复制代码
debug可以看到最后我们采用的是:
其实这三个日志系统spring都有提供的,这里为什么要执行一波判断然后再选第一个呢?而且SYSTEMS 也是不可修改的map,这点没搞懂
日志系统干的事无非就是记录日志打印日志,先不看了。
ApplicationEnvironmentPreparedEvent
看看这个侦听者在环境准备完成事件发生时要干什么:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
复制代码
/**
* 根据环境和classpath对日志系统进行一些设置
* @param environment the environment
* @param classLoader the classloader
*/
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem, this.logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
复制代码
ClasspathLoggingApplicationListener
直接上onApplicationEvent方法吧:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (logger.isDebugEnabled()) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
logger.debug("Application started with classpath: " + getClasspath());
}
else if (event instanceof ApplicationFailedEvent) {
logger.debug("Application failed to start with classpath: " + getClasspath());
}
}
}
复制代码
感觉也没什么好说的,下一个
BackgroundPreinitializer
/**
* {@link ApplicationListener} to trigger early initialization in a background thread of
* time consuming tasks.
* <p>
* Set the {@link #IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME} system property to
* {@code true} to disable this mechanism and let such initialization happen in the
* foreground.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Artsiom Yudovin
* @since 1.3.0
*/
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
复制代码
目前貌似看不懂,往后看,看onApplicationEvent方法:
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
&& event instanceof ApplicationStartingEvent && multipleProcessors()
&& preinitializationStarted.compareAndSet(false, true)) {
performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
&& preinitializationStarted.get()) {
try {
preinitializationComplete.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
复制代码
/**
* 指示Spring Boot如何运行预初始化的系统属性。 当该属性设置为true ,不进行任何预初始化,并且每一项都需要在前台进行初始化。
* 当该属性为false (默认值)时,预初始化在后台的单独线程中运行。
* @since 2.1.0
*/
public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";
复制代码
//执行类下,当前可用线程是大于1的。这个属性好像更系统配置有关。
private boolean multipleProcessors() {
return Runtime.getRuntime().availableProcessors() > 1;
}
复制代码
//这里用的是原子性Boolean对象,保证后面比较交换过程中的线程安全性。避免与初始化多次。
private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);
复制代码
可以看到默认情况下ApplicationStartingEvent事件发生时会执行预初始化,ApplicationReadyEvent或ApplicationFailedEvent事件发生的时候都会等待预初始化过程完成。
这里我并没发现任何地方有将preinitializationStarted设置会false的地方,感觉这个地方是有什么没看到的。这里有点看不懂。
performPreinitialization
看看performPreinitialization方法吧:
private void performPreinitialization() {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
runSafely(new ConversionServiceInitializer());
runSafely(new ValidationInitializer());
runSafely(new MessageConverterInitializer());
runSafely(new JacksonInitializer());
runSafely(new CharsetInitializer());
preinitializationComplete.countDown();
}
public void runSafely(Runnable runnable) {
try {
runnable.run();
}
catch (Throwable ex) {
// Ignore
}
}
}, "background-preinit");
thread.start();
}
catch (Exception ex) {
// This will fail on GAE where creating threads is prohibited. We can safely
// continue but startup will be slightly slower as the initialization will now
// happen on the main thread.
preinitializationComplete.countDown();
}
}
复制代码
看了下代码,似懂非懂,先留坑。
DelegatingApplicationListener
/**
*委托给在context.listener.classes环境属性下指定的其他侦听器的ApplicationListener 。
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
复制代码
看下这个侦听器的onApplicationEvent:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<ApplicationEvent> listener : delegates) {
this.multicaster.addApplicationListener(listener);
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
复制代码
@SuppressWarnings("unchecked")
private List<ApplicationListener<ApplicationEvent>> getListeners(ConfigurableEnvironment environment) {
if (environment == null) {
return Collections.emptyList();
}
String classNames = environment.getProperty(PROPERTY_NAME);
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
try {
Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz,
"class [" + className + "] must implement ApplicationListener");
listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils.instantiateClass(clazz));
}
catch (Exception ex) {
throw new ApplicationContextException("Failed to load context listener class [" + className + "]",
ex);
}
}
}
AnnotationAwareOrderComparator.sort(listeners);
return listeners;
}
private static final String PROPERTY_NAME = "context.listener.classes";
复制代码
只有当环境准备完成事件发生的时候,然后获取通过配置的context.listener.classes
属性来获取侦听器实例,然后把这些侦听器加到应用的侦听器集合里面,然后再次发布这个事件。
这个地方get到我们可以通过实现ApplicationListener的方式来自定义侦听器,通过
context.listener.classes
属性配置的方式将侦听器注册到Spring里面。 接着我们自定义的监听器就可以监听spring里面的事件了。
LiquibaseServiceLocatorApplicationListener
/**
* {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a
* version that works with Spring Boot executable archives.
*
* @author Phillip Webb
* @author Dave Syer
* @since 1.0.0
*/
public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener<ApplicationStartingEvent> {
复制代码
没多大看懂,直接看onApplicationEvent方法:
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator",
event.getSpringApplication().getClassLoader())) {
new LiquibasePresent().replaceServiceLocator();
}
}
复制代码
看来又是一个超纲知识,先pass。
ConfigFileApplicationListener
/**
* EnvironmentPostProcessor通过从众所周知的位置加载属性来配置上下文。默认情况下这些属性会被从以下这些目录下的`application.properties`和/或`application.yml`加载:
- file:./config/
- file:./
- classpath:config/
- classpath:
这个集合是按优先级排序的。(高优先级目录下的属性将覆盖低优先级目录下的属性)
另外可以使用setSearchLocations(String)和setSearchNames(String)来指定搜索路径和文件名称。
还将根据活动配置文件加载其他文件。 例如,如果“ Web”配置文件处于活动状态,则将考虑“ application-web.properties”和“ application-web.yml”。
“ spring.config.name”属性可用于指定要加载的备用名称,“ spring.config.location”属性可用于指定备用搜索位置或特定文件。
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.0.0
*/
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
复制代码
看来这个侦听器是去加载配置文件的。 看下onApplicationEvent方法:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
复制代码
看来,这个侦听器会在环境准备好的时候和应用程序准备好的时候被called。
ApplicationEnvironmentPreparedEvent
先看看环境准备好的时候,这个侦听器做了什么:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
复制代码
现在加载出EnvironmentPostProcessor,EnvironmentPostProcessor好像在前面哪里看过,再看下注释: 目前加上自己一共是5个处理器,为了结构所以不在这里写了,要看明细移步:跟我一起阅读SpringBoot源码(十一)——EnvironmentPostProcessor
ApplicationPreparedEvent
应用程序准备好事件尚未发布,等发布的时候再来看~~~~
AnsiOutputApplicationListener
/**
* An {@link ApplicationListener} that configures {@link AnsiOutput} depending on the
* value of the property {@code spring.output.ansi.enabled}. See {@link Enabled} for valid
* values.
*
* @author Raphael von der Grün
* @author Madhura Bhave
* @since 1.2.0
*/
public class AnsiOutputApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
复制代码
通过 spring.output.ansi.enabled
来配置AnsiOutput的侦听器。Enabled是一个枚举。 先看看AnsiOutput的定义:
/**
* 生成ANSI编码的输出,自动尝试检测终端是否支持ANSI。
*
* @author Phillip Webb
* @since 1.0.0
*/
public abstract class AnsiOutput {
复制代码
输出ANSI编码???? 再看看Enabled:
/**
* 可能的值传递给setEnabled 。 确定何时为着色应用程序输出输出ANSI转义序列。
*/
public enum Enabled {
/**
* 尝试检测ANSI着色功能是否可用。 AnsiOutput的默认值。
*/
DETECT,
/**
* 启用ANSI彩色输出。
*/
ALWAYS,
/**
* 禁用ANSI彩色输出。
*/
NEVER
}
复制代码
试了下,没啥效果,不知道怎么用的。
FileEncodingApplicationListener
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.containsProperty("spring.mandatory-file-encoding")) {
return;
}
String encoding = System.getProperty("file.encoding");
String desired = environment.getProperty("spring.mandatory-file-encoding");
if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
if (logger.isErrorEnabled()) {
logger.error("System property 'file.encoding' is currently '" + encoding + "'. It should be '" + desired
+ "' (as defined in 'spring.mandatoryFileEncoding').");
logger.error("Environment variable LANG is '" + System.getenv("LANG")
+ "'. You could use a locale setting that matches encoding='" + desired + "'.");
logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
+ "'. You could use a locale setting that matches encoding='" + desired + "'.");
}
throw new IllegalStateException("The Java Virtual Machine has not been configured to use the "
+ "desired default character encoding (" + desired + ").");
}
}
复制代码
如果环境配置了spring.mandatory-file-encoding
属性,则从系统配置中获取file.encoding
配置,如果spring.mandatory-file-encoding
和file.encoding
的配置不同,则抛出异常终止Spring应用。
侦听Spring上下文初始化的侦听器
先来看看SpringBoot启动过程中,准备好上下文的事件侦听器(目前得知的是4个):
很显然这个侦听器并不是只侦听应用的启动,还包括环境的准备完成,应用准备完成,上下文关闭,应用失败这些事件。 目前读源码还在应用启动阶段,就先看看onApplicationStartingEvent事件吧。
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
/获取当前的日志系统并设置给侦听器。
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
//然后执行beforeInitialize操作
this.loggingSystem.beforeInitialize();
}
复制代码
这里就先不去看怎么获取日志系统的了,这个在前面看过LogbackLoggingSystem
看下LogbackLoggingSystem的beforeInitialize方法:
@Override
public void beforeInitialize() {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
super.beforeInitialize();
loggerContext.getTurboFilterList().add(FILTER);
}
复制代码
太细了,看不了了,先跳过。