觀察者模式是經典行爲型設計模式之一。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的整合哦。