一、前言

简介: 在项目实际开发过程中,我们有很多这样的业务场景:一个事务中处理完一个业务逻辑后需要跟着处理另外一个业务逻辑,伪码大致如下:

@Service
public class ProductServiceImpl {...public void saveProduct(Product product) {productMapper.saveOrder(product);notifyService.notify(product);}...
}

很简单并且很常见的一段业务逻辑:首先将产品先保存数据库,然后发送通知。
某一天你们可能需要把新增的产品存到Es中,这时候也需要代码可能变成这样:

@Service
public class ProductServiceImpl {...public void saveProduct(Product product) {productMapper.saveProduct(product);esService.saveProduct(product)notifyService.notify(product);}...
}:

随着业务需求的变化,代码也需要跟着一遍遍的修改。而且还会存在另外一个问题,如果通知系统挂了,那就不能再新增产品了。
对于上面这种情况非常适合引入消息中间件(消息队列)来对业务进行解耦,但并非所有的业务系统都会引入消息中间件(引入会第三方架构组件会带来很大的运维成本)。
Spring提供了事件驱动机制可以帮助我们实现这一需求。

Spring事件驱动

spring事件驱动由3个部分组成

ApplicationEvent:表示事件本身,自定义事件需要继承该类,用来定义事件
ApplicationEventPublisher:事件发送器,主要用来发布事件
ApplicationListener:事件监听器接口,监听类实现ApplicationListener 里onApplicationEvent方法即可,也可以在方法上增加@EventListener以实现事件监听。

实现Spring事件驱动一般只需要三步:
自定义需要发布的事件类,需要继承ApplicationEvent类
使用ApplicationEventPublisher来发布自定义事件
使用@EventListener来监听事件
这里需要特别注意一点,默认情况下事件是同步的。即事件被publish后会等待Listener的处理。如果发布事件处的业务存在事务,监听器处理也会在相同的事务中。如果需要异步处理事件,可以onApplicationEvent方法上加@Aync支持异步或在有@EventListener的注解方法上加上@Aync。

源码实战

• 创建事件

public class ProductEvent extends ApplicationEvent {public ProductEvent(Product product) {super(product);}
}

• 发布事件

@Service
public class ProductServiceImpl implements IproductService {...@Autowiredprivate ApplicationEventPublisher publisher;@Override@Transactional(rollbackFor = Exception.class)public void saveProduct(Product product) {productMapper.saveProduct(product); //事件发布publisher.publishEvent(product);}...
}

• 事件监听

@Slf4j
@AllArgsConstructor
public class ProductListener {private final NotifyService notifyServcie;@Async@Order@EventListener(ProductEvent.class)public void notify(ProductEvent event) {Product product = (Product) event.getSource();notifyServcie.notify(product, "product");}
}• 在SpringBoot启动类上增加@EnableAsync 注解
@Slf4j
@EnableSwagger2
@SpringBootApplication
@EnableAsync
public class ApplicationBootstrap {...
}

• 使用了Async后会使用默认的线程池SimpleAsyncTaskExecutor,一般我们会在项目中自定义一个线程池。

@Configuration
public class ExecutorConfig {/** 核心线程数 */private int corePoolSize = 10;/** 最大线程数  */private int maxPoolSize = 50;/** 队列大小  */private int queueCapacity = 10;/** 线程最大空闲时间   */private int keepAliveSeconds = 150;@Bean("customExecutor")public Executor myExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(corePoolSize);executor.setMaxPoolSize(maxPoolSize);executor.setQueueCapacity(queueCapacity);executor.setThreadNamePrefix("customExecutor-");executor.setKeepAliveSeconds(keepAliveSeconds);// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}

SpringBoot 的事件是基于Spring的事件,所以我们先介绍Spring事件原理。
明白了使用,我们再来看看原理:

1.Spring 事件原理

核心类:
ApplicationEventMulticaster,事件派发器。
ApplicationListener,事件监听类。
ApplicationEvent,事件类。
事件派发器派发事件,事件监听类监听派发的事件。

1.ApplicationEventMulticaster事件派发器的注册时机,何时被注册到Spring容器内部的?

我们找到Spring容器创建Bean的流程中,AbstractApplicationContext#refresh这个方法里:

// Initialize message source for this context.initMessageSource();// 在这一步骤中,初始化了事件派发器// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();

initApplicationEventMulticaster方法:

 protected void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();...// 这一步骤创建了派发器this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);// 注册到单例池里面beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);...}

2.当我们调用了publishEvent(ApplicationEvent event);监听类怎么就会执行了呢?
我们点进该方法里面AbstractApplicationContext#publishEvent():

// 会调用ApplicationEventMulticaster的multicastEvent()方法
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

ApplicationEventMulticaster#multicastEvent():

 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));// 从派发器里面获取线程池对象Executor executor = getTaskExecutor();// getApplicationListeners(event, type) 或获取该事件对象类型的所有监听器for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {// 线程池异步调用executor.execute(() -> invokeListener(listener, event));}else {// 直接调用invokeListener(listener, event);}}}

invokeListener方法:

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {// 这个直接调用 listener.onApplicationEvent(event); 方法doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}}

总结:当我们调用 applicationContext.publishEvent(xxx);方法时,Spring内部会拿到关于此事件对象的所有监听器类对象,直接调用执行。

SpringBoot 启动事件流程

SpringBoot在启动时,会默认发布一些事件,我们可以自定义监听类实现监听该事件,做一些初始化等等操作,一些框架整合就是通过事件监听的方式进行内部的初始化。
值得注意的是SpringBoot对于有些事件的监听是只能通过读取配置里面配置的监听类才能生效,直接注解的方式是无法监听到的!
为什么会这样呢?
因为我们直接加注解的话,是必须要经过Spring容器的洗礼,有些事件的发布是在容器refresh之前做的,所以注解的方式是没办法生效的!

SpringBoot提供了配置文件的方式去配置监听器,那么配置文件中的实现类是何时获取到的呢?

在构造器里面获取的,SPI机制加载实现类。
我们要监听一些容器刷新之前的内部事件只能在spring.factories中指定。

启动时会发布的事件顺序:

  • ApplicationStartingEvent: 准备启动SpringBoot环境之前。
  • ApplicationEnvironmentPreparedEvent: 环境变量初始化完成,应用启动之前。
  • ApplicationContextInitializedEvent:应用初始化器执行后发布
  • ApplicationPreparedEvent:应用准备就绪,容器初始化之前发布。
  • ApplicationStartedEvent:容器初始化完成之后发布。
  • ApplicationReadyEvent:容器已经初始化完成并且已经可以接收Web请求之后发布。
  • ApplicationFailedEvent:容器启动失败。
    除此之外,在ApplicationPreparedEvent之后和ApplicationStartedEvent之前还将发布以下事件:
  • ContextRefreshedEvent:容器刷新完成发布。 WebServerInitializedEvent
    :在WebServer准备就绪后发送。ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分别是servlet和reactive变体。

以上的事件对象都继承 SpringApplicationEvent 类,SpringApplicationEvent 又继承 ApplicationEvent。

public abstract class SpringApplicationEvent extends ApplicationEvent {private final String[] args;public SpringApplicationEvent(SpringApplication application, String[] args) {super(application);this.args = args;}public SpringApplication getSpringApplication() {return (SpringApplication) getSource();}public final String[] getArgs() {return this.args;}}

以上事件的发布类是:EventPublishingRunListener。

EventPublishingRunListener 类是SpringBoot类似通过SPI机制加载进来的:

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();...// 这一步加载进来的SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();
}       


EventPublishingRunListener 实现了 SpringApplicationRunListener接口,该接口里面规范了SpringBoot启动时机的事件发布流程。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {private final SpringApplication application;// 事件派发器对象private final SimpleApplicationEventMulticaster initialMulticaster;....
}

参考文献:
1、https://blog.csdn.net/qq_33549942/article/details/122992865
2、https://developer.aliyun.com/article/829271

【微服务解耦之事件启动】Spring Boot 解耦之事件驱动相关推荐

  1. springboot 微服务_使用 Docker 部署 Spring Boot微服务

    Docker 技术发展为微服务落地提供了更加便利的环境,使用 Docker 部署 Spring Boot 其实非常简单,这篇文章我们就来简单学习下. 首先构建一个简单的 Spring Boot 项目, ...

  2. 阿里微服务专家自己手写Spring Boot 实现一个简单的自动配置模块

    为了更好的理解 Spring Boot 的 自动配置和工作原理,我们自己来实现一个简单的自动配置模块. 假设,现在项目需要一个功能,需要自动记录项目发布者的相关信息,我们如何通过 Spring Boo ...

  3. 微服务开发的入门级框架Spring Boot详解:注解

    2019独角兽企业重金招聘Python工程师标准>>> 通过前两章节的介绍,大家应该对Spring Boot有了些许的认识,也感觉到了这个框架带来的便利,下面我将讲解SpringBo ...

  4. 微服务为什么一定要选spring cloud?

    作者:董添 李秉谦 || 网易乐得技术团队 来自:http://tech.lede.com/ 现如今微服务架构十分流行,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性.同时,支持微服务的技术栈 ...

  5. 微服务为什么离不开spring cloud?

    转载自  微服务为什么离不开spring cloud? 现如今微服务架构十分流行,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性.同时,支持微服务的技术栈也是多种多样的,本系列文章主要介绍这些 ...

  6. 一个简单的微服务项目带你上手spring cloud 全家桶

    最近一个月,断断续续学习了spring cloud的主流微服务模块,然后实践了一个比较容易上手的微服务项目,现在做一个总结. 这个项目是在github上的一个比较经典的spring cloud易上手的 ...

  7. 微服务配置中心实战:Spring + MyBatis + Druid + Nacos

    转载自  微服务配置中心实战:Spring + MyBatis + Druid + Nacos 很多基于 Spring MVC 框架的 Web 开发中,Spring + MyBatis + Druid ...

  8. 问题|启动Spring Boot报错-4处 @Spring Boot

    目录 描述 问题 问题1:Application failed to start with classpath 问题2:Unable to load authentication plugin 'ca ...

  9. IDEA启动Spring Boot服务Bean重名、依赖工程冲突问题

    项目场景: 问题背景: ①微服务.maven.Idea ②A工程的pom.xml中引用了B工程 问题描述 服务启动报错:Annotation-specified bean name 'redisCon ...

  10. 【体系-微服务架构】23-Spring Cloud Spring生态链(Alibaba)

    目录 01.概述 02.创建统一的依赖管理 03.服务注册与发现 04.创建服务提供者 05.创建服务消费者 06.创建服务消费者(Feign) 07.使用熔断器防止服务雪崩 08.使用熔断器仪表盘监 ...

最新文章

  1. 电量检测芯片BQ27510使用心得
  2. oracle cost cardinality,ORACLE 执行计划中cost cardinality bytes cpu_cost io_cost解释
  3. oracle内部函数,[数据库]Oracle内置函数
  4. 十个问题弄清JVMGC(二)
  5. [HAOI2014]贴海报
  6. 清华大学计算机组成与体系结构,清华大学出版社-图书详情-《计算机组成与体系结构(第2版)》...
  7. 前端学习(3113):react-hello-类式组件
  8. SSM框架笔记13:Spring MVC基础
  9. 【转载】推荐5款超实用的.NET性能分析工具
  10. set学习(系统的学习)
  11. 数据库-SQL Server2005-第4季SQL从入门到提高-2SQL Server使用
  12. MongoDB 的命令操作
  13. SQL注入漏洞(类型篇)
  14. 基于C# 和Access数据库的电影院管理系统
  15. 掌握到胃-奈氏图与伯德图的绘制
  16. 论文阅读 [TPAMI-2022] Deep Visual Odometry With Adaptive Memory
  17. 网络地址转换(NAT)与代理服务器(Proxy Server)
  18. oracle数据库12cocp培训教程,OCA/OCP认证考试指南全册(第3版) Oracle Database 12c 中文pdf扫描版[164MB]...
  19. 【汇正财经】企业资本的意义
  20. 李兴华课程 java学习笔记

热门文章

  1. 怎样在Powerpoint中剪裁视频或音频ppt背景素材
  2. python中shift函数_pandas DataFrame.shift()函数
  3. 快餐店装修材料之灯具的布置
  4. 米家的扫地机器人是灰色_《米家扫地机器人》相关功能作用介绍
  5. 在Python中如何使用sorted()和sort()函数
  6. 做头像软件测试,用自己照片当微信头像的人,都是什么样的人?
  7. 【誉天教育】11月28日,云计算HCIE直通车周末班,邹老师带班
  8. 主观体验、情绪价值、第一印象、要不要画饼为什么?
  9. ietester测试本地html,15个在线网站检测工具
  10. python圆形噪点_python模块PIL-获取带噪点噪线的随机验证码