聊聊SpringBoot事件机制

SpringBoot 事件机制

使用背景

考虑到部分项目对消息队列的要求不高,又不想引入额外部署的消息队列,这时候就可以使用 Spring Event 实现【内存】级别的消息队列。

简单介绍

Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:

  1. 事件 ApplicationEvent:通过继承它,实现自定义事件。另外,通过它的 source 属性可以获取事件timestamp 属性可以获得发生时间。
  2. 事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
  3. 事件监听器 ApplicationListener:通过实现它,进行指定类型的事件的监听。

友情提示:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,

  • ApplicationEvent 继承自 java.util.EventObject
  • ApplicationListener 继承自 java.util.EventListener

观察者模式和发布订阅模式有什么不同?

观察者模式(Observer Pattern)

在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。例如用户注册的场景。

定义

观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。

组成部分

  • Subject(主题): 被观察的对象,维护一个观察者列表,并在自身状态改变时通知所有观察者。
  • Observer(观察者): 接收主题通知并更新自身状态的对象。

实现

  • 主题对象直接持有观察者的引用。
  • 主题对象负责通知所有观察者。

发布订阅模式(Publish-Subscribe Pattern)

定义

发布订阅模式是一种消息传递模式,发布者将消息发布到特定的通道,不直接与订阅者进行通信。订阅者订阅一个或多个通道,并接收来自这些通道的消息。

组成部分

  • Publisher(发布者): 生产消息的对象,将消息发布到某个通道。
  • Subscriber(订阅者): 消费消息的对象,订阅某个通道以接收消息。
  • Message Broker(消息代理): 管理发布者和订阅者之间的通信,确保消息从发布者传递到正确的订阅者。

实现

  • 发布者和订阅者通过消息代理进行通信,解耦了两者之间的直接依赖。
  • 消息代理负责管理和分发消息。

关键区别

  1. 耦合性:
    • 观察者模式: 观察者和主题之间存在直接依赖,观察者必须知道主题的存在。
    • 发布订阅模式: 发布者和订阅者之间没有直接依赖关系,消息代理负责两者的通信。
  2. 通信方向:
    • 观察者模式: 通知是从主题到观察者的单向通知。
    • 发布订阅模式: 通信可以是多向的,多个发布者和订阅者通过消息代理进行交互。
  3. 应用场景:
    • 观察者模式: 适用于对象之间存在明确依赖关系的场景,如 GUI 事件处理。
    • 发布订阅模式: 适用于需要解耦发布者和订阅者的场景,如消息队列系统。

简单来说:

  • 发布订阅模式 中间多了一个 事件通道,以此避免了发布者和订阅者之间产生的依赖关系。

更多参考:

  • 观察者模式和发布订阅模式有什么不同? - 知乎

代码示例

引入依赖

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

开启异步

启动类上添加 @EnableAsync 注解,开启 Spring 异步的功能。

1、事件类

/**
 * 用户注册事件
 *
 * @author chenmeng
 */
@Getter
public class UserRegisterEvent extends ApplicationEvent {

    /**
     * 用户名
     */
    private String username;

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

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }
}

2、事件发布者类

/**
 * 事件发布者类
 * 
 * @author chenmeng
 */
@Service
public class UserService implements ApplicationEventPublisherAware {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username) {
        // ... 执行注册逻辑
        logger.info("[事件发布-register][执行用户({}) 的注册逻辑]", username);

        // ... 发布
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

3、事件监听器类

/**
 * 事件监听器类(不需实现接口版)
 *
 * @author chenmeng
 */
@Service
public class CouponService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Order(1)
    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[事件监听-addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}

/**
 * 事件监听器类(实现接口版)
 *
 * @author chenmeng
 */
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    @Async // 可以不加,锦上添花,设置 @Async 注解,声明异步执行。毕竟实际场景下,发送邮件可能比较慢,又是非关键逻辑。
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("[事件监听-onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }
}

/**
 * 事件监听器类(不需实现接口版)
 *
 * @author chenmeng
 */
@Service
public class InfoService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Order(2)
    @EventListener
    public void sendInfo(UserRegisterEvent event) {
        logger.info("[事件监听-sendInfo][给用户({}) 发送信息]", event.getUsername());
    }
}

客户端

/**
 * @author chenmeng
 */
@RestController
@RequestMapping("/demo")
@RequiredArgsConstructor
public class DemoController {

    private final UserService userService;

    @GetMapping("/register")
    public String register(String username) {
        userService.register(username);
        return "success";
    }
}

测试结果

2024-12-31 14:29:15.712  INFO 25272 --- [nio-9111-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2024-12-31 14:29:15.741  INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.UserService   : [事件发布-register][执行用户(张三) 的注册逻辑]
2024-12-31 14:29:15.742  INFO 25272 --- [nio-9111-exec-2] c.c.project.service.CouponService        : [事件监听-addCoupon][给用户(张三) 发放优惠劵]
2024-12-31 14:29:15.742  INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.InfoService   : [事件监听-sendInfo][给用户(张三) 发送信息]
2024-12-31 14:29:15.744  INFO 25272 --- [         task-1] c.chenmeng.project.service.EmailService  : [事件监听-onApplicationEvent][给用户(张三) 发送邮件]

设置监听顺序

如果想要多个监听器按照指定顺序执行,可在监听方法上加上 @Order() 注解。

注意事项

  • 标上异步注解 @Async 的方法,使用会无效(即异步方法无法排序)
  • 若需排序,必须都使用添加 @EventListener 注解的形式类创建监听器类(另一种写法创建的监听器,笔者测试 @Order() 发现无效)

学习参考

  • 芋道 Spring Boot 事件机制 Event 入门 | 芋道源码 —— 纯源码解析博客

你可能感兴趣的:(#,消息队列,spring,boot,后端,java)