code-learning/springboot/13-Spring Boot 源码分析-ApplicationListener.md

32 KiB
Raw Blame History

精尽 Spring Boot 源码分析 —— ApplicationListener

1. 概述

本文,我们来补充 《精尽 Spring Boot 源码分析 —— SpringApplication》 文章,并未详细解析的 ApplicationListener 。

2. ApplicationListener

org.springframework.context.ApplicationListener ,应用事件监听器接口。代码如下:

// ApplicationListener.java

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

2.1 SmartApplicationListener

org.springframework.context.event.SmartApplicationListener 接口,实现 ApplicationListener、Ordered 接口,是 Spring3.0 新增的接口,提供了事件类型和来源的判断接口方法。代码如下:

// SmartApplicationListener.java

/**
 * Extended variant of the standard {@link ApplicationListener} interface,
 * exposing further metadata such as the supported event and source type.
 *
 * <p>For full introspection of generic event types, consider implementing
 * the {@link GenericApplicationListener} interface instead.
 *
 * @author Juergen Hoeller
 * @since 3.0
 * @see GenericApplicationListener
 * @see GenericApplicationListenerAdapter
 */
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	/**
	 * 事件类型
	 * Determine whether this listener actually supports the given event type.
	 * @param eventType the event type (never {@code null})
	 */
	boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

	/**
	 * 事件来源
	 * Determine whether this listener actually supports the given source type.
	 * <p>The default implementation always returns {@code true}.
	 * @param sourceType the source type, or {@code null} if no source
	 */
	default boolean supportsSourceType(@Nullable Class<?> sourceType) {
		return true;
	}

	/**
	 * Determine this listener's order in a set of listeners for the same event.
	 * <p>The default implementation returns {@link #LOWEST_PRECEDENCE}.
	 */
	@Override
	default int getOrder() {
		return LOWEST_PRECEDENCE;
	}

}

2.1 GenericApplicationListener

org.springframework.context.event.GenericApplicationListener ,继承 ApplicationListener、Ordered 接口,是 Spring4.2 新增的接口,它增强了对泛型的支持,#supportsEventType(ResolvableType) 方法的参数采用的是可解析类型 ResolvableType 。代码如下:

ResolvableType是 Spring4 提供的泛型操作支持类,通过它可以很容易地获得泛型的实际类型信息,比如类级、字段级等等泛型信息。在 Spring4 的框架中,很多核心类内部涉及的泛型操作大都使用 ResolvableType 类进行处理。

// GenericApplicationListener.java

/**
 * Extended variant of the standard {@link ApplicationListener} interface,
 * exposing further metadata such as the supported event and source type.
 *
 * <p>As of Spring Framework 4.2, this interface supersedes the Class-based
 * {@link SmartApplicationListener} with full handling of generic event types.
 *
 * @author Stephane Nicoll
 * @since 4.2
 * @see SmartApplicationListener
 * @see GenericApplicationListenerAdapter
 */
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	/**
	 * 事件类型【有变动】
	 * Determine whether this listener actually supports the given event type.
	 * @param eventType the event type (never {@code null})
	 */
	boolean supportsEventType(ResolvableType eventType);

	/**
	 * 事件来源
	 * Determine whether this listener actually supports the given source type.
	 * <p>The default implementation always returns {@code true}.
	 * @param sourceType the source type, or {@code null} if no source
	 */
	default boolean supportsSourceType(@Nullable Class<?> sourceType) {
		return true;
	}

	/**
	 * Determine this listener's order in a set of listeners for the same event.
	 * <p>The default implementation returns {@link #LOWEST_PRECEDENCE}.
	 */
	@Override
	default int getOrder() {
		return LOWEST_PRECEDENCE;
	}

}

3. SpringApplication 中的使用

在 SpringApplication 构造方法中,会调用 #getSpringFactoriesInstances(Class<T> type) 方法,获得 ApplicationListener 集合。代码如下:

// SpringApplication.java

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    // <1> 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // <2> 创建对象们
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // <3> 排序对象们
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
  • <1>
    

    处,加载指定 ApplicationListener 类型对应的,在

    META-INF/spring.factories
    

    里的类名的数组。

  • <2> 处,创建对象们。

  • <3> 处,排序对象们。

4. ConfigFileApplicationListener

org.springframework.boot.context.config.ConfigFileApplicationListener ,实现 SmartApplicationListener、Ordered、EnvironmentPostProcessor 接口,实现 Spring Boot 配置文件的加载。

考虑到它非常重要,且复杂,我们单独在 《精尽 Spring Boot 源码分析 —— 配置加载》 详细解析。

5. AnsiOutputApplicationListener

org.springframework.boot.context.config.AnsiOutputApplicationListener ,实现 ApplicationListener、Ordered 接口,在 Spring Boot 环境变量(environment)准备完成以后运行, 如果你的终端支持 ANSI ,设置彩色输出会让日志更具可读性。

不了解“彩色输出”的胖友,可以看看 《Spring Boot日志管理》 文章。

代码如下:

// ConfigFileApplicationListener.java

public class AnsiOutputApplicationListener
		implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { // <1>
//	    if (true) { 
//	        return; // <X>
//        }
		ConfigurableEnvironment environment = event.getEnvironment();
		// <2> 根据环境变量 spring.output.ansi.enabled 的值,设置 AnsiOutput.enabled 属性
		Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class)
                .ifBound(AnsiOutput::setEnabled);
		// <3> 根据环境变量 "spring.output.ansi.console-available 的值,设置 AnsiOutput.consoleAvailable 属性
		AnsiOutput.setConsoleAvailable(environment.getProperty("spring.output.ansi.console-available", Boolean.class));
	}

	@Override
	public int getOrder() {
		// Apply after ConfigFileApplicationListener has called EnvironmentPostProcessors
		return ConfigFileApplicationListener.DEFAULT_ORDER + 1;
	}

}
  • <1> 处,我们可以知道,监听的是 ApplicationEnvironmentPreparedEvent 事件。

  • <2> 处,根据环境变量 "spring.output.ansi.enabled" 的值,设置 AnsiOutput.enabled 属性。这块的逻辑,卡了艿艿很久,一点一点来说。

    • 首先,因为艿艿并没有细看 Binder 的实现代码,所以这块就卡了一会。简单来说,Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class) 代码块的意思是,从 environment 读取 "spring.output.ansi.enabled" 对应的值,并转换成 AnsiOutput.Enabled 类型。其中AnsiOutput.Enabled 的枚举值如下:

      // AnsiOutput#Enabled.java
      
      public enum Enabled {
      
      	/**
           * 自动探测,根据是否支持 ANSI 的功能,来判断是否要彩色输出
           *
           * 【默认值】
           *
      	 * Try to detect whether ANSI coloring capabilities are available. The default
      	 * value for {@link AnsiOutput}.
      	 */
      	DETECT,
      
      	/**
           * 总是开启 ANSI 彩色输出
           *
      	 * Enable ANSI-colored output.
      	 */
      	ALWAYS,
      
      	/**
           * 禁用 ANSI 彩色输出
           *
      	 * Disable ANSI-colored output.
      	 */
      	NEVER
      
      }
      
      • 貌似也没什么问题。但是,让艿艿闷逼的是,为什么结果会是 AnsiOutput.Enabled.ALWAYS ,在 IDEA 环境中。后来,在 environment 中,一个名字是 "systemProperties" 的 MapPropertySource 属性源,里面提供了 "spring.output.ansi.enabled=always" 的配置。

        • 各种全文检索代码,貌似除了测试用例里,没有地方强制赋值了 "spring.output.ansi.enabled"

        • 后来发现,

          "systemProperties"
          

          这个 MapPropertySource 属性源,读取的是

          System#getProperties()
          

          方法,但是为啥里面会有

          "spring.output.ansi.enabled=always"
          

          目前的猜测是IDEA 判断在 Spring Boot 环境下,自动添加进去的!

          • 然后,

            .ifBound(AnsiOutput::setEnabled)
            

            代码段,应该翻译成如下的代码,可能比较易懂:

            Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class)
            .ifBound(new Consumer<Enabled>() {
                @Override
                public void accept(Enabled enabled) {
                    AnsiOutput.setEnabled(enabled);
                }
            });
            
      • 即,Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class) 返回的是 BindResult 对象,然后调用 BindResult#ifBound(Consumer) 方法,将解析到的属性值,赋值到 AnsiOutput.enabled 属性。

        • 😈 真的是有点绕噢。
  • <3> 处,根据环境变量 "spring.output.ansi.console-available" 的值,设置 AnsiOutput.consoleAvailable 属性。

通过这样的方式,后续在 IDEA 中,我们就可以发现日志打印出来的,是带有彩色输出的。如果胖友是个调皮的人,可以尝试打开 <X> 处的三行注释,然后重新运行,就神奇的发现,彩色输出不见了,嘿嘿嘿。

6. LoggingApplicationListener

org.springframework.boot.context.logging.LoggingApplicationListener ,实现 GenericApplicationListener 接口,实现根据配置初始化日志系统 Logger 。

考虑到它非常重要,且复杂,我们单独在 《精尽 Spring Boot 源码分析 —— 日志系统》 详细解析。

7. ClasspathLoggingApplicationListener

org.springframework.boot.context.logging.ClasspathLoggingApplicationListener ,实现 GenericApplicationListener 接口,程序启动时,将 classpath 打印到 debug 日志,启动失败时 classpath 打印到 debug 日志。代码如下:

// ClasspathLoggingApplicationListener.java

public final class ClasspathLoggingApplicationListener implements GenericApplicationListener {

    /**
     * 顺序
     */
	private static final int ORDER = LoggingApplicationListener.DEFAULT_ORDER + 1;

	private static final Log logger = LogFactory.getLog(ClasspathLoggingApplicationListener.class);

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (logger.isDebugEnabled()) {
		    // 如果是 ApplicationEnvironmentPreparedEvent 事件,说明启动成功,打印成功到 debug 日志中
			if (event instanceof ApplicationEnvironmentPreparedEvent) {
				logger.debug("Application started with classpath: " + getClasspath());
            // 如果是 ApplicationFailedEvent 事件,说明启动失败,打印失败到 debug 日志中
			} else if (event instanceof ApplicationFailedEvent) {
				logger.debug("Application failed to start with classpath: " + getClasspath());
			}
		}
	}

	@Override
	public int getOrder() {
		return ORDER;
	}

	@Override
	public boolean supportsEventType(ResolvableType resolvableType) {
		Class<?> type = resolvableType.getRawClass(); // 使用 ResolvableType 类,可以解析当前传入的参数的泛型,从而后的事件类型
		if (type == null) {
			return false;
		}
		// 判断是否需为 ApplicationEnvironmentPreparedEvent 或者 ApplicationFailedEvent 事件
		return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(type)
				|| ApplicationFailedEvent.class.isAssignableFrom(type);
	}

    /**
     * @return 获得 classpath
     */
	private String getClasspath() {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		if (classLoader instanceof URLClassLoader) {
			return Arrays.toString(((URLClassLoader) classLoader).getURLs());
		}
		return "unknown";
	}

}

8. BackgroundPreinitializer

org.springframework.boot.autoconfigure.BackgroundPreinitializer ,实现 ApplicationListener 接口,实现后台提前执行耗时的初始化任务。代码如下:

// BackgroundPreinitializer.java

@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

    /**
     * System property that instructs Spring Boot how to run pre initialization. When the
     * property is set to {@code true}, no pre-initialization happens and each item is
     * initialized in the foreground as it needs to. When the property is {@code false}
     * (default), pre initialization runs in a separate thread in the background.
     * @since 2.1.0
     */
    public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";

    /**
     * 预初始化任务是否已启动
     */
    private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);

    /**
     * 预初始化任务的 CountDownLatch 对象,用于实现等待预初始化任务是否完成
     */
    private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);

    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        // <1> 如果是开启后台预初始化任务,默认情况下开启
        // 并且,是 ApplicationStartingEvent 事件,说明应用正在启动中
        // 并且,是多核环境
        // 并且,预初始化任务未启动
        if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
                && event instanceof ApplicationStartingEvent
                && multipleProcessors()
                && preinitializationStarted.compareAndSet(false, true)) {
            // 启动
            performPreinitialization();
        }
        // <2> 如果是 ApplicationReadyEvent 或 ApplicationFailedEvent 事件,说明应用启动成功后失败,则等待预初始化任务完成
        if ((event instanceof ApplicationReadyEvent
                || event instanceof ApplicationFailedEvent)
                && preinitializationStarted.get()) { // 判断预初始化任务已经启动
            // 通过 CountDownLatch 实现,预初始化任务执行完成。
            try {
                preinitializationComplete.await();
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * @return 判断是否多核环境
     */
    private boolean multipleProcessors() {
        return Runtime.getRuntime().availableProcessors() > 1;
    }
    
    // ... 省略部分代码
}
  • 几个变量,胖友直接看代码注释。
  • <1> 处,满足如下的四个条件(每一行注释,对应一个条件),调用 #performPreinitialization() 方法,启动线程,后台执行预初始化任务。关于 #performPreinitialization() 方法,在 「8.1 performPreinitialization」 详细解析。
  • <2> 处,如果是 ApplicationReadyEvent 或 ApplicationFailedEvent 事件,说明应用启动成功后失败,则等待预初始化任务完成。
  • 总结来说,<1> 处,启动后台任务,<2> 处,保证后台任务执行完成。

8.1 performPreinitialization

#performPreinitialization() 方法,启动线程,后台执行预初始化任务。代码如下:

// BackgroundPreinitializer.java

private void performPreinitialization() {
    try {
        // <1> 创建线程
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                // 安全运行每个初始化任务
                runSafely(new ConversionServiceInitializer());
                runSafely(new ValidationInitializer());
                runSafely(new MessageConverterInitializer());
                runSafely(new MBeanFactoryInitializer());
                runSafely(new JacksonInitializer());
                runSafely(new CharsetInitializer());
                // <3> 标记 preinitializationComplete 完成
                preinitializationComplete.countDown();
            }

            public void runSafely(Runnable runnable) {
                try {
                    runnable.run();
                } catch (Throwable ex) {
                    // Ignore
                }
            }

        }, "background-preinit");
        // <2> 启动线程
        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 完成
        preinitializationComplete.countDown();
    }
}
  • <1> 处,创建一个线程,用于后台调用 #runSafely(Runnable runnable) 方法,“安全”(即发生异常,不进行抛出去)执行每个初始化任务。
  • <2> 处,启动线程。
  • <3> 处,在所有初始化任务都执行完成后,通过标记 preinitializationComplete 完成,从而实现在 #onApplicationEvent(SpringApplicationEvent even)<2> 处的阻塞等待被通知完成。

8.2 初始化任务

9. DelegatingApplicationListener

org.springframework.boot.context.config.DelegatingApplicationListener ,实现 ApplicationListener、Ordered 接口,和 「DelegatingApplicationContextInitializer」 是类似的,只是读取的是环境变量 "context.listener.classes" 对应的 ApplicationContextInitializer 实现类们。

10. ParentContextCloserApplicationListener

org.springframework.boot.builder.ParentContextCloserApplicationListener ,实现 ApplicationListener、ApplicationContextAware、Ordered 接口,容器关闭时发出通知,如果父容器关闭,那么自容器也一起关闭。代码如下:

// ParentContextCloserApplicationListener.java

public class ParentContextCloserApplicationListener implements ApplicationListener<ParentContextAvailableEvent>, ApplicationContextAware, Ordered {

    /**
     * 顺序
     */
	private int order = Ordered.LOWEST_PRECEDENCE - 10;

	private ApplicationContext context;

	@Override
	public int getOrder() {
		return this.order;
	}

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.context = context;
	}

	@Override
	public void onApplicationEvent(ParentContextAvailableEvent event) {
		maybeInstallListenerInParent(event.getApplicationContext()); // <1>
	}
	
	// ... 省略其它方法
}
  • <1> 处,我们可以看到当接收到 ParentContextAvailableEvent 事件后,会调用 #maybeInstallListenerInParent(ConfigurableApplicationContext child) 方法,向父容器添加监听器,监听父容器的关闭事件。详细解析,见 「10.1 maybeInstallListenerInParent」
  • ParentContextAvailableEvent 事件的发布,依赖 org.springframework.boot.builder.ParentContextApplicationContextInitializer 初始化类。默认情况下ParentContextApplicationContextInitializer 类并未使用,所以吧,😈 ParentContextCloserApplicationListener 基本无法生效列。

10.1 maybeInstallListenerInParent

#maybeInstallListenerInParent(ConfigurableApplicationContext child) 方法,向父容器添加监听器,监听父容器的关闭事件。代码如下:

// ParentContextCloserApplicationListener.java

private void maybeInstallListenerInParent(ConfigurableApplicationContext child) {
    // 如果 child 是当前容器
    if (child == this.context
            // 并且父容器是 ConfigurableApplicationContext 类型
            && child.getParent() instanceof ConfigurableApplicationContext) {
        // 向父容器添加监听器,监听父容器的关闭事件
        ConfigurableApplicationContext parent = (ConfigurableApplicationContext) child.getParent();
        parent.addApplicationListener(createContextCloserListener(child)); // <1>
    }
}
  • <1> 处,会调用 #createContextCloserListener(ConfigurableApplicationContext child) 方法,创建 ContextCloserListener 对象。代码如下:

    // ParentContextCloserApplicationListener.java
    
    protected ContextCloserListener createContextCloserListener(ConfigurableApplicationContext child) {
    	return new ContextCloserListener(child);
    }
    
  • <1> 处,创建后的 ContextCloserListener 对象,向父容器 parent 中注册。

10.2 ContextCloserListener

ContextCloserListener ,是 ParentContextCloserApplicationListener 的内部类,实现 ApplicationListener 接口,监听父容器关闭时,关闭自己(容器)。代码如下:

// ParentContextCloserApplicationListener#ContextCloserListener.java

protected static class ContextCloserListener implements ApplicationListener<ContextClosedEvent> {

    private WeakReference<ConfigurableApplicationContext> childContext;

    public ContextCloserListener(ConfigurableApplicationContext childContext) {
        this.childContext = new WeakReference<>(childContext);
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        ConfigurableApplicationContext context = this.childContext.get();
        if ((context != null)
                && (event.getApplicationContext() == context.getParent()) // 如果是父容器
                && context.isActive()) { // 并且当前容器是启动状态
            // 关闭当前容器
            context.close();
        }
    }
}

11. ClearCachesApplicationListener

org.springframework.boot.ClearCachesApplicationListener ,实现 ApplicationListener 接口,实现 ReflectionUtils 的缓存、ClassLoader 的缓存。代码如下:

// ClearCachesApplicationListener.java

class ClearCachesApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 清空 ReflectionUtils 缓存
        ReflectionUtils.clearCache();
        // 清空类加载器的缓存
        clearClassLoaderCaches(Thread.currentThread().getContextClassLoader());
    }

    private void clearClassLoaderCaches(ClassLoader classLoader) {
        if (classLoader == null) {
            return;
        }
        // 同构反射调用 ClassLoader 类的 clearCache 方法,清空它的缓存
        try {
            Method clearCacheMethod = classLoader.getClass().getDeclaredMethod("clearCache");
            clearCacheMethod.invoke(classLoader);
        } catch (Exception ex) {
            // Ignore
        }
        // 如果有父加载器,则父加载器清空缓存
        clearClassLoaderCaches(classLoader.getParent());
    }

}
  • 在接收到容器初始化 ContextRefreshedEvent 事件,触发 ClearCachesApplicationListener 监听器,进行清空缓存。

12. FileEncodingApplicationListener

org.springframework.boot.context.FileEncodingApplicationListener ,实现 ApplicationListener、Ordered 接口在Spring Boot 环境准备完成以后运行,获取环境中的系统环境参数,检测当前系统环境的 file.encodingspring.mandatory-file-encoding 设置的值是否一样,如果不一样则抛出异常; 如果不配置 spring.mandatory-file-encoding 则不检查。代码如下:

// FileEncodingApplicationListener.java

public class FileEncodingApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    private static final Log logger = LogFactory.getLog(FileEncodingApplicationListener.class);

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        // 如果未配置,则不进行检查
        if (!environment.containsProperty("spring.mandatory-file-encoding")) {
            return;
        }
        // 比对系统变量的 `file.encoding` ,和环境变量的 `spring.mandatory-file-encoding` 。
        // 如果不一致,抛出 IllegalStateException 异常
        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 + "'.");
            // 抛出 IllegalStateException 异常
            throw new IllegalStateException(
                    "The Java Virtual Machine has not been configured to use the "
                            + "desired default character encoding (" + desired + ").");
        }
    }

}

13. LiquibaseServiceLocatorApplicationListener

org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener ,实现 ApplicationListener 接口,初始化 Liquibase 的 ServiceLocator 对象。代码如下:

如果不了解 Liquibase 的胖友,可以看看 《一起来学 SpringBoot 2.x | 第二十四篇数据库管理与迁移Liquibase 文章。

// LiquibaseServiceLocatorApplicationListener.java

public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener<ApplicationStartingEvent> {

    private static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorApplicationListener.class);

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        // <1> 如果存在 CustomResolverServiceLocator 类
        if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator",
                event.getSpringApplication().getClassLoader())) {
            new LiquibasePresent().replaceServiceLocator();
        }
    }

    /**
     * Inner class to prevent class not found issues.
     */
    private static class LiquibasePresent {

        public void replaceServiceLocator() {
            // <2> 创建 CustomResolverServiceLocator 对象
            CustomResolverServiceLocator customResolverServiceLocator = new CustomResolverServiceLocator(new SpringPackageScanClassResolver(logger));
            // 设置 ServiceLocator 的 `instance` 属性
            ServiceLocator.setInstance(customResolverServiceLocator);
        }

    }

}
  • <1> 处,判断是否存在 liquibase.servicelocator.CustomResolverServiceLocator 类。通过该判断,可以知道是否引入了 liquibase-core 包,需要使初始化 Liquibase 功能。
  • <2> 处,创建 CustomResolverServiceLocator 对象。当然,如果没有自定义的话,这个对象,返回的就是默认的 Liquibase ServiceLocator 对象。
  • <3> 处,设置到 ServiceLocator 的 instance 属性。

当然,以上的逻辑,胖友选择性了解即可。哈哈哈哈~

666. 彩蛋

严格来说本文并没有写太多具体的内容。更多的是为了后面的内容做一个铺垫同时也让胖友知道Spring Boot 默认提供的 ApplicationListener 实现类。

参考和推荐如下文章: