spring监听机制——观察者模式的应用
使用方法
spring监听模式需要三个组件:
1. 事件,需要继承ApplicationEvent,即观察者模式中的"主题",可以看做一个普通的bean类,用于保存在事件监听器的业务逻辑中需要的一些字段;
2. 事件监听器,需要实现ApplicationListener<E extends ApplicationEvent>,即观察者模式中的"观察者",在主题发生变化时收到通知,并作出相应的更新,加泛型表示只监听某种类型的事件;
3. 事件发布器,需要实现ApplicationEventPublisherAware,获取spring底层组件ApplicationEventPublisher,并调用其方法发布事件,即"通知"观察者。
其中,事件监听器和事件发布器需要在springIOC容器中注册。
示例Demo
事件类
import org.springframework.context.ApplicationEvent; /**
* spring监听机制中的"事件"
* created on 2019-04-15
*/
public class BusinessEvent extends ApplicationEvent { //事件的类型
private String type; /**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* 即事件是在哪个对象上发生的
*/
public BusinessEvent(Object source, String type) {
super(source);
this.type = type;
} public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
事件监听器
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component; /**
* spring监听机制中"监听器"
* created on 2019-04-15
*/
@Component
public class BusinessListener implements ApplicationListener<BusinessEvent> { /**
* 监听到事件后做的处理
* @param event
*/
@Override
public void onApplicationEvent(BusinessEvent event) {
System.out.println("监听到事件:" + event.getType());
}
}
事件发布器
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component; /**
* spring事件监听机制中的"事件发布器"
* created on 2019-04-15
*/
@Component
public class BusinessPublisher implements ApplicationEventPublisherAware { //spring提供的事件发布组件
private ApplicationEventPublisher applicationEventPublisher; @Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
} /**
* 发布事件
*/
public void publishEvent(BusinessEvent businessEvent) {
System.out.println("发布事件:" + businessEvent.getType());
this.applicationEventPublisher.publishEvent(businessEvent);
}
}
容器配置类
/**
* spring容器配置类
* 需要在容器中注册事件监听器、事件发布器
* created on 2019-04-15
*/
@ComponentScan(basePackages = {"cn.monolog.bennett.observer.event.listener"})
public class BeanConfig {
}
测试类
/**
* 用于测试spring事件监听
* created on 2019-04-15
*/
public class Test { public static void main(String[] args) {
//创建springIOC容器
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//从容器中获取事件发布器实例
BusinessPublisher businessPublisher = applicationContext.getBean(BusinessPublisher.class);
//创建事件
BusinessEvent businessEvent = new BusinessEvent(new Test(), BusinessType.ALLOT.getName());
//发布事件
businessPublisher.publishEvent(businessEvent);
}
}
源码分析
在观察者模式中,主题发生改变时,会"通知"观察者作出相应的操作,实现方式是获取观察者列表,然后遍历、分别执行一遍其更新方法。那么,在spring事件监听中,事件发生变化时,是如何"通知"到观察者的呢?如上面的demo所述,我们是通过spring的组件ApplicationEventListener接口执行publishEvent方法发布事件的,而这个抽象方法在spring中只有一个实现,就是AbstractrApplicationContext,这是一个容器类。我们来跟进一下这个容器类对于发布事件的实现方法源码:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null"); // Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
} // Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//获取事件广播器、然后广播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} // Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
粗体部分语句:首先获取事件广播器、然后广播事件。
所以问题分为两部分:如何获取事件广播器、怎样广播事件。
1. 获取事件广播器
直接跟进上述语句——getApplicationEventMulticaster(),似乎找不到答案,因为这个方法是直接返回了AbstractApplicationContext类的属性。问题转化为:AbstractApplicationContext类中的事件广播器属性是什么时候被赋值的?这就要从容器创建说起了。springIOC容器创建有一个重要步骤——刷新容器refresh(),就是在AbstractApplicationContext中定义的,这个refresh()中包含了容器创建、初始化的诸多操作,其中两个步骤与事件监听有关,看一下源码
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory); try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
initMessageSource(); // Initialize event multicaster for this context.
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
onRefresh(); // Check for listener beans and register them.
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
} // Destroy already created singletons to avoid dangling resources.
destroyBeans(); // Reset 'active' flag.
cancelRefresh(ex); // Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
第一个步骤是initApplicationEventMulticaster,即初始化事件广播器,继续跟进源码会发现,是先从BeanFactory中获取,如果不存在,就新建一个。第二个步骤是registerListeners,即注册监听器,从容器中获取所有ApplicationEventListener类型的组件,添加进事件广播器。
2. 广播事件
广播事件的方法是写在事件广播器的实现类——SimpleApplicationEventMulticater中的。
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//遍历监听器,分别执行invokeListener
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
从源码中可以看出,SimpleApplicationEventMulticater从容器中获取所有的监听器列表,遍历列表,对每个监听器分别执行invokeListener方法,继续跟进invokeListener方法,它会调用一个doInvokeListener,在这个doInvokeListner中:
@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//调用监听器实现类的onApplicationEvent方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
终于看到我们熟悉的:onApplicationEvent方法,这就是暴露在外层、供我们使用的事件监听方法;
也就是在这里,实现了观察者模式中的——"通知"观察者进行更新的操作。
spring监听机制——观察者模式的应用的更多相关文章
- 深入理解Spring的容器内事件发布监听机制
目录 1. 什么是事件监听机制 2. JDK中对事件监听机制的支持 2.1 基于JDK实现对任务执行结果的监听 3.Spring容器对事件监听机制的支持 3.1 基于Spring实现对任务执行结果的监 ...
- Spring 事件监听机制及原理分析
简介 在JAVA体系中,有支持实现事件监听机制,在Spring 中也专门提供了一套事件机制的接口,方便我们实现.比如我们可以实现当用户注册后,给他发送一封邮件告诉他注册成功的一些信息,比如用户订阅的主 ...
- SpringBoot事件监听机制及观察者模式/发布订阅模式
目录 本篇要点 什么是观察者模式? 发布订阅模式是什么? Spring事件监听机制概述 SpringBoot事件监听 定义注册事件 注解方式 @EventListener定义监听器 实现Applica ...
- Spring ApplicationContext(八)事件监听机制
Spring ApplicationContext(八)事件监听机制 本节则重点关注的是 Spring 的事件监听机制,主要是第 8 步:多播器注册:第 10 步:事件注册. public void ...
- 【spring源码学习】spring的事件发布监听机制源码解析
[一]相关源代码类 (1)spring的事件发布监听机制的核心管理类:org.springframework.context.event.SimpleApplicationEventMulticast ...
- Spring笔记(7) - Spring的事件和监听机制
一.背景 事件机制作为一种编程机制,在很多开发语言中都提供了支持,同时许多开源框架的设计中都使用了事件机制,比如SpringFramework. 在 Java 语言中,Java 的事件机制参与者有3种 ...
- Spring事件发布与监听机制
我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复[资料],即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板. 目录 ...
- Laravel 事件系统用法总结(监听事件,观察者模式)
看这篇文章先复习一下设计模式 : https://www.cnblogs.com/fps2tao/p/9640338.html 在理解了观察者模式后,我们开始正文 Laravel 的事件提供了一个简单 ...
- 7_3.springboot2.x启动配置原理_3.事件监听机制
事件监听机制配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListenerioc容器中的 ...
随机推荐
- [项目实战]训练retinanet(pytorch版)
采用github上star比较高的一个开源实现https://github.com/yhenon/pytorch-retinanet 在anaconda中新建了一个环境,因为一开始并没有新建环境,在原 ...
- 【问题解决方案】visudo: /etc/sudoers is busy, try again later
参考链接: 博客园:visudo: /etc/sudoers is busy, try again later CSDN:Shell 获取进程号并杀掉该进程 注:找到几篇相同的参考内容,都是只有查看进 ...
- Tomcat报java.io.IOException: Broken pipe错误
Tomcat报java.io.IOException: Broken pipe错误,如下图: 解决方案:我的原因是因为网络策略导致出现该问题,即网络端口未启用或被限制.
- elasticsearch——Rest Client
https://www.jianshu.com/p/66b91bec12e3 elasticsearch——Rest Client 0.2372018.05.10 15:23:03字数 1287阅读 ...
- 用HTTP核心模块配置一个静态Web服务器
静态Web服务器的主要功能由ngx_http_core_module模块(HTTP框架的主要成员)实现与core模块类似,可以根据相关模块(如ngx_http_gzip_filter_module.n ...
- spring security There was an unexpected error (type=Forbidden, status=403).
https://blog.csdn.net/qq_27093097/article/details/83190240 spring security There was an unexpected e ...
- flask项目中设置logo
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}&qu ...
- HTML5 入门基础
HTML5概述HTML5於2004年被WHATWG(网页超文本技术工作小组)提出,於2007年被W3C接纳.在2008年1月22日,第一份正式草案已公布.WHATWG表示该规范是目前正在进行的工作,仍 ...
- Mybatis(三)MyBatis 动态SQL
在 MyBatis 3 之前的版本中,使用动态 SQL 需要学习和了解非常多的标签,现在 MyBatis 采用了功能强大的 OGNL( Object-Graph Navigation Language ...
- @transactional注解在什么情况下会失效,为什么?
一,特性: 1,一般在service里加@Transactional注解,不建议在接口上添加,加了此注解后此类会纳入spring事务管理中,每个业务方法执行时,都会开启一个事务,不过都是按照相同的管理 ...