Spring学习笔记(4)Spring 事件原理及其应用

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

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

首先了解几个概念:

Spring学习笔记(4)Spring 事件原理及其应用

Spring 事件类结构

Spring学习笔记(4)Spring 事件原理及其应用

1. 事件类

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

Spring学习笔记(4)Spring 事件原理及其应用

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 ApplicationContext 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也有特定的几个子类,来表示容器启动、刷新、停止以及关闭事件:

Spring学习笔记(4)Spring 事件原理及其应用

2.事件监听器

事件监听器接口中,只定义了一个方法: onApplicationEvent(E event)该方法接收 ApplicationEvent事件对象,在该方法中编写事件的响应处理逻辑。

public interface ApplicationListener extends EventListener {

    /**
     * 接收ApplicationEvent 事件对象
     * 在该方法中编写事件的响应处理逻辑
     * @param event
     */
    void onApplicationEvent(E event);
}

我们同样也可以实现该接口来实现特定的事件监听器功能,比如邮件发送的监听器:

public class MailSenderListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(MailSendEvent event) {
        System.out.println("邮件发送器的 resource:" + event.getSource() + "邮件发送器的 msg:" + event.getMsg());
    }
}

3.事件广播器

事件广播器负责将事件通知监听器注册表中的事件监听器,然后再由事件监听器分别对事件进行响应。Spring中定义了如下接口:

Spring学习笔记(4)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学习笔记(4)Spring 事件原理及其应用

Spring 事件类的应用

那么在Spring中,事件类到底是如何运行的呢?首先我们会在xml配置文件中配置相应的 ApplicationListener类型的监听器,因此在容器启动后,这些类型的bean会被 ApplicationContext容器所识别,它们负责监听容器内发布的对应的 ApplicationEvent类型的事件。


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 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 {

    @Override
    public void onApplicationEvent(MailSendEvent event) {
        System.out.println("邮件发送器的 resource:" + event.getSource() + "邮件发送器的 msg:" + event.getMsg());
    }
}
public class ContextClosedEventListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("关闭事件:" + this.getClass().getName());
    }
}
public class ContextRefreshedEventListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("刷新/打开事件:" + this.getClass().getName());
    }
}

这时,将监听器们注入xml文件中:


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> applicationListeners = new LinkedHashSet<>();

    private BeanFactory beanFactory;

    @Override
    public void addApplicationListener(ApplicationListener listener) {
        applicationListeners.add((ApplicationListener) 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 getApplicationListeners(ApplicationEvent event) {
        LinkedList allListeners = new LinkedList<>();
        for (ApplicationListener listener : allListeners) {
            if (supportsEvent(listener, event)) {
                allListeners.add(listener);
            }
        }
        return allListeners;
    }

    protected boolean supportsEvent(ApplicationListener applicationListener, ApplicationEvent event) {
        Class 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.测试验证

`java
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

Original: https://www.cnblogs.com/EthanWong/p/16465195.html
Author: 归斯君
Title: Spring学习笔记(4)Spring 事件原理及其应用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/574803/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 宝塔nginx无法安装wordfence怎么解决?使用chattr i命令

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/ytkah/p/15543431.htmlAuthor:…

    Java 2023年5月30日
    081
  • 彻底搞懂混而不乱的Java日志体系

    1 混而不乱的Java日志体系 1.1 Java日志发展史 1999年Ceki推出log4j日志框架,并捐给Apache。Ceki也加入Apache组织。据说Apache基金会还曾…

    Java 2023年5月29日
    074
  • JAVA入门基础_从零开始的培训_MYSQL高级

    第1章 Linux下MySQL的安装与使用 Linux下MYSQL的卸载 安装MYSQL之前的准备步骤 正式安装 检查/tmp临时目录权限 安装前检查依赖并卸载mariadb 按照…

    Java 2023年6月9日
    061
  • 题目:完成网站的咨询聊天(UDP)

    题目:完成网站的咨询聊天(UDP) 学生端: package com.gao.Project.Pro6; import java.io.IOException; import ja…

    Java 2023年6月5日
    084
  • WPF 多线程处理(5)

    WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 项目的目录: 以下是Fi…

    Java 2023年6月7日
    083
  • Typora + PicGo + SM.MS实现图片自动上传

    Typora + PicGo + SM.MS实现图片自动上传 1.SM.MS PicGo插件设置 打开PigGo的 &#x63D2;&#x4EF6;&#x8…

    Java 2023年6月9日
    097
  • 简易线程池的实现

    1 线程池 为了避免多线程操作过程种线程频繁申请和释放所带来的性能消耗,可以提前创建多个线程,当有任务到来时从线程池中选择一个线程执行,执行完后继续在线程池中待命。 核心是使用一个…

    Java 2023年5月30日
    0110
  • 数组(Java)

    数组的定义 数组是相同类型数据的有序集合 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成 其中,每个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们 数…

    Java 2023年6月9日
    093
  • 带你深入理解3.4.2的版本更新,对用户带来了什么?

    JNPF快速开发平台迎来了3.4.2 JAVA版本的更新,还有很多朋友可能对我们平台更新的具体内容不甚理解,本文就带你从在线开发的控件组件的角度看看3.4.2版本为用户带来了那些改…

    Java 2023年6月5日
    0101
  • Java开发笔记(一百四十四)实现FXML对应的控制器

    前面介绍了如何通过fxml文件编排界面布局,可是光有静态界面根本没法处理业务,必须另外书写业务逻辑的代码,方能响应各按钮的单击事件,并将业务结果即使呈现到界面上。显然,fxml内部…

    Java 2023年6月6日
    081
  • 快速导入上亿行数据文件到数据库表(使用 JDBC 的 executeBatch)

    最近在 cnblogs 网站上,看其他人博客,谈及一个包含很多行(一亿)的大文件,一周之内,将其数据导入到数据库表。 我谈到可以使用 “使用数据库事务,分批 commi…

    Java 2023年6月9日
    087
  • 敏捷培训有感

    一周前参加了个关于敏捷的培训,今天回想起来,记忆最深的是两个游戏环节。 游戏一 组装 10 只同样小狗,每只小狗需要 5 块积木,流水线上 5 个人,每人负责固定的一块积木的拼接。…

    Java 2023年6月16日
    079
  • JAVA变量、常量以及其命名规范

    变量 变量即可以变化的量 Java是一种强类型语言,每个变量都必须声明其类型。 Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域。 常量 常量(Consta…

    Java 2023年6月9日
    076
  • MySQL事务隔离级别

    MySQL事务隔离级别 事务 事务是由单独的一个或者多个SQL语句组成,是一个最小的不可再分割的单元,这一组操作里面的所有的执行,要么全部成功、要么全部不成功。如果有一个执行不成功…

    Java 2023年6月15日
    076
  • 在ASP.NET 中调用RSACryptoServiceProvider失败,提示未找到文件

    CspParameters RSAParams = new CspParameters(); RSAParams.Flags = CspProviderFlags.UseMachi…

    Java 2023年6月14日
    059
  • Linux上安装tomcat

    参考https://www.digitalocean.com/community/tutorials/how-to-install-apache-tomcat-8-on-cento…

    Java 2023年6月14日
    090
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球