<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot事件監(jiān)聽機制及觀察者模式/發(fā)布訂閱模式

          共 6635字,需瀏覽 14分鐘

           ·

          2023-06-25 22:35

          走過路過不要錯過

          點擊藍字關注我們


          本篇要點

          • 介紹觀察者模式和發(fā)布訂閱模式的區(qū)別。

          • SpringBoot快速入門事件監(jiān)聽。

          什么是觀察者模式?

          觀察者模式是經典行為型設計模式之一。

          在GoF的《設計模式》中,觀察者模式的定義:在對象之間定義一個一對多的依賴,當一個對象狀態(tài)改變的時候,所有依賴的對象都會自動收到通知。如果你覺得比較抽象,接下來這個例子應該會讓你有所感覺:

          就拿用戶注冊功能為例吧,假設用戶注冊成功之后,我們將會發(fā)送郵件,優(yōu)惠券等等操作,很容易就能寫出下面的邏輯:

          @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);
          // 發(fā)送郵件
          emailService.sendEmail(username);
          // 發(fā)送優(yōu)惠券
          couponService.addCoupon(username);
          return "注冊成功!";
          }
          }


          這樣寫會有什么問題呢?受王爭老師啟發(fā):

          • 方法調用時,同步阻塞導致響應變慢,需要異步非阻塞的解決方案。

          • 注冊接口此時做的事情:注冊,發(fā)郵件,優(yōu)惠券,違反單一職責的原則。當然,如果后續(xù)沒有拓展和修改的需求,這樣子倒可以接受。

          • 如果后續(xù)注冊的需求頻繁變更,相應就需要頻繁變更register方法,違反了開閉原則。

          針對以上的問題,我們想一想解決的方案:

          一、異步非阻塞的效果可以新開一個線程執(zhí)行耗時的發(fā)送郵件任務,但頻繁地創(chuàng)建和銷毀線程比較耗時,并且并發(fā)線程數無法控制,創(chuàng)建過多的線程會導致堆棧溢出。

          二、使用線程池執(zhí)行任務解決上述問題。

          @Service
          @Slf4j
          public class SimpleEmailService {
          // 啟動一個線程執(zhí)行耗時操作
          public void sendEmail(String username) {
          Thread thread = new Thread(()->{
          try {
          // 模擬發(fā)郵件耗時操作
          Thread.sleep(3000);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          log.info("給用戶 [{}] 發(fā)送郵件...", username);
          });
          thread.start();
          }
          }

          @Slf4j
          @Service
          public class SimpleCouponService {

          ExecutorService executorService = Executors.newSingleThreadExecutor();
          // 線程池執(zhí)行任務,減少資源消耗
          public void addCoupon(String username) {
          executorService.execute(() -> {
          try {
          Thread.sleep(3000);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          log.info("給用戶 [{}] 發(fā)放優(yōu)惠券", username);
          });
          }
          }


          這里用戶注冊事件對【發(fā)送短信和優(yōu)惠券】其實是一對多的關系,可以使用觀察者模式進行解耦:

          /**
          * 主題接口
          * @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("向[{}]發(fā)送郵件", message);
          }
          }
          @Component
          @Slf4j
          public class CouponObserver implements Observer {

          @Override
          public void update(String message) {
          log.info("向[{}]發(fā)送優(yōu)惠券",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";
          }
          }


          發(fā)布訂閱模式是什么?

          觀察者模式和發(fā)布訂閱模式是有一點點區(qū)別的,區(qū)別有以下幾點:

          • 前者:觀察者訂閱主題,主題也維護觀察者的記錄,而后者:發(fā)布者和訂閱者不需要彼此了解,而是在消息隊列或代理的幫助下通信,實現松耦合。

          • 前者主要以同步方式實現,即某個事件發(fā)生時,由Subject調用所有Observers的對應方法,后者則主要使用消息隊列異步實現。

          盡管兩者存在差異,但是他們其實在概念上相似,網上說法很多,不需要過于糾結,重點在于我們需要他們?yōu)槭裁闯霈F,解決了什么問題。

          Spring事件監(jiān)聽機制概述

          SpringBoot中事件監(jiān)聽機制則通過發(fā)布-訂閱實現,主要包括以下三部分:

          • 事件 ApplicationEvent,繼承JDK的EventObject,可自定義事件。

          • 事件發(fā)布者 ApplicationEventPublisher,負責事件發(fā)布。

          • 事件監(jiān)聽者 ApplicationListener,繼承JDK的EventListener,負責監(jiān)聽指定的事件。

          我們通過SpringBoot的方式,能夠很容易實現事件監(jiān)聽,接下來我們改造一下上面的案例:

          SpringBoot事件監(jiān)聽

          定義注冊事件

          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定義監(jiān)聽器

          /**
          * 注解方式 @EventListener
          * @author Summerday
          */

          @Service
          @Slf4j
          public class CouponService {
          /**
          * 監(jiān)聽用戶注冊事件,執(zhí)行發(fā)放優(yōu)惠券邏輯
          */

          @EventListener
          public void addCoupon(UserRegisterEvent event) {
          log.info("給用戶[{}]發(fā)放優(yōu)惠券", event.getUsername());
          }
          }


          實現ApplicationListener的方式定義監(jiān)聽器

          /**
          * 實現ApplicationListener<Event>的方式
          * @author Summerday
          */

          @Service
          @Slf4j
          public class EmailService implements ApplicationListener<UserRegisterEvent> {
          /**
          * 監(jiān)聽用戶注冊事件, 異步發(fā)送執(zhí)行發(fā)送郵件邏輯
          */

          @Override
          @Async
          public void onApplicationEvent(UserRegisterEvent event) {
          log.info("給用戶[{}]發(fā)送郵件", event.getUsername());
          }
          }


          注冊事件發(fā)布者

          @Service
          @Slf4j
          public class UserService implements ApplicationEventPublisherAware {

          // 注入事件發(fā)布者
          private ApplicationEventPublisher applicationEventPublisher;

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

          /**
          * 發(fā)布事件
          */

          public void register(String username) {
          log.info("執(zhí)行用戶[{}]的注冊邏輯", 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=Java爛豬皮,結果如下:


          2020-12-21 00:59:46.679  INFO 12800 --- [nio-8081-exec-1] com.hyh.service.UserService              : 執(zhí)行用戶[Java爛豬皮]的注冊邏輯
          2020-12-21 00:59:46.681 INFO 12800 --- [nio-8081-exec-1] com.hyh.service.CouponService : 給用戶[Java爛豬皮]發(fā)放優(yōu)惠券
          2020-12-21 00:59:46.689 INFO 12800 --- [task-1] com.hyh.service.EmailService : 給用戶[Java爛豬皮]發(fā)送郵件




          想進大廠的小伙伴請注意,

          大廠面試的套路很神奇,

          早做準備對大家更有好處,

          埋頭刷題效率低,

          看面經會更有效率!

          小編準備了一份大廠常問面經匯總集

          剩下的就不會給大家一展出來了,以上資料按照一下操作即可獲得

          ——將文章進行轉發(fā)評論關注公眾號【Java烤豬皮】,關注后繼續(xù)后臺回復領取口令“ 666 ”即可免費領文章取中所提供的資料。




          往期精品推薦



          騰訊、阿里、滴滴后臺試題匯集總結 — (含答案)

          面試:史上最全多線程序面試題!

          最新阿里內推Java后端試題

          JVM難學?那是因為你沒有真正看完整這篇文章


          結束


          關注作者微信公眾號 — 《JAVA烤豬皮》


          了解了更多java后端架構知識以及最新面試寶典



          看完本文記得給作者點贊+在看哦~~~大家的支持,是作者來源不斷出文的動力~

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品视频免费在线观看 | JUY-579被丈夫的上司侵犯后的第7天,我 尤物网在线观看 | 国产精品人成A片一区二区 | 嫩草乱码一区三区四区 | 看欧美黄色片 |