zl程序教程

您现在的位置是:首页 >  后端

当前栏目

SpringBoot源码解析ApplicationEnvironmentPreparedEvent

2023-09-11 14:19:34 时间

转自:https://blog.csdn.net/m0_37298252/article/details/122355631

最近两篇文章主要分析了ConfigFileApplicationListener对事件ApplicationEnvironmentPreparedEvent的处理,包括EnvironmentPostProcessor扩展点和系统配置文件的加载,而之前也提到过,实际上有很多监听器都会监听该事件的发布,本文对其它几个监听器的相关处理做个简单的介绍

首先看下收到事件的监听器列表

ConfigFileApplicationListener已经介绍地很详细了,接下来对剩下的监听器做逐一分析

AnsiOutputApplicationListener

1     public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
2         ConfigurableEnvironment environment = event.getEnvironment();
3         Binder.get(environment).bind("spring.output.ansi.enabled", Enabled.class).ifBound(AnsiOutput::setEnabled);
4         AnsiOutput.setConsoleAvailable((Boolean)environment.getProperty("spring.output.ansi.console-available", Boolean.class));
5     }

这个监听器主要用来设置日志的颜色,比如默认情况下控制台看到的日志格式如下

如果设置了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,它是一个枚举类

1     public static enum Enabled {
2         DETECT,
3         ALWAYS,
4         NEVER;
5         private Enabled() {
6         }
7     }

绑定的结果存储到BindResult中

 1 public final class BindResult<T> {
 2     ......
 3     private final T value;
 4     ......
 5     public void ifBound(Consumer<? super T> consumer) {
 6         Assert.notNull(consumer, "Consumer must not be null");
 7         if (this.value != null) {
 8             consumer.accept(this.value);
 9         }
10     }
11     ......
12 }

ifBound接收一个Consumer,如果绑定的属性不为空,则调用consumer的处理逻辑
上面传进来的是AnsiOutput::setEnabled,所以就相当于把配置文件中的spring.output.ansi.enabled属性赋给AnsiOutput的enabled变量
而Binder后面的一行代码就直接到Environment中取了spring.output.ansi.console-available赋值给AnsiOutput的consoleAvailable属性

这个监听器就是用来设置日志颜色的,用处不是很大,一般也不会做额外配置
另外由于会在日志中输出表示颜色的分隔符,有可能会对一些日志收集组件产生一定干扰,所以还是慎用

LoggingApplicationListener

 1     public void onApplicationEvent(ApplicationEvent event) {
 2         if (event instanceof ApplicationStartingEvent) {
 3             this.onApplicationStartingEvent((ApplicationStartingEvent)event);
 4         } else if (event instanceof ApplicationEnvironmentPreparedEvent) {
 5             this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
 6         } else if (event instanceof ApplicationPreparedEvent) {
 7             this.onApplicationPreparedEvent((ApplicationPreparedEvent)event);
 8         } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent)event).getApplicationContext().getParent() == null) {
 9             this.onContextClosedEvent();
10         } else if (event instanceof ApplicationFailedEvent) {
11             this.onApplicationFailedEvent();
12         }
13     }

对当前事件的处理,在方法onApplicationEnvironmentPreparedEvent中

1     private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
2         if (this.loggingSystem == null) {
3             this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
4         }
5         this.initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
6     }

loggingSystem在之前处理ApplicationStartingEvent事件的时候已经做了初始化,我们之前对它做过详细的介绍,表示当前系统使用的日志体系是logback,还是log4j,亦或者是JDK自带的日志框架

initialize方法主要是对日志相关的配置做一个初始化,比如日志大小、日志文件地址、日志滚动的周期等等

 1     protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
 2         (new LoggingSystemProperties(environment)).apply();
 3         this.logFile = LogFile.get(environment);
 4         if (this.logFile != null) {
 5             this.logFile.applyToSystemProperties();
 6         }
 7 
 8         this.initializeEarlyLoggingLevel(environment);
 9         this.initializeSystem(environment, this.loggingSystem, this.logFile);
10         this.initializeFinalLoggingLevels(environment, this.loggingSystem);
11         this.registerShutdownHookIfNecessary(environment, this.loggingSystem);
12     }

我们点开其中的几个方法,可以看到都是一些日志文件,或者日志相关的配置

 1     public void apply(LogFile logFile) {
 2         PropertyResolver resolver = this.getPropertyResolver();
 3         this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
 4         this.setSystemProperty("PID", (new ApplicationPid()).toString());
 5         this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
 6         this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
 7         this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
 8         this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
 9         this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
10         this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
11         if (logFile != null) {
12             logFile.applyToSystemProperties();
13         }
14     }
1     public static LogFile get(PropertyResolver propertyResolver) {
2         String file = propertyResolver.getProperty("logging.file");
3         String path = propertyResolver.getProperty("logging.path");
4         return !StringUtils.hasLength(file) && !StringUtils.hasLength(path) ? null : new LogFile(file, path);
5     }

总而言之,这个监听器会根据日志的专有配置文件、或者系统配置文件中的日志相关属性,对日志组件做一些初始化

ClasspathLoggingApplicationListener

1     public void onApplicationEvent(ApplicationEvent event) {
2         if (logger.isDebugEnabled()) {
3             if (event instanceof ApplicationEnvironmentPreparedEvent) {
4                 logger.debug("Application started with classpath: " + this.getClasspath());
5             } else if (event instanceof ApplicationFailedEvent) {
6                 logger.debug("Application failed to start with classpath: " + this.getClasspath());
7             }
8         }
9     }

如果当前日志级别是debug,就把classpath打印出来

BackgroundPreinitializer

 1     public void onApplicationEvent(SpringApplicationEvent event) {
 2         if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore") && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
 3             this.performPreinitialization();
 4         }
 5         if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) && preinitializationStarted.get()) {
 6             try {
 7                 preinitializationComplete.await();
 8             } catch (InterruptedException var3) {
 9                 Thread.currentThread().interrupt();
10             }
11         }
12     }

这个监听器虽然接收了当前事件,但是并没有针对它做任何处理

DelegatingApplicationListener

这个监听器之前在分析事件ApplicationStartingEvent的时候也提到过,它在接收到事件ApplicationEnvironmentPreparedEvent的时候会做一些初始化

 1     public void onApplicationEvent(ApplicationEvent event) {
 2    
 3         if (event instanceof ApplicationEnvironmentPreparedEvent) {
 4             List<ApplicationListener<ApplicationEvent>> delegates = this.getListeners(((ApplicationEnvironmentPreparedEvent)event).getEnvironment());
 5             if (delegates.isEmpty()) {
 6                 return;
 7             }
 8 
 9             this.multicaster = new SimpleApplicationEventMulticaster();
10             Iterator var3 = delegates.iterator();
11 
12             while(var3.hasNext()) {
13                 ApplicationListener<ApplicationEvent> listener = (ApplicationListener)var3.next();
14                 this.multicaster.addApplicationListener(listener);
15             }
16         }
17 
18         if (this.multicaster != null) {
19             this.multicaster.multicastEvent(event);
20         }
21 
22     }

进入第一行的getListeners方法,它从Environment中获取了context.listener.classes属性,我们可以在这个属性中配置一些自定义的监听器,获取到类名后实例化,返回实例化后的监听器列表

 1     private List<ApplicationListener<ApplicationEvent>> getListeners(ConfigurableEnvironment environment) {
 2         if (environment == null) {
 3             return Collections.emptyList();
 4         } else {
 5             String classNames = environment.getProperty("context.listener.classes");
 6             List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList();
 7             if (StringUtils.hasLength(classNames)) {
 8                 Iterator var4 = StringUtils.commaDelimitedListToSet(classNames).iterator();
 9 
10                 while(var4.hasNext()) {
11                     String className = (String)var4.next();
12 
13                     try {
14                         Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
15                         Assert.isAssignable(ApplicationListener.class, clazz, "class [" + className + "] must implement ApplicationListener");
16                         listeners.add((ApplicationListener)BeanUtils.instantiateClass(clazz));
17                     } catch (Exception var7) {
18                         throw new ApplicationContextException("Failed to load context listener class [" + className + "]", var7);
19                     }
20                 }
21             }
22 
23             AnnotationAwareOrderComparator.sort(listeners);
24             return listeners;
25         }
26     }

若这一步得到的监听器列表不为空,即我们通过context.listener.classes属性配置了一些监听器,那么它就初始化内部的事件多播器,并把这些监听器添加到多播器中
后续再接受到事件,包括当前的这个事件,会通过这个多播器向我们配置的监听器进行广播

FileEncodingApplicationListener

 1     public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
 2         ConfigurableEnvironment environment = event.getEnvironment();
 3         if (environment.containsProperty("spring.mandatory-file-encoding")) {
 4             String encoding = System.getProperty("file.encoding");
 5             String desired = environment.getProperty("spring.mandatory-file-encoding");
 6             if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
 7                 logger.error("System property 'file.encoding' is currently '" + encoding + "'. It should be '" + desired + "' (as defined in 'spring.mandatoryFileEncoding').");
 8                 logger.error("Environment variable LANG is '" + System.getenv("LANG") + "'. You could use a locale setting that matches encoding='" + desired + "'.");
 9                 logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL") + "'. You could use a locale setting that matches encoding='" + desired + "'.");
10                 throw new IllegalStateException("The Java Virtual Machine has not been configured to use the desired default character encoding (" + desired + ").");
11             }
12         }
13     }

这个监听器用来对文件编码做一个校验,如果我们配置了属性spring.mandatory-file-encoding,并且系统属性file.encoding不为空,那么这两个属性指定的文件编码必须一致