观察者模式是经典行为型设计模式之一。html
在GoF的《设计模式》中,观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,全部依赖的对象都会自动收到通知。若是你以为比较抽象,接下来这个例子应该会让你有所感受:java
就拿用户注册功能为例吧,假设用户注册成功以后,咱们将会发送邮件,优惠券等等操做,很容易就能写出下面的逻辑:git
@RestController @RequestMapping("/user") public class SimpleUserController { @Autowired private SimpleEmailService emailService; @Autowired private SimpleCouponService couponService; @Autowired private SimpleUserService userService; @GetMapping("/register") public String register(String username) { // 注册 userService.register(username); // 发送邮件 emailService.sendEmail(username); // 发送优惠券 couponService.addCoupon(username); return "注册成功!"; } }
这样写会有什么问题呢?受王争老师启发:spring
方法调用时,同步阻塞致使响应变慢,须要异步非阻塞的解决方案。设计模式
注册接口此时作的事情:注册,发邮件,优惠券,违反单一职责的原则。固然,若是后续没有拓展和修改的需求,这样子倒能够接受。springboot
若是后续注册的需求频繁变动,相应就须要频繁变动register方法,违反了开闭原则。并发
针对以上的问题,咱们想想解决的方案:app
1、异步非阻塞的效果能够新开一个线程执行耗时的发送邮件任务,但频繁地建立和销毁线程比较耗时,而且并发线程数没法控制,建立过多的线程会致使堆栈溢出。异步
2、使用线程池执行任务解决上述问题。ide
@Service @Slf4j public class SimpleEmailService { // 启动一个线程执行耗时操做 public void sendEmail(String username) { Thread thread = new Thread(()->{ try { // 模拟发邮件耗时操做 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("给用户 [{}] 发送邮件...", username); }); thread.start(); } } @Slf4j @Service public class SimpleCouponService { ExecutorService executorService = Executors.newSingleThreadExecutor(); // 线程池执行任务,减小资源消耗 public void addCoupon(String username) { executorService.execute(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("给用户 [{}] 发放优惠券", username); }); } }
这里用户注册事件对【发送短信和优惠券】实际上是一对多的关系,可使用观察者模式进行解耦:
/** * 主题接口 * @author Summerday */ public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(String message); } /** * 观察者接口 * @author Summerday */ public interface Observer { void update(String message); } @Component @Slf4j public class EmailObserver implements Observer { @Override public void update(String message) { log.info("向[{}]发送邮件", message); } } @Component @Slf4j public class CouponObserver implements Observer { @Override public void update(String message) { log.info("向[{}]发送优惠券",message); } } @Component public class UserRegisterSubject implements Subject { @Autowired List<Observer> observers; @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers(String username) { for (Observer observer : observers) { observer.update(username); } } } @RestController @RequestMapping("/") public class UserController { @Autowired UserRegisterSubject subject; @Autowired private SimpleUserService userService; @GetMapping("/reg") public String reg(String username) { userService.register(username); subject.notifyObservers(username); return "success"; } }
观察者模式和发布订阅模式是有一点点区别的,区别有如下几点:
图源:https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c
尽管二者存在差别,可是他们其实在概念上类似,网上说法不少,不须要过于纠结,重点在于咱们须要他们为何出现,解决了什么问题。
SpringBoot中事件监听机制则经过发布-订阅实现,主要包括如下三部分:
咱们经过SpringBoot的方式,可以很容易实现事件监听,接下来咱们改造一下上面的案例:
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; } public String getUsername() { return username; } }
/** * 注解方式 @EventListener * @author Summerday */ @Service @Slf4j public class CouponService { /** * 监听用户注册事件,执行发放优惠券逻辑 */ @EventListener public void addCoupon(UserRegisterEvent event) { log.info("给用户[{}]发放优惠券", event.getUsername()); } }
/** * 实现ApplicationListener<Event>的方式 * @author Summerday */ @Service @Slf4j public class EmailService implements ApplicationListener<UserRegisterEvent> { /** * 监听用户注册事件, 异步发送执行发送邮件逻辑 */ @Override @Async public void onApplicationEvent(UserRegisterEvent event) { log.info("给用户[{}]发送邮件", event.getUsername()); } }
@Service @Slf4j public class UserService implements ApplicationEventPublisherAware { // 注入事件发布者 private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } /** * 发布事件 */ public void register(String username) { log.info("执行用户[{}]的注册逻辑", username); applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username)); } }
@RestController @RequestMapping("/event") public class UserEventController { @Autowired private UserService userService; @GetMapping("/register") public String register(String username){ userService.register(username); return "恭喜注册成功!"; } }
@EnableAsync // 开启异步 @SpringBootApplication public class SpringBootEventListenerApplication { public static void main(String[] args) { SpringApplication.run(SpringBootEventListenerApplication.class, args); } }
启动程序,访问接口:http://localhost:8081/event/register?username=天乔巴夏
,结果以下:
2020-12-21 00:59:46.679 INFO 12800 --- [nio-8081-exec-1] com.hyh.service.UserService : 执行用户[天乔巴夏]的注册逻辑 2020-12-21 00:59:46.681 INFO 12800 --- [nio-8081-exec-1] com.hyh.service.CouponService : 给用户[天乔巴夏]发放优惠券 2020-12-21 00:59:46.689 INFO 12800 --- [ task-1] com.hyh.service.EmailService : 给用户[天乔巴夏]发送邮件
本文内容均为对优秀博客及官方文档总结而得,原文地址均已在文中参考阅读处标注。最后,文中的代码样例已经所有上传至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其余SpringBoot的整合哦。