构造流程源码分析:ApplicationListener加载
ApplicationListener加载
完成了
ApplicationContextlnitializer 的加载之后,便会进行 ApplicationListener 的加载。它的常见应用场景为:当容器初始化完成之后,需要处理一些如数据的加载、初始化缓存、特定任务的注册等操作。而在此阶段,更多的是用于 ApplicationContext 管理 Bean 过程的场景。
Spring 事件传播机制是基于观察者模式(Observer) 实现的。比如,在 ApplicationContext管 理 Bean 生 命 周 期 的 过 程 中 , 会 将 一 些 改 变 定 义 为 事 件 ( ApplicationEvent ) 。
ApplicationContext 通过 ApplicationListener 监听 ApplicationEvent,当事件被发布之后,ApplicationListener 用来对事件做出具体的操作。
ApplicationListener 的整个配置和加载流程与
ApplicationContexthnitializer 完全一致, 也是 先 通 过SpringFactoriesLoader的loadFactoryNames方 法 获 得META-INF/spring.factories 中对应配置,然后再进行实例化,最后将获得的结果集合添加到SpringApplication 的成员变量 listeners 中,代码如下。
private List<ApplicationListener<?>> listeners;
public void setl isteners(Collection<了 extends App. lcationLi stener<?》>li steners)
this. listeners = new Arraylist<>(listeners);
同样的,在调用 setListeners 方法时也会进行覆盖赋值的操作,之前加载的内容会被清除。
下 面 我 们 看 看 ApplicationListener 这里的基本使 用 。ApplicationListener 接 口 和ApplicationEvent 类配合使用,可实现 ApplicationContext 的事件处理。如果容器中存在AplicationListener 的 Bean,当 ApplicationContext 调用 publishEvent 方法时,对应的 Bean会被触发。这就是上文提到的观察者模式的实现。
在接口 ApplicationL istener 中只定义了一个 onAplicationEvent 方法,当监听事件被触发时,onApplicationEvent 方法会被执行,接口 ApplicationListener 的源代码如下。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends Ev
entListener
void onApplicationEvent(E event);}
onApplicationEvent 方法一般用于处理应用程序事件,参数 event 为 ApplicationEvent 的子类,是具体响应(接收到)的事件。
当 ApplicationContext 被初始化或刷新时,会触发 ContextRefreshedEvent 事件,下面我们就实现一-个 ApplicationListener 来监听此事件的发生,代码如下。
@Component //需对该类进行 Bean 的实例化
public class LearnListener implements ApplicationListener<ContextRefreshedE
vent>
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
/打印容中出 Beon8 得空婴中初始化
ystem. out. println(" 监听器获得容器
+ event. getApplica
tion-
Context(). getBeanDefinitionCount());
}
上面的 LearnListener 实现了 ApplicationListener 并监听 ContextRefreshedEvent 事件,当容器创建或刷新时,该监听器的 onApplicationEvent 方法会被调用,并打印出当前容器中 Bean 的数量。
在具体的实战业务中,我们也可以自定义事件,在完成业务之后手动触发对应的事件监听器,也就是手动调用 ApplicationContext 的 publishEvent(ApplicationEvent event)方法。
入口类推断
创 建 SpringApplication 的 最 后 一 步 便 是 推 断 入 口 类 , 我 们 通 过 调 用 自 身 的
deduce-MainApplicationClass 方法来进行入口类的推断。
private Class<?> deduceMainApplicationClass() {
/获取栈元素数组
StackTraceElement[] stackTrace = new Runt imeException() . getStackTrace
()
//遍历栈元素数组
for (StackTraceElement stackTraceElement : stackTrace) {
//匹配第一个 main 方法,并返回
("main" . equals(stackTraceElement,getMethodName())) {
return Class. forName( stackTraceElement . getClassName());
} catch (ClassNotFoundException ex) {
//如果发生异常,忽略该异常,并继续执行
return null;}
该方法实现的基本流程就是先创建一个运行时异常, 然后获得栈数组,遍历栈数组,判断类的方法中是否包含 main 方法。第-个被匹配的类会通过 Class.forName 方法创建对象,并 将 其 被 返 回 , 最 后 在 上 层 方 法 中 将 对 象 赋 值 给 SpringApplication 的 成 员 变 量mainApplicationClass。在遍历过程中如果发生异常,会忽略掉该异常并继续执行遍历操作。
至此,整个 SpringApplication 类的实例化过程便完成 了。
SpringApplication的定制化配置
前面我们学习了 Spring Boot 启动过程中构建 SpringApplication 所做的一系列初始化操作,这些操作都是 Spring Boot 默认配置的。如果在此过程中需要定制化配置,Spring Boot 在SpringApplication 类中也提供了相应的入口。
但正常情况下,如果无特殊需要,采用默认配置即可。
针对定制化配置,Spring Boot 提供了如基于入口类、配置文件、环境变量、命令行参数等多种形式。下面我们了 解一下几种不同的配置形式。
基础配置
基础配置与在 application.properties 文件中的配置-样, 用来修改 SpringBoot 预置的参数。
比如,我们想在启动程序的时候不打印 Banner 信息,可以通过在 application.properties 文件 中 设 置 “spring.main.banner-mode=off 来 进 行 关 闭 。当 然 , 我 们 也 可 以 通 过SpringApplication 提供的相关方法来进行同样的操作。以下是官方提供的关闭 Banner 的代码。
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.clas
s);
app. setBannerMode( Banner .Mode.0FF);
app. run(args);
}
除了前面讲到的 setInitializers 和 setL isteners 方法之外,其他的 Setter 方法都具有类似的功能,比如我们可以通过 setWebApplicationType 方法来代替 Spring Boot 默认的自动类型推断。
针对这些 Setter 方法,SpringBoot 还专门提供了流式处理类 SpringApplicationBuilder,我们将它的功能与 SpringApplication 逐一对照,可知 SpringApplicationBuilder 的优点是使代码更加简洁、流畅。
其他相同配置形式的功能就不再赘述了,我们可通过查看源代码进行进一步的学习。出于集中配置、方便管理的思路, 不建议大家在启动类中配置过多的参数。比如,针对 Banner的设置,我们可以在多处进行配置,但为了方便管理,尽可能的统一在 application.properties文件中。
配置源配置
除了直接通过 Setter 方法进行参数的配置,我们还可以通过设置配置源参数对整个配置文件或配置类进行配置。我们可通过两个途径进行配置:
SpringApplication 构造方法参数或 SpringApplication 提供的 setSources 方法来进行设置。
在 3.3 节 SpringApplication 构造方法参数中已经讲到可以通过 Class<?...primarySources参数来配置普通类。
因此,配置类可通过 SpringApplication 的构造方法来进行指定。但这种方法有一一个弊端就是无法指定 XML 配置和基于 package 的配置。
另外一种配置形式为直接调用 setSources 方法来进行设置,方法源代码如下。
private Set<String> sources = new LinkedHashSet<>();
public void setSources (Set<String> sources) {
Assert. notNull(sources, "Sources must not be nul1");
this. sources = new L inkedHashSet<> ( sources);
}
该方法的参数为 String 集合,可传递类名、package 名称和 XML 配置资源。下面我们以类名为例进行演示。
WithoutAnnoConfiguration 配置类代码如下。
public class WithoutAnnoConfiguration
public WithoutAnnoConfiguration(){
System. out. println( "Wi thoutAnnoConfiguration 对象被创建");
@Value("${ admin. name}")
private String name ;
@Value( "${admin. age}")
private int age;
//省略 getter/setter 方法
}
使用该配置的实例代码如下。
public static void main(String[] args){
SpringApplication app = new SpringApplication(SpringLearnApplication.class);
Set<String> set = new HashSet<>();
set. add (WithoutAnnoConfiguration. class . getName());
app . setSources(set);
ConfigurableApplicat ionContext context = app . run(args);
WithoutAnnoConfiguration bean = context . getBean(WithoutAnnoConfiguration.
class);System. out. println(bean. getName());
}
运行程序,我们在日志中即可看到已经获取到对应类的属性值。
无论是通过构造参数的形式还是通过 Setter 方法的形式对配置源信息进行指定,在 SpringBoot 中都会将其合并。SpringApplication 类中提供了 一个 getAllSources 方法,能够将两者参数进行合并。
public Set<0bject> getAllSources() {
//创建去除的 L inkedHashSet
Set<0bject> allSources = new LinkedHashSet<>();
// primarySources 不为空则加入 Set
if (!CollectionUtils. isEmpty(this. primarySources)) {
allSources . addAll(this . primarySources);
// sources 不为空则加入 Set
if (!CollectionUtils . isEmpty(this. sources)) {
allSources . addAll(this. sources);
//对 Set 进行包装,变为不可变的 Set
return Collections . unmodifiableSet(allSources);}}
关于 SpringApplication 类指定配置及配置源就讲到这里,更多相关配置信息可参考对应章节进行学习。
小结
本章内容重点围绕 SpringApplication 类的初始化过程展开,详细介绍了在初始化过程中Spring Boot 所 进 行 的 操 作 : Web应用类型推断 、 入 口类 推 断 、 默认的
Application-Contextlnitializer 接口加载、默认的 ApplicationListener 加载、SpringApplication类的参数配置功能, 以及针对这些操作我们能够进行的自定义组件及配置。建议大家在学习的过程中可配合相应的实战练习,获得更好的学习效果。
本文给大家讲解的内容是ApplicationListener加载和入口类推断、SpringApplication 的定制化配置
下篇文章给大家讲解的是SpringBoot运行流程源码分析;
觉得文章不错的朋友可以转发此文关注小编;
感谢大家的支持!