springboot源码分析14-事件发布机制以及应用监听器

简介: 摘要:事件驱动模型,也就是我们经常提到用到的观察者模式。当然也可以将其理解为发布-订阅模型。具体的实现要素有如下几个方面。1、首先是一对多的关系,一是目标对象,多则是观察者对象。

摘要:事件驱动模型,也就是我们经常提到用到的观察者模式。当然也可以将其理解为发布-订阅模型。具体的实现要素有如下几个方面。

1、首先是一对多的关系,一是目标对象,多则是观察者对象。比如报社是一个,而订报者是多个。

2、当目标对象的行为发生变化的时候,多个观察者对象会级联触发并做出相应的处理。换言之,目标对象的行为发生变化的时候,只需要通知一下所有的观察者对象(订阅过的)即可。具体的各个观察者怎么去处理,使用什么方式去处理,并不是目标对象所需要考虑的范畴。也就是说目标与观察者的实现是低耦合。目标对象的职责就是去通知各个观察者,各个观察者的职责是具体做事情的。大家各司其职协调工作。

3、目标对象怎么能在自身状态发生变化的时候,去实时通知到各个观察者呢?无外乎就是如下的两种思路。

实现方案1:

     所有需要通知的观察者去目标对象中注册登记,当目标对象需要通知的时候,查询登记列表中的所有观察者,然后一个个的下发。

实现方案2:

所有需要通知的观察者去目标对象中注册登记,登记的时候告诉目标对象自己需要监听的事件类型,只有是自己注册的事件变化时,才接受通知,否则目标对象的其他事件不要通知这个观察者。

上述的两个方案,方案1强调的是目标对象只要发生行为状态改变,所有的观察者都可以收到通知,并自行处理。方案2有点类似精准投递,比如观察者对象1只监听a事件,那么当目标对象触发b事件的时候不需要通知观察者对象1。两种方案各有优缺点,我个人倾向使用方案2。因为该方案可以根据不同的事件源去通知不同的观察者。

了解了上述的内容之后,接下来我们看一下Springboot中所使用的事件发布机制以及ApplicationListener。

1.1. SpringApplicationRunListener

我们一步到位,直接定位到SpringApplication类中的run方法,相关实现代码如下:

public ConfigurableApplicationContext run(String... args) {

...

SpringApplicationRunListeners listeners = getRunListeners(args);

listeners.starting();

...

}

我们重点看一下getRunListeners方法的处理逻辑,该方法的实例代码如下:

private SpringApplicationRunListeners getRunListeners(String[] args) {

Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };

return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(

pringApplicationRunListener.class, types, this, args));

}

首先看一下getSpringFactoriesInstances方法,代码如下:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,

Class<?>[] parameterTypes, Object... args) {

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

Set<String> names = new LinkedHashSet<>(

SpringFactoriesLoader.loadFactoryNames(type, classLoader));

List<T> instances = createSpringFactoriesInstances(type, parameterTypes,

classLoader, args, names);

AnnotationAwareOrderComparator.sort(instances);//order值越小,越靠前

return instances;

}

上述的代码,前面的系列文章也详细讲解过,就是查询所有META-INF/spring.factories配置文件中key为org.springframework.boot.SpringApplicationRunListener的值,我们找一下spring-boot-2.0.0.M7.jar中的META-INF/spring.factories配置文件,相关配置如下所示:

# Run Listeners

org.springframework.boot.SpringApplicationRunListener=\

org.springframework.boot.context.event.EventPublishingRunListener

因此上述代码执行完毕之后。会通过反射实例化EventPublishingRunListener类,该类的构造函数如下:

private final SimpleApplicationEventMulticaster initialMulticaster;

public EventPublishingRunListener(SpringApplication application, String[] args) {

this.application = application;

this.args = args;

this.initialMulticaster = new SimpleApplicationEventMulticaster();

for (ApplicationListener<?> listener : application.getListeners()) {

this.initialMulticaster.addApplicationListener(listener);

}

}

首先,实例化SimpleApplicationEventMulticaster类,然后调用application对象中的getListeners()方法,并循环将该函数的返回值集合添加到initialMulticaster中。initialMulticaster我们可以将其理解为事件发布器。getListeners()方法中返回的集合在哪里初始化的呢?我们继续回到SpringApplication类的构造函数中。如下所示:

private List<ApplicationListener<?>> listeners;

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

...

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

}

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {

this.listeners = new ArrayList<>();

this.listeners.addAll(listeners);

}

SpringApplication类的构造函数中,也就直接通过getSpringFactoriesInstances方法直接获取到META-INF/spring.factories配置文件中key为org.springframework.context.ApplicationListener的值,我们找一下spring-boot-2.0.0.M7.jar中的META-INF/spring.factories配置文件,相关配置如下所示:

# Application Listeners

org.springframework.context.ApplicationListener=\

org.springframework.boot.ClearCachesApplicationListener,\

org.springframework.boot.builder.ParentContextCloserApplicationListener,\

org.springframework.boot.context.FileEncodingApplicationListener,\

org.springframework.boot.context.config.AnsiOutputApplicationListener,\

org.springframework.boot.context.config.ConfigFileApplicationListener,\

org.springframework.boot.context.config.DelegatingApplicationListener,\

org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\

org.springframework.boot.context.logging.LoggingApplicationListener,\

org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

上述的13个监听类均是监听不同的事件进行处理的,我们也可以自定义一些监听器进行业务处理,添加方式如下所示:

SpringApplication springApplication = new SpringApplication(DemoApplication.class);

   springApplication.addListeners(new ShareniuApplicationListener());

通过上述代码我们可以看出,所有的事件监听器最终存储在SpringApplication类中的listeners集合中。

1.2. Springboot中事件的发布以及监听

接下来,我们来看一下Springboot中事件的发布以及监听。我们继续回归到listeners.starting()方法中。

public void starting() {

for (SpringApplicationRunListener listener : this.listeners) {

listener.starting();

}

}

循环遍历所有的listeners,并依此调用listeners中的starting方法。

注意:listeners是SpringApplicationRunListener类型,并非是ApplicationListener类型,这点一定不要搞混淆了。SpringApplicationRunListener中持有所有的ApplicationListener类型监听器集合。EventPublishingRunListener类中的starting方法代码如下:

public void starting() {

this.initialMulticaster.multicastEvent(

new ApplicationStartingEvent(this.application, this.args));

}

注意这里产生的事件是ApplicationStartingEvent类型,因此只有监听到ApplicationStartingEvent事件的监听器才可以观察到进而进行自己的处理。this.initialMulticaster.multicastEvent方法实现如下:

public void multicastEvent(ApplicationEvent event) {

multicastEvent(event, resolveDefaultEventType(event));

}

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {

ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));

for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {

Executor executor = getTaskExecutor();

if (executor != null) {

executor.execute(() -> invokeListener(listener, event));

}

else {

invokeListener(listener, event);

}

}

}

在构造ApplicationStartedEvent时传给它的基类EventObjectprotected不可序列化属性source。实例化ApplicationStartedEventinstance.getClass()并包装为ResolvableType类型以保存类型信息,并将它和event作为参数传入SimpleApplicationEventMulticastermulticastEvent方法。multicastEvent首先获取ApplicationListener,使用getApplicationListeners方法,这个方法中抛开对listener做了一些缓存类工作外,主要就是将事件和对应的监听器做了下是否支持的验证,返回通过了retrieveApplicationListeners中通过了supportsEvent验证的监听器集合,这里就体现出了ResolvableType的作用,它保存了类型的信息同时对泛型类型也支持。

在整个SpringBoot启动的过程中,会先后出产生如下的几个事件。

ApplicationStartingEvent-->>ApplicationEnvironmentPreparedEvent-->>ApplicationPreparedEvent

ContextRefreshedEvent-->>ApplicationReadyEvent-->>ContextClosedEvent

后续的系列文章,我们对于核心的监听器一个个进行讲解,从而加深印象。

相关文章
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
190 1
|
2月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
4月前
|
前端开发 JavaScript Java
【实操】SpringBoot监听Iphone15邮件提醒,Selenium+Python自动化抢购脚本
本文介绍了一个结合SpringBoot和Python的实用功能,旨在监控iPhone 15的库存状态并通过邮件提醒用户。系统采用SpringBoot监听苹果官网API,解析JSON数据判断是否有货,并展示最近的库存记录。此外,还能自动触发Selenium+Python脚本实现自动化购买。文中详细介绍了技术栈、接口分析、邮件配置及自动化脚本的设置方法。该项目不仅适用于熟悉后端开发的人员,也适合回顾Layui和Jquery等前端技术。
59 0
【实操】SpringBoot监听Iphone15邮件提醒,Selenium+Python自动化抢购脚本
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
119 62
|
1月前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
74 8
|
29天前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
44 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
102 2
|
1月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
51 0
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?