SpringBoot源码解析(九)ApplicationEnvironmentPreparedEvent

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

首先看下收到事件的监听器列表
SpringBoot源码解析(九)ApplicationEnvironmentPreparedEvent_第1张图片
ConfigFileApplicationListener已经介绍地很详细了,接下来对剩下的监听器做逐一分析

AnsiOutputApplicationListener

    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));
    }

这个监听器主要用来设置日志的颜色,比如默认情况下控制台看到的日志格式如下
SpringBoot源码解析(九)ApplicationEnvironmentPreparedEvent_第2张图片
如果设置了spring.output.ansi.enabled=ALWAYS,日志的颜色发生了变化,可读性更好一点
SpringBoot源码解析(九)ApplicationEnvironmentPreparedEvent_第3张图片
这里重点说下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属性

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

LoggingApplicationListener

    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);
    }

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

ClasspathLoggingApplicationListener

    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打印出来
SpringBoot源码解析(九)ApplicationEnvironmentPreparedEvent_第4张图片

BackgroundPreinitializer

    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();
            }
        }
    }

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

DelegatingApplicationListener

这个监听器之前在分析事件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属性配置了一些监听器,那么它就初始化内部的事件多播器,并把这些监听器添加到多播器中
后续再接受到事件,包括当前的这个事件,会通过这个多播器向我们配置的监听器进行广播

FileEncodingApplicationListener

    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不为空,那么这两个属性指定的文件编码必须一致

你可能感兴趣的:(java,springboot源码,spring,spring,boot,java)