最近两篇文章主要分析了ConfigFileApplicationListener对事件ApplicationEnvironmentPreparedEvent的处理,包括EnvironmentPostProcessor扩展点和系统配置文件的加载,而之前也提到过,实际上有很多监听器都会监听该事件的发布,本文对其它几个监听器的相关处理做个简单的介绍
首先看下收到事件的监听器列表
ConfigFileApplicationListener已经介绍地很详细了,接下来对剩下的监听器做逐一分析
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
Binder.get(environment).bind("spring.output.ansi.enabled", Enabled.class).ifBound(AnsiOutput::setEnabled);
AnsiOutput.setConsoleAvailable((Boolean)environment.getProperty("spring.output.ansi.console-available", Boolean.class));
}
这个监听器主要用来设置日志的颜色,比如默认情况下控制台看到的日志格式如下
如果设置了spring.output.ansi.enabled=ALWAYS,日志的颜色发生了变化,可读性更好一点
这里重点说下Binder这个类
Binder.get(environment).bind("spring.output.ansi.enabled", Enabled.class).ifBound(AnsiOutput::setEnabled);
它是SpringBoot 2.x加入的新特性,用来将Environment中的属性绑定到指定的类型中,可以是List、Map等集合,也可以是自定义的实体类
这里就是将spring.output.ansi.enabled属性的值赋给AnsiOutput.Enabled,它是一个枚举类
public static enum Enabled {
DETECT,
ALWAYS,
NEVER;
private Enabled() {
}
}
绑定的结果存储到BindResult中
public final class BindResult<T> {
......
private final T value;
......
public void ifBound(Consumer<? super T> consumer) {
Assert.notNull(consumer, "Consumer must not be null");
if (this.value != null) {
consumer.accept(this.value);
}
}
......
}
ifBound接收一个Consumer,如果绑定的属性不为空,则调用consumer的处理逻辑
上面传进来的是AnsiOutput::setEnabled,所以就相当于把配置文件中的spring.output.ansi.enabled属性赋给AnsiOutput的enabled变量
而Binder后面的一行代码就直接到Environment中取了spring.output.ansi.console-available赋值给AnsiOutput的consoleAvailable属性
这个监听器就是用来设置日志颜色的,用处不是很大,一般也不会做额外配置
另外由于会在日志中输出表示颜色的分隔符,有可能会对一些日志收集组件产生一定干扰,所以还是慎用
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();
}
}
对当前事件的处理,在方法onApplicationEnvironmentPreparedEvent中
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
this.initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
loggingSystem在之前处理ApplicationStartingEvent事件的时候已经做了初始化,我们之前对它做过详细的介绍,表示当前系统使用的日志体系是logback,还是log4j,亦或者是JDK自带的日志框架
initialize方法主要是对日志相关的配置做一个初始化,比如日志大小、日志文件地址、日志滚动的周期等等
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
(new LoggingSystemProperties(environment)).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.initializeEarlyLoggingLevel(environment);
this.initializeSystem(environment, this.loggingSystem, this.logFile);
this.initializeFinalLoggingLevels(environment, this.loggingSystem);
this.registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
我们点开其中的几个方法,可以看到都是一些日志文件,或者日志相关的配置
public void apply(LogFile logFile) {
PropertyResolver resolver = this.getPropertyResolver();
this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
this.setSystemProperty("PID", (new ApplicationPid()).toString());
this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
public static LogFile get(PropertyResolver propertyResolver) {
String file = propertyResolver.getProperty("logging.file");
String path = propertyResolver.getProperty("logging.path");
return !StringUtils.hasLength(file) && !StringUtils.hasLength(path) ? null : new LogFile(file, path);
}
总而言之,这个监听器会根据日志的专有配置文件、或者系统配置文件中的日志相关属性,对日志组件做一些初始化
public void onApplicationEvent(ApplicationEvent event) {
if (logger.isDebugEnabled()) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
logger.debug("Application started with classpath: " + this.getClasspath());
} else if (event instanceof ApplicationFailedEvent) {
logger.debug("Application failed to start with classpath: " + this.getClasspath());
}
}
}
如果当前日志级别是debug,就把classpath打印出来
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();
}
}
}
这个监听器虽然接收了当前事件,但是并没有针对它做任何处理
这个监听器之前在分析事件ApplicationStartingEvent的时候也提到过,它在接收到事件ApplicationEnvironmentPreparedEvent的时候会做一些初始化
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);
}
}
进入第一行的getListeners方法,它从Environment中获取了context.listener.classes属性,我们可以在这个属性中配置一些自定义的监听器,获取到类名后实例化,返回实例化后的监听器列表
private List<ApplicationListener<ApplicationEvent>> getListeners(ConfigurableEnvironment environment) {
if (environment == null) {
return Collections.emptyList();
} else {
String classNames = environment.getProperty("context.listener.classes");
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList();
if (StringUtils.hasLength(classNames)) {
Iterator var4 = StringUtils.commaDelimitedListToSet(classNames).iterator();
while(var4.hasNext()) {
String className = (String)var4.next();
try {
Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz, "class [" + className + "] must implement ApplicationListener");
listeners.add((ApplicationListener)BeanUtils.instantiateClass(clazz));
} catch (Exception var7) {
throw new ApplicationContextException("Failed to load context listener class [" + className + "]", var7);
}
}
}
AnnotationAwareOrderComparator.sort(listeners);
return listeners;
}
}
若这一步得到的监听器列表不为空,即我们通过context.listener.classes属性配置了一些监听器,那么它就初始化内部的事件多播器,并把这些监听器添加到多播器中
后续再接受到事件,包括当前的这个事件,会通过这个多播器向我们配置的监听器进行广播
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (environment.containsProperty("spring.mandatory-file-encoding")) {
String encoding = System.getProperty("file.encoding");
String desired = environment.getProperty("spring.mandatory-file-encoding");
if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
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不为空,那么这两个属性指定的文件编码必须一致