在 JDK 中已经提供相应的自定义事件发布功能的基础类:

  • java.util.EventObject类 :自定义事件类型
  • java.util.EventListener接口:事件的监听器

首先了解几个概念:

Spring 事件类结构

1. 事件类

事件类也就是定义发送的内容,比如可以通过继承ApplicationContextEvent来自定义一个特定事件类。

1.1 ApplicationEvent

首先是继承 EventObjectApplicationEvent,通过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"/>

AbstractApplicationContextrefresh()方法中可以看到自动注册的内容:

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,容器在启动开始就会检查是否存在名称为applicationEventMulticasterApplicationEventMulticaster对象实例,如果有就使用提供的实现,没有则默认初始化一个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 事件原理及其应用的更多相关文章

  1. Java架构师之路 Spring学习笔记(一) Spring介绍

    前言 这是一篇原创的Spring学习笔记.主要记录我学习Spring4.0的过程.本人有四年的Java Web开发经验,最近在面试中遇到面试官总会问一些简单但我不会的Java问题,让我觉得有必要重新审 ...

  2. spring学习笔记(一) Spring概述

    博主Spring学习笔记整理大部分内容来自Spring实战(第四版)这本书.  强烈建议新手购入或者需要电子书的留言. 在学习Spring之前,我们要了解这么几个问题:什么是Spring?Spring ...

  3. Spring 学习笔记(2) Spring Bean

    一.IoC 容器 IoC 容器是 Spring 的核心,Spring 通过 IoC 容器来管理对象的实例化和初始化(这些对象就是 Spring Bean),以及对象从创建到销毁的整个生命周期.也就是管 ...

  4. [Spring学习笔记 4 ] AOP 概念原理以及java动态代理

    一.Spring IoC容器补充(1) Spring IoC容器,DI(依赖注入): 注入的方式:设值方法注入setter(属性注入)/构造子注入(构造函数传入依赖的对象)/字段注入Field(注解) ...

  5. Spring学习笔记之Spring概述

    概述   Spring是一个java应用最广的开源框架,它是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Deve ...

  6. [Spring学习笔记 5 ] Spring AOP 详解1

    知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...

  7. 1.1(Spring学习笔记)Spring基础(BeanFactory、ApplicationContext 、依赖注入)

    1.准备工作 下载Spring:http://repo.spring.io/libs-release-local/org/springframework/spring/    选择需要下载的版本    ...

  8. Spring学习笔记-Hello Spring

    实现原理 采用XML方式配置Bean的时候,Bean的定义和实现分离的,采用注解的方式可以将两者合为一体,Bean的定义信息直接以注解形式定义在实现类中,从而实现了零配置. 控制反转是一种通过描述(X ...

  9. Spring学习笔记之五----Spring MVC

    Spring MVC通常的执行流程是:当一个Web请求被发送给Spring MVC Application,Dispatcher Servlet接收到这个请求,通过HandlerMapping找到Co ...

  10. Spring学习笔记之 Spring IOC容器(二) 之注入参数值,自动组件扫描方式,控制Bean实例化方式,使用注解方式

     本节主要内容:    1. 给MessageBean注入参数值    2. 测试Spring自动组件扫描方式    3. 如何控制ExampleBean实例化方式    4. 使用注解方式重构Jdb ...

随机推荐

  1. Quantexa CDI(场景决策智能)Syneo平台介绍

    Quantexa 大数据服务提供商, 使用实体解析, 关系分析和人工智能技术帮助客户进行数据处理和预防金融犯罪. 企业概览 2016年成立, 当前规模500人 服务特色是场景决策智能CDI(conte ...

  2. Go 项目配置文件的定义和读取

    前言 我们在写应用时,基本都会用到配置文件,从各种 shell 到 nginx 等,都有自己的配置文件.虽然这没有太多难度,但是配置项一般相对比较繁杂,解析.校验也会比较麻烦.本文就给大家讲讲我们是怎 ...

  3. c++ :STL

    基础知识 容器 容器就是一些模板类的集合,不同之处就是容器中封装的是数据结构 1.序列容器 主要有vector向量容器.list列表容器.deque双端队列容器 元素在容器中是无序的 2.排序容器 包 ...

  4. linux中MySQL主从配置(Django实现主从读写分离)

    一 linux中MySQL主从配置原理(主从分离,主从同步) mysql主从配置的流程大体如图: 1)master会将变动记录到二进制日志里面: 2)master有一个I/O线程将二进制日志发送到sl ...

  5. ELK 1.4 logstash各种插件

      kibana各种插件: 1.过虑插件 kv (1)KV插件:接收一个键值数据,按照指定分隔符解析为Logstash 事件中的数据结构,放到事件顶层.  常用字段:    • field_split ...

  6. 535. Encode and Decode TinyURL - LeetCode

    Question 535. Encode and Decode TinyURL Solution 题目大意:实现长链接加密成短链接,短链接解密成长链接 思路:加密成短链接+key,将长链接按key保存 ...

  7. Spring 源码(16)Spring Bean的创建过程(7)属性填充

    知识回顾 上一篇介绍了Spring中三级缓存的singletonObjects.earlySingletonObjects.singletonFactories,Spring在处理循环依赖时在实例化后 ...

  8. 搭建NTP时间服务器~使用NTP同步时间~构建主机间时间自动同步关系

    NTP是一个时间服务器,同时它也是一个时间客户端. 我们可以使用它构建主机与主机之间的时间自动同步环境,保证所有服务器时间一致性. 常用的公共NTP时间服务器有: cn.ntp.org.cn 中国 n ...

  9. 【FineBI】FineBI连接阿里云mysql教程

    因为某些原因需要查看数据信息,之前连接成功一次,今天软件更新了以后发现连接信息丢. 又重新折腾了一下. 主要有2个地方: 1.查看阿里云数据库外网连接地址:打开云数据库RDS-实例列表-管理-数据库连 ...

  10. 做一个能对标阿里云的前端APM工具(下)

    上篇请访问这里做一个能对标阿里云的前端APM工具(上) 样本多样性问题 上一小节中的实施方案是微观的,即单次性的.具体的.但是从宏观上看,我需要保证性能测试是公允的,符合大众预期的.为了达到这种效果, ...