SpringBoot事件监听机制及观察者模式/发布订阅模式

前端之家收集整理的这篇文章主要介绍了SpringBoot事件监听机制及观察者模式/发布订阅模式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本篇要点

  • 介绍观察者模式和发布订阅模式的区别。
  • SpringBoot快速入门事件监听。

什么是观察者模式?

观察者模式是经典行为型设计模式之一。

在GoF的《设计模式》中,观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。如果你觉得比较抽象,接下来这个例子应该会让你有所感觉:

就拿用户注册功能为例吧,假设用户注册成功之后,我们将会发送邮件,优惠券等等操作,很容易就能写出下面的逻辑:

  1. @RestController
  2. @RequestMapping("/user")
  3. public class SimpleUserController {
  4. @Autowired
  5. private SimpleEmailService emailService;
  6. @Autowired
  7. private SimpleCouponService couponService;
  8. @Autowired
  9. private SimpleUserService userService;
  10. @GetMapping("/register")
  11. public String register(String username) {
  12. // 注册
  13. userService.register(username);
  14. // 发送邮件
  15. emailService.sendEmail(username);
  16. // 发送优惠券
  17. couponService.addCoupon(username);
  18. return "注册成功!";
  19. }
  20. }

这样写会有什么问题呢?受王争老师启发:

  • 方法调用时,同步阻塞导致响应变慢,需要异步非阻塞的解决方案。

  • 注册接口此时做的事情:注册,发邮件,优惠券,违反单一职责的原则。当然,如果后续没有拓展和修改的需求,这样子倒可以接受。

  • 如果后续注册的需求频繁变更,相应就需要频繁变更register方法,违反了开闭原则。

针对以上的问题,我们想一想解决的方案:

一、异步非阻塞的效果可以新开一个线程执行耗时的发送邮件任务,但频繁地创建和销毁线程比较耗时,并且并发线程数无法控制,创建过多的线程会导致堆栈溢出。

二、使用线程池执行任务解决上述问题。

  1. @Service
  2. @Slf4j
  3. public class SimpleEmailService {
  4. // 启动一个线程执行耗时操作
  5. public void sendEmail(String username) {
  6. Thread thread = new Thread(()->{
  7. try {
  8. // 模拟发邮件耗时操作
  9. Thread.sleep(3000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. log.info("给用户 [{}] 发送邮件...",username);
  14. });
  15. thread.start();
  16. }
  17. }
  18. @Slf4j
  19. @Service
  20. public class SimpleCouponService {
  21. ExecutorService executorService = Executors.newSingleThreadExecutor();
  22. // 线程池执行任务,减少资源消耗
  23. public void addCoupon(String username) {
  24. executorService.execute(() -> {
  25. try {
  26. Thread.sleep(3000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. log.info("给用户 [{}] 发放优惠券",username);
  31. });
  32. }
  33. }

这里用户注册事件对【发送短信和优惠券】其实是一对多的关系,可以使用观察者模式进行解耦:

  1. /**
  2. * 主题接口
  3. * @author Summerday
  4. */
  5. public interface Subject {
  6. void registerObserver(Observer observer);
  7. void removeObserver(Observer observer);
  8. void notifyObservers(String message);
  9. }
  10. /**
  11. * 观察者接口
  12. * @author Summerday
  13. */
  14. public interface Observer {
  15. void update(String message);
  16. }
  17. @Component
  18. @Slf4j
  19. public class EmailObserver implements Observer {
  20. @Override
  21. public void update(String message) {
  22. log.info("向[{}]发送邮件",message);
  23. }
  24. }
  25. @Component
  26. @Slf4j
  27. public class CouponObserver implements Observer {
  28. @Override
  29. public void update(String message) {
  30. log.info("向[{}]发送优惠券",message);
  31. }
  32. }
  33. @Component
  34. public class UserRegisterSubject implements Subject {
  35. @Autowired
  36. List<Observer> observers;
  37. @Override
  38. public void registerObserver(Observer observer) {
  39. observers.add(observer);
  40. }
  41. @Override
  42. public void removeObserver(Observer observer) {
  43. observers.remove(observer);
  44. }
  45. @Override
  46. public void notifyObservers(String username) {
  47. for (Observer observer : observers) {
  48. observer.update(username);
  49. }
  50. }
  51. }
  52. @RestController
  53. @RequestMapping("/")
  54. public class UserController {
  55. @Autowired
  56. UserRegisterSubject subject;
  57. @Autowired
  58. private SimpleUserService userService;
  59. @GetMapping("/reg")
  60. public String reg(String username) {
  61. userService.register(username);
  62. subject.notifyObservers(username);
  63. return "success";
  64. }
  65. }

发布订阅模式是什么?

观察者模式和发布订阅模式是有一点点区别的,区别有以下几点:

  • 前者:观察者订阅主题主题也维护观察者的记录,而后者:发布者和订阅者不需要彼此了解,而是在消息队列或代理的帮助下通信,实现松耦合。
  • 前者主要以同步方式实现,即某个事件发生时,由Subject调用所有Observers的对应方法,后者则主要使用消息队列异步实现。

图源:https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

尽管两者存在差异,但是他们其实在概念上相似,网上说法很多,不需要过于纠结,重点在于我们需要他们为什么出现,解决了什么问题。

Spring事件监听机制概述

SpringBoot中事件监听机制则通过发布-订阅实现,主要包括以下三部分:

  • 事件 ApplicationEvent,继承JDK的EventObject,可自定义事件。
  • 事件发布者 ApplicationEventPublisher,负责事件发布。
  • 事件监听者 ApplicationListener,继承JDK的EventListener,负责监听指定的事件。

我们通过SpringBoot的方式,能够很容易实现事件监听,接下来我们改造一下上面的案例:

SpringBoot事件监听

定义注册事件

  1. public class UserRegisterEvent extends ApplicationEvent {
  2. private String username;
  3. public UserRegisterEvent(Object source) {
  4. super(source);
  5. }
  6. public UserRegisterEvent(Object source,String username) {
  7. super(source);
  8. this.username = username;
  9. }
  10. public String getUsername() {
  11. return username;
  12. }
  13. }

注解方式 @EventListener定义监听器

  1. /**
  2. * 注解方式 @EventListener
  3. * @author Summerday
  4. */
  5. @Service
  6. @Slf4j
  7. public class CouponService {
  8. /**
  9. * 监听用户注册事件,执行发放优惠券逻辑
  10. */
  11. @EventListener
  12. public void addCoupon(UserRegisterEvent event) {
  13. log.info("给用户[{}]发放优惠券",event.getUsername());
  14. }
  15. }

实现ApplicationListener的方式定义监听器

  1. /**
  2. * 实现ApplicationListener<Event>的方式
  3. * @author Summerday
  4. */
  5. @Service
  6. @Slf4j
  7. public class EmailService implements ApplicationListener<UserRegisterEvent> {
  8. /**
  9. * 监听用户注册事件,异步发送执行发送邮件逻辑
  10. */
  11. @Override
  12. @Async
  13. public void onApplicationEvent(UserRegisterEvent event) {
  14. log.info("给用户[{}]发送邮件",event.getUsername());
  15. }
  16. }

注册事件发布者

  1. @Service
  2. @Slf4j
  3. public class UserService implements ApplicationEventPublisherAware {
  4. // 注入事件发布者
  5. private ApplicationEventPublisher applicationEventPublisher;
  6. @Override
  7. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  8. this.applicationEventPublisher = applicationEventPublisher;
  9. }
  10. /**
  11. * 发布事件
  12. */
  13. public void register(String username) {
  14. log.info("执行用户[{}]的注册逻辑",username);
  15. applicationEventPublisher.publishEvent(new UserRegisterEvent(this,username));
  16. }
  17. }

定义接口

  1. @RestController
  2. @RequestMapping("/event")
  3. public class UserEventController {
  4. @Autowired
  5. private UserService userService;
  6. @GetMapping("/register")
  7. public String register(String username){
  8. userService.register(username);
  9. return "恭喜注册成功!";
  10. }
  11. }

主程序类

  1. @EnableAsync // 开启异步
  2. @SpringBootApplication
  3. public class SpringBootEventListenerApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SpringBootEventListenerApplication.class,args);
  6. }
  7. }

测试接口

启动程序,访问接口:http://localhost:8081/event/register?username=天乔巴夏,结果如下:

  1. 2020-12-21 00:59:46.679 INFO 12800 --- [nio-8081-exec-1] com.hyh.service.UserService : 执行用户[天乔巴夏]的注册逻辑
  2. 2020-12-21 00:59:46.681 INFO 12800 --- [nio-8081-exec-1] com.hyh.service.CouponService : 用户[天乔巴夏]发放优惠券
  3. 2020-12-21 00:59:46.689 INFO 12800 --- [ task-1] com.hyh.service.EmailService : 用户[天乔巴夏]发送邮件

源码下载

本文内容均为对优秀博客及官方文档总结而得,原文地址均已在文中参考阅读处标注。最后,文中的代码样例已经全部上传至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其他SpringBoot的整合哦。

参考阅读

猜你在找的Springboot相关文章