【开发经验】spring事件监听机制关心的同步、异步、事务问题

news2024/12/24 2:57:52

文章目录

    • spring发布订阅示例
    • 同步核心源码分析
    • 如何配置异步
    • 事务问题

        观察者模式又称为发布订阅模式,定义为:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。

        如下图所示,一个场景中,主要流程是用户下单,并且返回操作,但是下单之后,要做很多与主干业务无关的流程,如发送短信、发送邮件、积分增加等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FPfVdOQt-1682166965766)(C:\Users\likuo\AppData\Roaming\Typora\typora-user-images\image-20230422203315173.png)]

        这种模式想必大家多多少少都了解过,但是大家讨论最多的就是通过使用mq中间件实现发布订阅模式,但是如果没有中间件呢?本文重点阐述,在没有中间件的情况下,如何做到使用观察者模式解耦。

spring发布订阅示例

        spring和guava都实现了比较优雅的发布订阅的框架,但是默认开发都会在spring的框架下开发。因此通过spring来实现发布订阅会更加方便一点。

主要业务流程代码,用户完成下单并且返回,并且发布订单下单事件。

    @Autowired
    private ApplicationContext applicationContext;
    public String saveOrder(Order order){
        this.orderMapper.save(order);
        //创建下单事件
        this.applicationContext.publishEvent(new SaveOrderEvent(order));
        return "下单成功";
    }

创建下单事件类,描述该事件信息和当事件发生时可以添加一些通用代码。

// 下单事件
public class SaveOrderEvent  extends ApplicationEvent {

    public SaveOrderEvent(Object source) {
        //编写事件后的代码
        super(source);
    }
}

        事件监听,这里的写法示例两种(我也没研究过其他的写法)。一种是作用在类上,一个类一个监听事件,另一种可以总用在方法上,通过@EventListener来实现监听。

@Component
public class SaveOrderEventListener implements ApplicationListener<SaveOrderEvent> {
    @Override
    public void onApplicationEvent(SaveOrderEvent event) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程name:"+Thread.currentThread().getName()+"类打印信息"+event);
    }
}
@Service
public class OrderService{

  @EventListener(SaveOrderEvent.class)
    public void methodTest(SaveOrderEvent event){
        System.out.println("线程name:"+Thread.currentThread().getName()+"方法中打印"+event);
    }
}

执行代码后,打印日志如下:

线程name:http-nio-6902-exec-3方法中打印com.example.demo.task.SaveOrderEvent[source=Order{orderId=‘12312312’}]
线程name:http-nio-6902-exec-3类打印信息com.example.demo.task.SaveOrderEvent[source=Order{orderId=‘12312312’}]

同步核心源码分析

        我原本以为spring的发布订阅模式是异步的,这可能是我用mq中间件用的太多了缘故吧。实际上,默认情况下spring的发布订阅是同步的(可以通过配置实现异步)。为了更进一步测试他是异步还是同步, 我在一个订阅事件中设置了休眠,然后就因为这个休眠,影响了主流程的响应时间。为此我还去研究了它的源码。最终找到核心源码为下org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            //如果有配置执行器,则为异步,如果没有配置,则为同步。
  			Executor executor = getTaskExecutor();
  			if (executor != null) {
  				executor.execute(() -> invokeListener(listener, event));
  			}
  			else {
  				invokeListener(listener, event);
  			}
  		}
  	}

如何配置异步

        我在寻找了很多资料,大部分博客是使用@Async注解实现,其实仔细观察其源码可发现,官方这种写法,肯定是可以通过配置实现异步。因此,我在此示例两种写法。

方法一:

代码示例如下,自定义一个线程池,并且把线程池设置上即可。个人认为是相对符合官方的配置思路。

 @Bean("taskExecutor")
    public Executor getExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                20,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(10000));
        return executor;
    }

    @Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public ApplicationEventMulticaster initEventMulticaster(@Qualifier("taskExecutor") Executor taskExecutor) {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);
        return simpleApplicationEventMulticaster;
    }

方法2:

        通过@Async注解实现,在使用@Async的时候一般都会自定义线程池,因为@Async的默认线程池为SimpleAsyncTaskExecutor,不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。

Application上添加@EnableAsync注解。然后在方法上添加注解即可@Async即可

@Component
public class SaveOrderEventListener implements ApplicationListener<SaveOrderEvent> {
    @Override
    @Async("taskExecutor")
    public void onApplicationEvent(SaveOrderEvent event) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程name:"+Thread.currentThread().getName()+"类打印信息"+event);
    }
}

事务问题

        异步、解耦和事务在一起本身是一个伪命题,至少我并为发现一套完美的异步事务解决方案。但是,为了追求数据一致性,开发者也是在一直努力着。如果要是想使用spring的异步发布订阅的时候实现数据一致性。也许可以尝试下@TransactionalEventListener。从命名上来看,即可得出,他是一个事件监听加上了事务的扩展。只不过加入了回调的方式来解决,这样就能够在事务进行CommitedRollback…等的时候才会去进行Event的处理,达到事务同步的目的。

   //配置监听,事务commit之后执行。
   @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void methodTest(SaveOrderEvent event){
        System.out.println("线程name:"+Thread.currentThread().getName()+"方法中打印"+event);
    }

        如上,配置了事务执行之后执行。并且它还有很多其他的监听方式,如:BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION。这种的相对于@EventListener功能更加多了一些。毕竟在真实的场景中,经常是有事务存在的,并且为了减小事务的执行时间,要求第三方的接口调用不在事务中执行。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Go】六、并发编程

文章目录 并发编程1、并发介绍2、Goroutine3、runtime包 3、Channel3.1、channel相关信息 4、Goroutine池&#xff08;❌&#xff09;5、定时器6、select多路复用7、并发安全和锁8、Sync9、原子操作&#xff08;atomic包&#xff09; 并发编程 1、并发介绍 1、进程和线程 ​…

心塞,被面试官在朋友圈吐槽了

​前阵子一个后辈小学弟向我诉苦&#xff0c;说自己在参加某大厂测试的时候被面试官怼得哑口无言&#xff0c;场面让他一度十分尴尬。 印象最深的就是下面几个问题&#xff1a; 自动化测试中&#xff0c;如何解决Case依赖&#xff1f;你们公司业务中&#xff0c;自动化和手工分…

“五一”预订量创5年新高!如何制定营销活动引爆门店客流?

作为疫情3年经济复苏后&#xff0c;2023年的第一个长假&#xff0c;今年“五一”的消费需求将全面集中释放&#xff0c;带动全国各地线下实体生意全面复苏。 根据官方平台发布的数据显示&#xff0c;今年五一的旅游订单比疫情前的2019年增长了200%&#xff0c;是近5年预订量最多…

Spring Security 整体架构

Spring Security 整体架构 整体架构 在的架构设计中&#xff0c;认证 和 授权 是分开的&#xff0c;无论使用什么样的认证方式。都不会影响授权&#xff0c;这是两个独立的存在&#xff0c;这种独立带来的好处之一&#xff0c;就是可以非常方便地整合一些外部的解决方案。 认…

(数字图像处理MATLAB+Python)第五章图像增强-第三节:基于照度反射模型的图像增强

文章目录 一&#xff1a;基于同态滤波的增强&#xff08;1&#xff09;概述&#xff08;2&#xff09;程序 二&#xff1a;Retinex理论&#xff08;1&#xff09;Retinex理论概述&#xff08;1&#xff09;SSR&#xff08;单尺度Retinex 算法&#xff09;&#xff08;2&#xf…

Oracle的学习心得和知识总结(二十二)|Oracle数据库Real Application Testing之Database Replay实操(二)

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

操作系统概述及Linux基本指令(1)

目录 一. 操作系统 1.1 什么是操作系统 1.2 操作系统的核心工作 二. Linux的基本指令 2.1 ls指令 -- 打印文件名 2.2 pwd指令 -- 显示路径 2.3 cd指令 -- 进入特定目录 2.4 touch指令 -- 创建普通文件 2.5 mkdir指令 -- 创建路径 2.6 rmdir/rm指令 -- 删除路径或普通…

【GeoDjango框架解析】空间方法的ORM查询

原文作者&#xff1a;我辈理想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 Django数据操作-ORM 第一章 【Django开发入门】ORM的增删改查和批量操作 第二章 【Django开发入门】ORM查询分页以及返回Json格式 文章目录 Django数据…

中国人民大学与加拿大女王大学金融硕士——在职读研该如何平衡学习与工作呢

边工作边考研&#xff0c;对于所有人来说都是个不小的挑战&#xff0c;每年都有大量在职生因为焦躁、压力而中途离场。学习时间碎片化&#xff0c;复习进度特别容易被工作上的事情所打断&#xff0c;再想“重新启动”就会很难。想要节省备考时间建议你读免联考的中外合作办学项…

基于Canvas实现图片的上传和渲染

1 Canvas 1.1 什么是Canvas 是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。例如&#xff0c;它可以用于绘制图表、制作图片构图或者制作简单的动画。 1.2 基本使用 宽高&#xff1a; <canvas id"tutorial" width"150" height&qu…

<C++>C++入门

目录 前言 一、C关键字&#xff08;C98&#xff09; 二、命名空间 1. 命名空间定义 2. 命名空间的使用 3. 命名空间的使用有三种方式&#xff1a; 三、C输入&输出 四、缺省参数 1. 缺省参数概念 2. 缺省参数的分类 3. 缺省参数的应用 五、函数重载 六、引用 1. 引用的概念…

web实验(2)

&#xff08;1&#xff09; 应用html标签和css完成如下所示页面效果&#xff0c;图片见附件。 说明&#xff1a; 内容相对于浏览器居中,宽860px鼠标移动至列表项上&#xff0c;显示背景色#F8F8F8分割线2px solid #ccc&#xff0c;每项高130px第一行文字&#xff1a;20px 黑体…

4.功能权限

基于角色的权限控制&#xff0c;用户分配角色&#xff0c;角色分配菜单。 1. 权限注解 1.基于【权限标识】的权限控制 权限标识&#xff0c;对应 system_menu 表的 permission 字段&#xff0c;推荐格式为 {系统}:{模块}:{操作}&#xff0c;例如说 system:admin:add 标识 sy…

chatgpt智能提效职场办公-ppt怎么做才好看又快

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 制作ppt有几个方面可以考虑&#xff0c;以实现既好看又快速的目的&#xff1a; 使用模板&#xff1a;使用ppt模板可以更快速地制作出一…

JavaScript概述二(Date+正则表达式+Math+函数+面向对象)

1.Date 1.1 new一个Date对象表示当前系统时间 var nownew Date(); console.log(now);1.2 根据传入的时间格式表示时间 var date1new Date(2023-4-20 00:16:40); console.log(date1); 1.3 传入时间毫秒数&#xff0c;返回从1900年1月1日8时&#xff08;东八区&#xff09;X分X…

C语言入门篇——操作符篇

目录 1、操作符分类 2、操作符的属性 3、算术操作符 4、移位操作符 5、位操作符 6、赋值操作符 7、单目操作符 8、关系操作符 9、逻辑操作符 10、条件操作符 11、逗号操作符 12、下标引用、函数调用和结构成员 1、操作符分类 算术操作符&#xff08;&#xff0c;-&…

办公必备!不再被格式问题困扰,轻松搞定文档转换!

大家平时在工作中会需要将文档转换为其他格式吗&#xff1f; 日常工作中&#xff0c;经常碰到需要文件格式转换的情况&#xff0c;对于掌握了一些转换技能的朋友说&#xff0c;文件格式转换自然不在话下 对于不熟练的朋友来说&#xff0c;想要轻松转换文件格式&#xff0c;就…

c++ std::enable_shared_from_this作用

enable_shared_from_this 是什么 std::enable_shared_from_this 是一个类模板&#xff0c;用来返回指向当前对象的shared_ptr智能指针。在说明它的作用前我们可以看一下如下代码&#xff1a; demo.cpp #include <memory> #include <iostream>class A { public:A…

web实验(3)

应用JavaScript编写留言的功能&#xff0c;在文本中输入文字提交后&#xff0c;在下方进行显示。 提示&#xff1a;可将下方内容以列表体现&#xff0c;提交时动态创建列表的项。可使用以下两种方式之一的方法&#xff1a; 使用CreateElenment动态创建li标签及li中的文本 在列…

PADS-LOGIC项目原理图设计

最小板原理图设计 目录 1 菜单与工具使用 2 常用设置 2.1选项卡 2.2 图纸设置 2.3 颜色设置 3 设计技巧 3.1 模块化设计思路 3.2 元件放置 3.3 走线及连接符 4 原理图绘制 4.1 POWER原理图设计 4.2 MCU原理图设计 4.2.1晶振电路 4.2.2复位电路 4.2.3 BOOT电路 …