Spring学习笔记(4)Spring 事件原理及其应用
在 JDK 中已经提供相应的自定义事件发布功能的基础类:
java.util.EventObject
类 :自定义事件类型java.util.EventListener
接口:事件的监听器
首先了解几个概念:
Spring 事件类结构
1. 事件类
事件类也就是定义发送的内容,比如可以通过继承ApplicationContextEvent
来自定义一个特定事件类。
1.1 ApplicationEvent
类
首先是继承 EventObject
的ApplicationEvent
,通过source来指定事件源:
public abstract class ApplicationEvent extends EventObject {
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ApplicationEvent(Object source) {
super(source);
}
}
1.2 ApplicationContextEvent
类
是主要的容器事件,它有容器启动、刷新、停止以及关闭各种事件的子类。
public class ApplicationContextEvent extends ApplicationEvent {
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ApplicationContextEvent(Object source) {
super(source);
}
/**
* Get the <code>ApplicationContext</code> that the event was raised for.
*/
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
public class ContextClosedEvent extends ApplicationContextEvent{
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ContextClosedEvent(Object source) {
super(source);
}
}
public class ContextRefreshedEvent extends ApplicationContextEvent{
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ContextRefreshedEvent(Object source) {
super(source);
}
}
我们可以通过继承该类来实现,特定的事件类型需求,比如要实现一个邮件发送事件。只需要继承ApplicationContextEvent
即可:
public class MailSendEvent extends ApplicationContextEvent {
private String msg;
public MailSendEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
同时ApplicationContextEvent
也有特定的几个子类,来表示容器启动、刷新、停止以及关闭事件:
2.事件监听器
事件监听器接口中,只定义了一个方法:onApplicationEvent(E event)
该方法接收ApplicationEvent
事件对象,在该方法中编写事件的响应处理逻辑。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* 接收ApplicationEvent 事件对象
* 在该方法中编写事件的响应处理逻辑
* @param event
*/
void onApplicationEvent(E event);
}
我们同样也可以实现该接口来实现特定的事件监听器功能,比如邮件发送的监听器:
public class MailSenderListener implements ApplicationListener<MailSendEvent> {
@Override
public void onApplicationEvent(MailSendEvent event) {
System.out.println("邮件发送器的 resource:" + event.getSource() + "邮件发送器的 msg:" + event.getMsg());
}
}
3.事件广播器
事件广播器负责将事件通知监听器注册表中的事件监听器,然后再由事件监听器分别对事件进行响应。Spring中定义了如下接口:
public interface ApplicationEventMulticaster {
/**
* 添加事件监听器
* @param listener
*/
void addApplicationListener(ApplicationListener<?> listener);
/**
* 移除事件监听器
* @param listener
*/
void removeApplicationListener(ApplicationListener<?> listener);
/**
* 广播事件
* @param event
*/
void multicastEvent(ApplicationEvent event);
}
及其简单实现类SimpleApplicationEventMulticaster
:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster{
public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
setBeanFactory(beanFactory);
}
/**unchecked 表示告诉编译器忽略指定的警告,不用再编译完成后出现警告信息*/
@SuppressWarnings("unchecked")
@Override
public void multicastEvent(ApplicationEvent event) {
for (ApplicationListener applicationListener : getApplicationListeners(event)) {
applicationListener.onApplicationEvent(event);
}
}
}
4.事件发布者
它本身作为事件源,会在合适的时点,将相应事件发布给对应的事件监听器:
public interface ApplicationEventPublisher {
/**
* 通知监听者并发布事件
* @param event
*/
void publishEvent(ApplicationEvent event);
}
在Spring容器事件中,ApplicationContext
接口定义继承了ApplicationEventPublisher
接口,所以实际上AbstractApplicationContext
在事件中承担了事件发布者的角色。
但是在实际上具体实现事件的发布和事件监听器注册方面,将功能转接给ApplicationEventMulticaster
接口,最终具体实现则放在AbstractApplicationEventMulticaster
的实现类中:
Spring 事件类的应用
那么在Spring中,事件类到底是如何运行的呢?首先我们会在xml配置文件中配置相应的ApplicationListener
类型的监听器,因此在容器启动后,这些类型的bean会被ApplicationContext
容器所识别,它们负责监听容器内发布的对应的ApplicationEvent
类型的事件。
<bean class="cn.ethan.springframework.test.event.ContextRefreshedEventListener"/>
<bean class="cn.ethan.springframework.test.event.MailSenderListener"/>
<bean class="cn.ethan.springframework.test.event.ContextClosedEventListener"/>
在AbstractApplicationContext
的refresh()
方法中可以看到自动注册的内容:
public void refresh() throws BeansException {
// 6. 初始化事件发布者
initApplicationEventMulticaster();
// 7. 注册事件监听器
registerListeners();
// 9. 发布容器刷新完成事件
finishRefresh();
}
private void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);
}
private void registerListeners() {
Collection<ApplicationListener> applicationListeners = getBeansOfType(ApplicationListener.class).values();
for (ApplicationListener listener : applicationListeners) {
applicationEventMulticaster.addApplicationListener(listener);
}
}
private void finishRefresh() {
publishEvent(new ContextRefreshedEvent(this));
}
public void publishEvent(ApplicationEvent event) {
applicationEventMulticaster.multicastEvent(event);
}
所以在ApplicationContext
容器启动时,会自动注册EventListener
类型的 Bean,一旦检测到有ApplicationContextEvent
类型的事件发布,将通知这些注册到容器的EventListener
应用实例
下面将构建一个发送邮件的Spring事件实例:
1. 邮件发送事件MailSendEvent
public class MailSendEvent extends ApplicationContextEvent {
private String msg;
public MailSendEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
2.邮件发送事件监听器MailSendListener
(邮件发送事件)、ContextRefreshedEventListener
(容器刷新事件) 和 ContextClosedEventListener
(容器关闭事件)
public class MailSenderListener implements ApplicationListener<MailSendEvent> {
@Override
public void onApplicationEvent(MailSendEvent event) {
System.out.println("邮件发送器的 resource:" + event.getSource() + "邮件发送器的 msg:" + event.getMsg());
}
}
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("关闭事件:" + this.getClass().getName());
}
}
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("刷新/打开事件:" + this.getClass().getName());
}
}
这时,将监听器们注入xml文件中:
<bean class="cn.ethan.springframework.test.event.ContextRefreshedEventListener"/>
<bean class="cn.ethan.springframework.test.event.MailSenderListener"/>
<bean class="cn.ethan.springframework.test.event.ContextClosedEventListener"/>
3.邮件发送事件发布者
事件发布者ApplicationEventPublisher
,因为前面提到,applicationContext
继承了ApplicationEventPublisher
,而applicationContext
将事件发布功能委托给了ApplicationEventMulticaster
,容器在启动开始就会检查是否存在名称为applicationEventMulticaster
的 ApplicationEventMulticaster
对象实例,如果有就使用提供的实现,没有则默认初始化一个SimpleApplicationEventMulticaster
作为将会使用的ApplicationEventMulticaster
/**
* @description: 实现了事件监听器的管理功能
* @author: wjw
* @date: 2022/7/9
*/
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {
public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();
private BeanFactory beanFactory;
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);
}
@Override
public void removeApplicationListener(ApplicationListener<?> listener) {
applicationListeners.remove(listener);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* 获得监听器
* @param event
* @return
*/
protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
LinkedList<ApplicationListener> allListeners = new LinkedList<>();
for (ApplicationListener<ApplicationEvent> listener : allListeners) {
if (supportsEvent(listener, event)) {
allListeners.add(listener);
}
}
return allListeners;
}
protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {
Class<? extends ApplicationListener> listenerClass = applicationListener.getClass();
/**根据不同实例化类型,判断后获取对应目标 class*/
Class<?> targetClass = ClassUtils.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;
Type genericInterface = targetClass.getGenericInterfaces()[0];
Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
String className = actualTypeArgument.getTypeName();
Class<?> eventClassName;
try {
eventClassName = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException("wrong event class name: " + className);
}
return eventClassName.isAssignableFrom(event.getClass());
}
}
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster{
public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
setBeanFactory(beanFactory);
}
/**unchecked 表示告诉编译器忽略指定的警告,不用再编译完成后出现警告信息*/
@SuppressWarnings("unchecked")
@Override
public void multicastEvent(ApplicationEvent event) {
for (ApplicationListener applicationListener : getApplicationListeners(event)) {
applicationListener.onApplicationEvent(event);
}
}
}
4.测试验证
public void test_event() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.publishEvent(new CustomEvent(applicationContext, 110L, "test!"));
System.out.println("-----------------------------------------------------------------");
applicationContext.publishEvent(new MailSendEvent(applicationContext, "邮件发送测试"));
applicationContext.registerShutdownHook();
}
刷新/打开事件:cn.ethan.springframework.test.event.ContextRefreshedEventListener$$EnhancerByCGLIB$$2e5c458
-----------------------------------------------------------------
邮件发送器的 resource:cn.ethan.springframework.context.support.ClassPathXmlApplicationContext@5f2050f6邮件发送器的 msg:邮件发送测试
关闭事件:cn.ethan.springframework.test.event.ContextClosedEventListener$$EnhancerByCGLIB$$fbc2c978
Spring学习笔记(4)Spring 事件原理及其应用的更多相关文章
- Java架构师之路 Spring学习笔记(一) Spring介绍
前言 这是一篇原创的Spring学习笔记.主要记录我学习Spring4.0的过程.本人有四年的Java Web开发经验,最近在面试中遇到面试官总会问一些简单但我不会的Java问题,让我觉得有必要重新审 ...
- spring学习笔记(一) Spring概述
博主Spring学习笔记整理大部分内容来自Spring实战(第四版)这本书. 强烈建议新手购入或者需要电子书的留言. 在学习Spring之前,我们要了解这么几个问题:什么是Spring?Spring ...
- Spring 学习笔记(2) Spring Bean
一.IoC 容器 IoC 容器是 Spring 的核心,Spring 通过 IoC 容器来管理对象的实例化和初始化(这些对象就是 Spring Bean),以及对象从创建到销毁的整个生命周期.也就是管 ...
- [Spring学习笔记 4 ] AOP 概念原理以及java动态代理
一.Spring IoC容器补充(1) Spring IoC容器,DI(依赖注入): 注入的方式:设值方法注入setter(属性注入)/构造子注入(构造函数传入依赖的对象)/字段注入Field(注解) ...
- Spring学习笔记之Spring概述
概述 Spring是一个java应用最广的开源框架,它是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Deve ...
- [Spring学习笔记 5 ] Spring AOP 详解1
知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...
- 1.1(Spring学习笔记)Spring基础(BeanFactory、ApplicationContext 、依赖注入)
1.准备工作 下载Spring:http://repo.spring.io/libs-release-local/org/springframework/spring/ 选择需要下载的版本 ...
- Spring学习笔记-Hello Spring
实现原理 采用XML方式配置Bean的时候,Bean的定义和实现分离的,采用注解的方式可以将两者合为一体,Bean的定义信息直接以注解形式定义在实现类中,从而实现了零配置. 控制反转是一种通过描述(X ...
- Spring学习笔记之五----Spring MVC
Spring MVC通常的执行流程是:当一个Web请求被发送给Spring MVC Application,Dispatcher Servlet接收到这个请求,通过HandlerMapping找到Co ...
- Spring学习笔记之 Spring IOC容器(二) 之注入参数值,自动组件扫描方式,控制Bean实例化方式,使用注解方式
本节主要内容: 1. 给MessageBean注入参数值 2. 测试Spring自动组件扫描方式 3. 如何控制ExampleBean实例化方式 4. 使用注解方式重构Jdb ...
随机推荐
- python学习-Day38-HTTP
目录 HTTP简介 可以充当客户端的有哪些 HTTP 工作原理 HTTP协议 HTTP协议四大特性 数据格式 请求格式: 响应格式: HTTP 请求方法 HTTP 状态码分类 响应分为五类: HTTP ...
- 自学java的困难
在自学的一些基础阶段,倒是没什么太大的问题,但是在想搞一个项目的时候,就显得手足无措了.因为,很多博主讲的的那些,都行需要一定的条件,比如前端框架,数据库的数据等等. 简单一点的SSM框架整合相对简单 ...
- 一文看懂二层接口、三层接口、PVID及VLANIF
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 文章来源:朱仕耿个人博客 一位同事问了个关于二层和三层端口的概念及区分,以及关于VLANIF. ...
- Java学习笔记-基础语法Ⅲ
继承:子类使用extends来继承父类,子类可以有父类的内容,还可以有子类自己特有的内容 继承的好处: 提高了代码的复用性(多个类相同的成员可以放到同一个类中) 提高了代码的维护性(如果方法的代码需要 ...
- springCloud 微服务通过minio实现文件上传和文件下载接口
直接上代码吧,好多文章的下载都写的不明不白的,让人理解错,气死了!! 文件上传功能 文件上传很简单,首先你得部署好minio,然后写好配置信息,我的是动态读取nacos上配置的yml @Autowir ...
- 什么叫做 Docker
什么叫做 Docker 本文写于 2020 年 11 月 5 日 没有人会喜欢环境配置 在去年的时候我开始学习 Python,并利用 Python 制作了一些小工具.但问题是我很难让别人去用我的软件, ...
- 有了 Promise 和 then,为什么还要使用 async?
有了 Promise 和 then,为什么还要使用 async? 本文写于 2020 年 5 月 13 日 最近代码写着写着,我突然意识到一个问题--我们既然已经有了 Promise 和 then,为 ...
- mysql事务管理和mysql用户管理
1.什么是事务? 事务是一条或者是一组语句组成一个单元,这个单元要么全部执行,要么全不执行. 2.事务特性:ACID: A:atomicity原子性:整个事务中的所有操作要么全部成功执行,要么全部失败 ...
- 安装Iftop到CentOS(YUM)
iftop是Linux系统下实时流量监控工具. 运行环境 系统版本:CentOS Linux release 7.6.1810 (Core) 软件版本:Python 硬件要求:无 安装过程 1.安装i ...
- Es图形化软件使用之ElasticSearch-head、Kibana,Elasticsearch之-倒排索引操作、映射管理、文档增删改查
今日内容概要 ElasticSearch之-ElasticSearch-head ElasticSearch之-安装Kibana Elasticsearch之-倒排索引 Elasticsearch之- ...