从源码Debug深入spring事件机制,基于观察者模式仿写spring事件监听骨架

文章目录

  • 1.测试案例
  • 2.DEBUG源码分析
  • 3. 异步监听
  • 4.ApplicationListener子接口
  • 5. 注解支持
  • 6. 基于观察者模式高仿spring事件监听
    • 6.1 先定义自定义一个事件
    • 6.2 定义两个监听器
    • 6.3 定义一个持有所有监听器的对象,类似spring的`SimpleApplicationEventMulticaster`
    • 6.4 事件发布测试

1.测试案例

定义一个事件

package com.example.demo.event;
import org.springframework.context.ApplicationEvent;

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

定义两个listener

package com.example.demo.event;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class Listener1 implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        String source = (String) event.getSource();
        System.out.println(this.getClass().getName() + ":" + source);
    }
}
package com.example.demo.event;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class Listener2 implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        String source = (String) event.getSource();
        System.out.println(this.getClass().getName() + ":" + source);
    }
}

注入spring容器里的ApplicationEventPublisher对象,发布事件

package com.example.demo;


import com.example.demo.event.MyEvent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventPublisherTest {

    @Resource
    private ApplicationEventPublisher eventPublisher;

    @Test
    public void test() {
        eventPublisher.publishEvent(new MyEvent("xxx"));
    }
}

2.DEBUG源码分析

eventPublisher.publishEvent(new MyEvent("xxx"));进去很容易就能找到,可以发现SimpleApplicationEventMulticaster这个事件发布对象持有所有listenter对象及MyEvent对象,
事件发布过程其实就是遍历拿到每个listener对象并调用它自己的onApplicationEvent()方法

SimpleApplicationEventMulticaster类的主要方法:

  • addApplicationListener(ApplicationListener<?> listener) :
  • addApplicationListenerBean(String listenerBeanName):
  • removeApplicationListener(ApplicationListener<?> listener):
  • removeApplicationListenerBean(String listenerBeanName):
  • multicastEvent(ApplicationEvent event):广播事件;
  • multicastEvent(ApplicationEvent event, @Nullable
    ResolvableType eventType):广播事件,指定事件的source类型。

在这里插入图片描述

3. 异步监听

从上图断点可以看到ApplicationEventMulticaster对象持有有taskExecutor字段为null,导致没有异步执行所有监听器。这里需要想办法这个字段设置线程池即可:
springboot默认会配置一个ThreadPoolTaskExecutor对象在容器里,这里把它拿出来设置给ApplicationEventMulticaster对象即可

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;


/**
 * @author Administrator
 */
@SpringBootApplication
public class DemoApplication {

    @Resource
    private SimpleApplicationEventMulticaster eventMulticaster;
    @Resource
    private ThreadPoolTaskExecutor executor;

    @PostConstruct
    public void setEventExecutor() {
        eventMulticaster.setTaskExecutor(executor);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

4.ApplicationListener子接口

还可以实现子类的getOrder方法可以实现多个监听器排序;实现supportsEventType,supportsSourceType可以实现按过滤Class过来或按事件源Source的Class过滤
在这里插入图片描述

5. 注解支持

这里不通过实现接口来实现监听,顺序排序,按事件类型过滤监听,采用更方便的注解实现,且一个类中就可以实现多个监听

package com.example.demo.event;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
public class Listener3 {

    @Order(Integer.MIN_VALUE) // 优先级最高
    @EventListener(MyEvent.class)  // 只监听指定类型的事件
    public void onApplicationEvent(ApplicationEvent event) {
        MyEvent myEvent = (MyEvent) event;
        System.out.println(this.getClass().getName() + ":" + myEvent.getSource());
    }
}

6. 基于观察者模式高仿spring事件监听

6.1 先定义自定义一个事件

Java中已定义观察者模式事件及监听器顶级接口

package com.example.demo.javanativeevent;

import java.util.EventObject;

public class MyJavaNativeEvent extends EventObject {

    public MyJavaNativeEvent(Object source) {
        super(source);
    }
}

6.2 定义两个监听器

因为Java提供的EventListener无具体监听方法,且无合适的子接口,故这里自定义一个类似spirng的子接口NativeEventListener。这样后续事件发布就是遍历这种类型接口并调用onApplicationEvent()方法

package com.example.demo.javanativeevent;

import java.util.EventListener;
import java.util.EventObject;

public interface NativeEventListener extends EventListener {
    void onApplicationEvent(EventObject event);
}
package com.example.demo.javanativeevent;

import java.util.EventObject;


public class JavaNativeListener1 implements NativeEventListener {

    @Override
    public void onApplicationEvent(EventObject event){
        String source = (String)event.getSource();
        System.out.println(this.getClass().getName()+":"+source);
    }
}

6.3 定义一个持有所有监听器的对象,类似spring的SimpleApplicationEventMulticaster

package com.example.demo.javanativeevent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EventObject;
import java.util.List;

/**
 * TODO
 *
 * @author majun
 * @version 1.0
 * @since 2023-08-13 18:50
 */
public class SimpleJavaNativeEventMulticaster {

    public List<NativeEventListener> eventListeners= Collections.synchronizedList(new ArrayList<>(8));


    public void addListener(NativeEventListener listener){
        this.eventListeners.add(listener);
    }
    public void multicastEvent(EventObject eventObject){
        eventListeners.stream().parallel().forEach(listener -> {
           listener.onApplicationEvent(eventObject);
        });
    }
}

6.4 事件发布测试

package com.example.demo.javanativeevent;

/**
 * TODO
 *
 * @author majun
 * @version 1.0
 * @since 2023-08-13 19:01
 */
public class EventPublishTest {


    public static void main(String[] args) {
        // 注册两个listener,这是spirng把这个过程隐藏在spring容器初始化过程中了
        SimpleJavaNativeEventMulticaster eventMulticaster = new SimpleJavaNativeEventMulticaster();
        eventMulticaster.addListener(new JavaNativeListener1());
        eventMulticaster.addListener(new JavaNativeListener2());
        // 发布事件
        eventMulticaster.multicastEvent(new MyJavaNativeEvent("yyyy"));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/72091.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++起飞之路】初级—— auto、范围for循环、宏函数和内联函数

auto、范围for、内联函数、宏函数和nullptr 一、auto — 类型推导的魔法&#xff08;C 11)1、auto 是什么&#xff1f;2、工作原理3、优势4、限制和注意事项 二、范围for (C11)1、基本语法2、优势3、工作原理4、注意事项5、C11&#xff1a; 范围 for 循环的扩展&#xff1a; 三…

数据结构:力扣OJ题

目录 ​编辑题一&#xff1a;链表分割 思路一&#xff1a; 题二&#xff1a;相交链表 思路一&#xff1a; 题三&#xff1a;环形链表 思路一&#xff1a; 题四&#xff1a;链表的回文结构 思路一&#xff1a; 链表反转&#xff1a; 查找中间节点&#xff1a; 本人实力…

找不到资产文件project.assets.json

NuGet 在“obj”文件夹中写入名为 project.assets.json 的文件&#xff0c;.NET SDK 使用该文件来获取有关要传递到编译器的包的信息 。 如果在生成过程中找不到资产文件 project.assets.json&#xff0c;则会发生此错误。 1.执行命令的方式解决 点击工具&#xff0c;分别展开命…

【简单认识zookeeper+kafka分布式消息队列集群的部署】

文章目录 一、zookeeper1、定义2、工作机制3、Zookeeper 特点4、Zookeeper 数据结构5、Zookeeper 应用场景6、Zookeeper 选举机制&#xff08;1&#xff09;第一次启动选举机制&#xff08;2&#xff09;非第一次启动选举机制 7、部署zookeeper群集 二、消息队列概述1、为什么需…

释放AI创作潜能:从大模型训练到高产力应用

文章目录 每日一句正能量前言什么是人工智能生成内容&#xff08;AIGC&#xff09;人工智能生成内容&#xff08;AIGC&#xff09;能做什么为什么要用人工智能生成内容&#xff08;AIGC&#xff09;创作成果用Java实现冒泡排序算法学生信息收集系统学生请假管理系统需求分析教务…

kafka partition的数据文件(offffset,MessageSize,data)

partition中的每条Message包含了以下三个属性&#xff1a; offset&#xff0c;MessageSize&#xff0c;data&#xff0c;其中offset表示Message在这个partition中的偏移量&#xff0c;offset不是该Message在partition数据文件中的实际存储位置&#xff0c;而是逻辑上一个值&…

ApiPost的使用

1. 设计接口 请求参数的介绍 Query:相当于get请求&#xff0c;写的参数在地址栏中可以看到 Body: 相当于 post请求&#xff0c;请求参数不在地址栏中显示。 请求表单类型&#xff0c;用form-data json文件类型&#xff0c;用row 2. 预期响应期望 设置完每一项点一下生成响应…

9-数据结构-栈(C语言版)

数据结构-栈&#xff08;C语言版&#xff09; 目录 数据结构-栈&#xff08;C语言版&#xff09; 1.栈的基础知识 1.入栈&#xff0c;出栈的排列组合 情景二&#xff1a;Catalan函数&#xff08;计算不同出栈的总数&#xff09; 2.栈的基本操作 1.顺序存储 (1)顺序栈-定义…

Centos7.6 安装mysql过程全记录

在centos 7.6上 离线安装mysql 的步骤&#xff0c;可参考下文&#xff1a; 一、查看当前MySQL的安装情况并卸载 1. 查看当前MySQL的安装情况 查找之前是否安装了MySQL rpm -qa|grep -i mysql 2.卸载mysql 如果已经安装mysql&#xff0c;则需要先停止MySQL&#xff0c;再删除…

Redis集群 (三十九)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Redis主从复制 1.1 概念 1.2 作用 1.3 缺点 1.4 流程 1.5 搭建 1.6 验证 二、Reids哨兵模式 2.1 概念 2.2 作用 2.3 缺点 2.4 结构 2.5 搭建 2.6 验证 三、Red…

基于 CentOS 7 构建 LVS-DR 群集 配置nginx负载均衡

环境配置&#xff1a; RHCE客户机192.168.100.146node1lvs192.168.100.145node2RS192.168.100.147node3RS192.168.100.148 配置ipvsadm httpd&#xff1a; [rootnode1 ~]# yum install ipvsadm.x86_64 [rootnode2 ~]# yum install http -y [rootnode2 ~]# systemctl …

SpringBoot案例-部门管理-修改

目录 前言 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文件 思路分析 功能接口开发 控制层&#xff08;Controller类&#xff09; 业务层&#xff08;Service类&#xff09; 业务类 业务实现类 持久层&#xff08;Mapper类&#xff09; 接口测试 前…

centos 7.x 单用户模式

最近碰到 centos 7.9 一些参数设置错误无法启动系统的情况&#xff0c;研究后可以使用单用户模式进入系统进行恢复操作。 进入启动界面&#xff0c;按 e ro 替换为 rw init/sysroot/bin/sh 替换前 替换后 Ctrl-x 进行重启进入单用户模式 执行 chroot /sysroot 可以查看日…

js jquery写一个画板 实现撤回、清空、换色的功能

画布的canvas画板的大小就是这个画板图片的大小 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><metaname"viewport&qu…

云计算|OpenStack|使用VMware安装华为云的R006版CNA和VRM---初步使用(二)

前言&#xff1a; 在前面一篇文章云计算|OpenStack|使用VMware安装华为云的R006版CNA和VRM---初始安装&#xff08;一&#xff09;_华为cna_晚风_END的博客-CSDN博客 介绍了基于VMware虚拟机里嵌套部署华为云的云计算&#xff0c;不过仅仅是做到了在VRM的web界面添加计算节点…

云安全攻防(八)之 Docker Remote API 未授权访问逃逸

Docker Remote API 未授权访问逃逸 基础知识 Docker Remote API 是一个取代远程命令行界面&#xff08;rcli&#xff09;的REST API&#xff0c;其默认绑定2375端口&#xff0c;如管理员对其配置不当可导致未授权访问漏洞。攻击者利用 docker client 或者 http 直接请求就可以…

Spring Boot集成Mybatis Plus通过Pagehelper实现分页查询

文章目录 0 简要说明Pagehelper1 搭建环境1.1 项目目录1.2 项目搭建需要的依赖1.3 配置分页插件拦截器1.4 源代码启动类实体类数据层xml映射文件业务层业务层实现类控制层接口配置swagger请求体 2 可能出现的疑问或者问题2.1 关于total属性疑问2.2 分页不生效问题 3 案例说明3.…

数据库技术--数据库引擎,数据访问接口及其关系详解(附加形象的比喻)

目录 背景数据库引擎Jet数据库&#xff1a;ISAM&#xff1a;ODBC&#xff08;Open Database Connectivity&#xff09;&#xff1a; 数据访问接口ADO&#xff08;ActiveX Data Objects&#xff09;DAO&#xff08;Data Access Objects&#xff09;RDO&#xff08;Remote Data O…

【C语言】操作符详解

目录 一、算数操作符 二、移位操作符 1.左移操作符 2.右移操作符 (1) 逻辑右移 (2) 算术右移 (3)小总结 三、位操作符 四、赋值操作符 五、单目操作符 六、关系操作符 七、逻辑操作符 八、 条件操作符 九、逗号表达式 十、下标引用、函数调用和结构成员 1. [ ]下…

第一百二十八天学习记录:数据结构与算法基础:栈和队列(上)(王卓教学视频)

栈和队列的定义和特点 1、栈和队列是两种常用的、重要的数据结构 2、栈和队列是限定插入和删除只能在表的“端点”进行的线性表 线性表可以在任意一个位置插入和删除&#xff0c;栈只能在最后位置插入和删除 队列 只能删除第一个元素 栈和队列是线性表的子集&#xf…