Spring框架事件机制的背景和重要性
背景
-
解耦设计:
- 在复杂的应用程序中,组件之间的紧密耦合会导致代码难以维护和扩展。事件机制提供了一种解耦的方式,允许组件通过事件进行通信,而无需直接依赖。
-
异步处理:
- 事件机制支持异步处理,可以在不阻塞主线程的情况下处理耗时操作,提高应用的响应速度和用户体验。
-
基于发布-订阅模式:
- Spring的事件机制采用了发布-订阅模式,使得事件的发布者和订阅者可以独立演化。发布者无需知道谁在监听这些事件,而监听者也不需要知道事件的来源。
重要性
-
增强可维护性:
- 通过事件机制,系统的各个部分可以独立发展和演化,减少了模块之间的依赖性,提高了代码的可维护性。
-
支持事件驱动架构:
- 在微服务架构和现代应用程序中,事件驱动模型越来越受到重视。Spring的事件机制使得构建事件驱动的系统变得简单和高效。
-
提高系统灵活性:
- 开发者可以轻松添加新的事件监听器,而不影响现有的代码逻辑,从而提高了系统的灵活性和扩展性。
-
简化异步操作:
- Spring的事件机制支持异步事件处理,简化了多线程编程的复杂性,使得开发者可以更专注于业务逻辑的实现。
-
丰富的生态系统:
- Spring框架与Spring Boot等项目的结合,为事件机制提供了丰富的配置选项和易用的工具,使得开发者可以快速上手和应用。
Spring事件机制概述
1. 什么是事件机制?
事件机制是一种用于组件之间通信的模式,它允许系统中的不同部分通过事件进行交互,而不需要直接调用彼此的接口。这种机制在许多现代软件架构中被广泛使用,特别是在解耦和异步处理的场景中。
2. Spring事件机制的基本概念
-
事件(Event):表示发生的某个动作或状态变化,通常是一个POJO(普通Java对象),可以包含事件相关的数据。
-
事件发布者(Publisher):负责发布事件的组件。在Spring中,任何Spring管理的bean都可以充当事件发布者。
-
事件监听器(Listener):负责响应特定事件的组件。当某个事件被发布时,所有注册到该事件的监听器都会被调用。
3. 事件的工作流程
-
发布事件:事件发布者通过
ApplicationEventPublisher
接口发布事件。 -
监听事件:事件监听器通过实现
ApplicationListener
接口或使用@EventListener
注解来注册自己,以便接收特定的事件。 -
处理事件:当事件被发布后,Spring框架会自动调用所有注册的监听器,以处理该事件。
4. Spring事件机制的优点
-
解耦合:发布者和监听者之间没有直接的依赖关系,使得系统更易于维护和扩展。
-
灵活性:可以动态添加或移除事件监听器,无需修改事件发布者的代码。
-
异步处理:支持异步事件处理,提高系统的响应能力和性能。
5. 应用场景
-
状态变化通知:当某个业务状态发生变化时,通知相关组件进行相应的处理。
-
系统集成:在微服务架构中,不同服务之间可以通过事件进行异步通信。
-
复杂业务流程:在复杂的业务流程中,事件机制可以帮助管理各个步骤之间的调用和反馈。
通过掌握Spring的事件机制,开发者能够更有效地构建灵活和可扩展的应用程序,从而提升开发效率和系统的可维护性。
个人理解总结:
我们为什么要用事件发布机制?
举一个实际例子,我们正在开发一个商城业务系统,有订单模块,有余额支付模块,我们查完订单的商品明细,每一种商品对应自己的税收分类编码,开发票时需要根据税收分类编码来设置税率,有各种税率,包括0税率,免税。由于公司业务体量不大,开票接口无法直接对税局,只能对三方,因此我们开票比较谨慎,需要管理员审核后才开。
那么大致的逻辑就是,我们的接口入参是一个订单编码
查看订单编号对应的商品明细,遍历商品明细,查找每个商品的税收分类编号,设置税率
将上述组装好的数据加入发票表中,并为该条数据设置一个唯一的请求ID(幂等性考虑,防止同一条数据重复申请开票,造成税务风险),此时属于待审核状态
管理员在后台审核通过,我们将数据组装好,使用RestTemplate模板来发送请求,开票
上述操作,涉及到新增接口,查询主表接口,查询商品明细接口,查询税收分类编码接口,修改发票主表接口,请求第三方接口,发送邮件接口,发送短信通知接口。
突然有一天接到新的需求,项目马上演示,开票不需要审核,直接开票,并且立刻就要改完这个功能,那么这样的话前端申请开票,后端需要直接开票,不采用任何设计模式的情况下,大概是要写一个耦合性非常高的service,代码异常之乱,并且如果需要重新改成审核开票,可能会出非常多的bug,处在这样的情况下,事件发布机制就能无缝衔接以上这些操作,并且松耦合,符合设计原则:对修改关闭,对扩展开放。
不使用任何框架的情况下,我们怎么实现一个监听器模式呢?
1,定义一个事件类,内容和实体类一样,只需要各种属性,一个有参构造器,调用Event类的构造器,作为被发布的事件源,事件类的作用是承担接口入参的数据源。
2,定义一个监听器接口,提供一个方法,入参就是事件类,主要是给实现层来处理自己的逻辑
2,定义一个发布类,这个类最大的特点是持有一个List<监听器接口>列表,提供两个方法,先注册,后发布,这个类提供两个方法,注册和发布方法。注册的目的是初始化监听器实例,发布方法主要是循环执行List<监听器>,将监听器的监听方法遍历执行。这也就是为什么,发布类执行完,监听器里面就执行。一点都不神奇!
Spring中的实际使用:
1,按照上述思路,先定义事件类
@Getter @Setter public class ExpressCallBackEvent extends ApplicationEvent { private ExpressNotifyDTO dto; public ExpressCallBackEvent(ExpressNotifyDTO dto) { super(dto); this.dto = dto; } }
2,然后定义监听器,监听器就只需要让实现层自己处理逻辑
@Component public class ExpressCallBackListener { @Autowired private IExpressOrderService expressOrderService;//依赖注入实现层 @EventListener public void expressCallBack(ExpressCallBackEvent event) { expressOrderService.updateExpressOrderByWx(event.getDto());//实现层自己的方法 } }
3,发布类发布事件,这里直接用spring上下文来发布
ExpressNotifyDTO notifyDTO = new ExpressNotifyDTO(); notifyDTO.setCustomOrderNo(dto.getUserWalletPay().getRelatedOrderId()+""); notifyDTO.setPayment_number("userWallet_"+dto.getUserWalletPay().getAmount()); notifyDTO.setTotal(dto.getUserWalletPay().getAmount().multiply(new BigDecimal("100")).intValue()); //applicationContext是实现层依赖注入进来的,作为发布类 applicationContext.publishEvent(new ExpressCallBackEvent(notifyDTO));
好了,简简单单理解并实现事件发布订阅。
上述实际案例中,最后是通过Aspect环绕通知来解决各个service逻辑组装的,简单贴一下代码算了,环绕通知本篇不做解释,这样就相当快速完成了各个逻辑的合并,既便要改回原来的,只要修改Aspect就可以了,各种操作切换自如。
//演示使用 @Aspect @Component public class AutoInvoiceAspect { @Autowired private ApplicationContext applicationContext; @Pointcut("execution(* com.smt.web.service.*.addInvoiceAndManyProducts(..))") public void autoInvoicePointcut() {} @Pointcut("execution(* com.smt.web.service.*.addFarmerInvoice(..))") public void autoInputInvoicePointcut() {} /** * 普通开票的切面方法 * @param joinPoint * @return * @throws Throwable */ //autoInvoicePointcut()是切点表达式的写法,写法多种多样 //ProceedingJoinPoint参数也可以换成JoinPoint,有兴趣自行研究这两者的区别(提示:取决于接口是否有返回值) @Around("autoInvoicePointcut()") public Object invoice(ProceedingJoinPoint joinPoint) throws Throwable { Object result = joinPoint.proceed(); applicationContext.publishEvent(new InvoiceEvent(1,(Long) result)); return result; } /** * 进项发票切面方法 * @param joinPoint * @return * @throws Throwable */ @Around("autoInputInvoicePointcut()") public Object inputInvoice(ProceedingJoinPoint joinPoint) throws Throwable { Object result = joinPoint.proceed(); applicationContext.publishEvent(new InvoiceEvent(2,(Long) result)); return result; }标签:指南,Spring,开票,接口,事件,监听器,机制,监听 From: https://blog.csdn.net/Ta20220617/article/details/143433779