精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

您好,歡迎來電子發燒友網! ,新用戶?[免費注冊]

您的位置:電子發燒友網>源碼下載>數值算法/人工智能>

淺談spring事件業務解耦與異步調用

大小:0.5 MB 人氣: 2017-10-11 需要積分:1

  使用spring的事件機制有助于對我們的項目進一步的解耦。假如現在我們面臨一個需求:

  我需要在用戶注冊成功的時候,根據用戶提交的郵箱、手機信息,向用戶發送郵箱認證和手機號短信通知。傳統的做法之一是在我們的UserService層注入郵件發送和短信發送的相關類,然后在完成用戶注冊同時,調用對應類方法完成郵件發送和短信發送

  但這樣做的話,會把我們郵件、短信發送的業務與我們的UserService的邏輯業務耦合在了一起。耦合造成的常見缺點是,我(甚至假設很頻繁的)修改了郵件、短信發送的API,我就可能需要在UserService層修改相應的調用方法,但這樣做人家UserService就會很無辜并吐槽: 你改郵件、短信發送的業務,又不關我的事,干嘛老改到我身上來了?這就是你的不對了。

  對呀!根據職責分明的設計原則,人家UserService就只該管用戶管理部分的業務邏輯,你老讓它干別人干的事,它當然不高興了!

  那該怎么拌?涼拌?不不不。。。我們可以通過spring的事件機制來實現解耦呀。利用觀察者設計模式,設置監聽器來監聽userService的注冊事件(同時,我們可以很自然地將userService理解成了 事件發布者),一旦userService注冊了,監聽器就完成相應的郵箱、短信發送工作(同時,我們也可以很自然地將 發送郵件、 發送短信理解成我們的 事件源)。這樣userService就不用管別人的事了,只需要在完成注冊功能時候,當下老大,號令手下(監聽器),讓它完成短信、郵箱的發送工作。

  spring的事件通信常按下列流程進行

  Created with Rapha?l 2.1.0事件發布者廣播事件(源)監聽器收到廣播,獲取事件源監聽器根據事件源采取相應的處理措施

  事件實例分析

  在這里面,我們涉及到三個主要對象:事件發布者、事件源、事件監聽器。根據這三個對象,我們來配置我們的注冊事件實例:

  1. 定義事件源

  利用事件通信的第一步往往便是定義我們的事件。在spring中,所有事件都必須擴展抽象類ApplicationEvent,同時將事件源作為構造函數參數,在這里,我們定義了發郵件、發短信兩個事件如下所示

  /*****************郵件發送事件源*************/publicclassSendEmailEventextendsApplicationEvent{//定義事件的核心成員:發送目的地,共監聽器調用完成郵箱發送功能privateString emailAddress; publicSendEmailEvent(Object source,String emailAddress ) { //source字面意思是根源,意指發送事件的根源,即我們的事件發布者super(source); this.emailAddress = emailAddress; } publicString getEmailAddress() { returnemailAddress; } } /*****************短信發送事件源*************/publicclasssendMessageEventextendsApplicationEvent{privateString phoneNum; publicsendMessageEvent(Object source,String phoneNum ) { super(source); this.phoneNum = phoneNum; } publicString getPhoneNum() { returnphoneNum; } }

  2. 定義事件監聽器

  事件監聽類需要實現我們的ApplicationListener接口,除了可以實現ApplicationListener定義事件監聽器外,我們還可以讓事件監聽類實現SmartApplicationListener(智能監聽器)接口,。關于它的具體用法和實現可參考我的下一篇文章《spring學習筆記(14)趣談spring 事件機制[2]:多監聽器流水線式順序處理 》。而此外,如果我們事件監聽器監聽的事件類型唯一的話,我們可以通過泛型來簡化配置。

  現在我們先來看看本例定義:

  publicclassRegisterListenerimplementsApplicationListener{/* *當我們的發布者發布時間時,我們的監聽器收到信號,就會調用這個方法 *我們對其進行重寫來適應我們的需求 *@Param event:我們的事件源 */@OverridepublicvoidonApplicationEvent(ApplicationEvent event) { //我們定義了兩個事件:發短信,發郵箱,他們一旦被發布都會被此方法調用//于是我們需要判斷當前event的具體類型if(event instanceofSendEmailEvent){//如果是發郵箱事件System.out.println(“正在向”+ ((SendEmailEvent) event).getEmailAddress()+ “發送郵件。。.。。.”);//模擬發送郵件事件try{ Thread.sleep(1* 1000);//模擬請求郵箱服務器、驗證賬號密碼,發送郵件耗時。} catch(InterruptedException e) { e.printStackTrace(); } System.out.println(“郵件發送成功!”); }elseif(event instanceofsendMessageEvent){//是發短信事件event = (sendMessageEvent) event; System.out.println(“正在向”+ ((sendMessageEvent) event).getPhoneNum()+ “發送短信。。.。。.”);//模擬發送郵短信事件try{ Thread.sleep(1* 1000);//模擬發送短信過程} catch(InterruptedException e) { e.printStackTrace(); } System.out.println(“短信發送成功!”); } } } /******************通過泛型配置實例如下******************/publicclassRegisterListenerimplementsApplicationListener《SendEmailEvent》 {//這里使用泛型@Override//因為使用了泛型,我們的重寫方法入參事件就唯一了。publicvoidonApplicationEvent(SendEmailEvent event) { 。。.。。 } 。。.。 }

  3. 定義事件發布者

  事件發送的代表類是ApplicationEventPublisher我們的事件發布類常實現ApplicationEventPublisherAware接口,同時需要定義成員屬性ApplicationEventPublisher來發布我們的事件。

  除了通過實現ApplicationEventPublisherAware外,我們還可以實現ApplicationContextAware接口來完成定義,ApplicationContext接口繼承了ApplicationEventPublisher。ApplicationContext是我們的事件容器上層,我們發布事件,也可以通過此容器完成發布。下面使用兩種方法來定義我們的發布者

  在本例中,我們的時間發布者自然就是我們的吐槽者,userService:

  /**********方法一:實現除了通過實現ApplicationEventPublisherAware接口************/publicclassUserServiceimplementsApplicationEventPublisherAware{privateApplicationEventPublisher applicationEventPublisher;//底層事件發布者@OverridepublicvoidsetApplicationEventPublisher(//通過Set方法完成我們的實際發布者注入 ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } publicvoiddoLogin(String emailAddress,String phoneNum) throwsInterruptedException{ Thread.sleep(200);//模擬用戶注冊的相關業務邏輯處理System.out.println(“注冊成功!”); //下列向用戶發送郵件SendEmailEvent sendEmailEvent = newSendEmailEvent(this,emailAddress);//定義事件sendMessageEvent sendMessageEvent = newsendMessageEvent(this, phoneNum); applicationEventPublisher.publishEvent(sendEmailEvent);//發布事件applicationEventPublisher.publishEvent(sendMessageEvent); } //。。.忽略其他用戶管理業務方法} /**********方法二:實現除了通過實現ApplicationContext接口************/publicclassUserService2implementsApplicationContextAware{privateApplicationContext applicationContext; @OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext) throwsBeansException { this.applicationContext = applicationContext; } publicvoiddoLogin(String emailAddress,String phoneNum) throwsInterruptedException{ Thread.sleep(200);//模擬用戶注冊的相關業務邏輯處理System.out.println(“注冊成功!”); //下列向用戶發送郵件SendEmailEvent sendEmailEvent = newSendEmailEvent(this,emailAddress);//定義事件sendMessageEvent sendMessageEvent = newsendMessageEvent(this, phoneNum); applicationContext.publishEvent(sendEmailEvent);//發布事件applicationContext.publishEvent(sendMessageEvent); } //。。.忽略其他用戶管理業務方法}

  4. 在IOC容器注冊監聽器

  《!-- 在spring容器中注冊事件監聽器, 應用上下文將會識別實現了ApplicationListener接口的Bean, 并在特定時刻將所有的事件通知它們 --》《beanid=“RegisterListener”class=“test.event.RegisterListener”/》《!-- 注冊我們的發布者,后面測試用到 --》《beanid=“userService”class=“test.event.UserService”/》

  5. 測試方法

  publicstaticvoidmain(String args[]) throwsInterruptedException{ ApplicationContext ac = newClassPathXmlApplicationContext(“classpath:test/event/event.xml”); UserService userService = (UserService) ac.getBean(“userService”); Long beginTime = System.currentTimeMillis(); userService.doLogin(“zenghao@google.com”,“12345678911”);//完成注冊請求System.out.println(“處理注冊相關業務耗時”+ (System.currentTimeMillis() - beginTime )+ “ms”); System.out.println(“處理其他業務邏輯”); Thread.sleep(500);//模擬處理其他業務請求耗時System.out.println(“處理所有業務耗時”+ (System.currentTimeMillis() - beginTime )+ “ms”); System.out.println(“向客戶端發送注冊成功響應”); }

  6. 測試結果及分析

  調用上面測試方法,控制臺打印信息

  注冊成功!

  正在向zenghao@google.com發送郵件……

  郵件發送成功!

  正在向12345678911發送短信……

  發送成功!

  處理注冊相關業務耗時2201ms

  處理其他業務邏輯開始。。

  處理其他業務邏輯結束。。

  處理所有業務耗時2701ms

  向客戶端發送注冊成功響應

  在本例中,我們通過事件機制完成了userService和郵件、短信發送業務的解耦。但觀察我們的測試結果,我們會發現,這樣的用戶體驗真是糟糕透了:天吶,我去你那注冊個用戶,要我等近3秒鐘!這太久了!

  為什么會這么久?我們根據方法分析:

  1. 注冊查詢數據庫用了200ms(查詢用戶名、郵箱、手機號有沒被使用,插入用戶信息到數據庫等操作)

  2. 發送郵件用了1000ms

  3. 發送短信用了1000ms

  4. 處理其他業務邏輯(保存用戶信息到session,其他信息數據處理等)

  第1,4步的時間耗損我們很難優化,但2,3步是主要耗時的地方,我們能不能想辦法把它縮減掉了,它把我們的正常的業務處理堵塞了。什么?堵塞,想到堵塞,我們會很自然地想到非堵塞,那就通過異步來完成2,3唄!

  7. 異步拓展。

  在spring3以上,拓展了自己獨立的時間機制,我們可以使用@Async來完成異步配置。

  首先我們需要在我們的IOC容器增加

  《!--先在命名空間中增加我們的task標簽,注意它們的添加位置 xmlns 多加下面的內容: xmlns:task=“http://www.springframework.org/schema/task” 然后xsi:schemaLocation多加下面的內容 http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd --》《!-- 我們的異步事件配置,非常簡單 --》《!--開啟注解調度支持 @Async @Scheduled--》《task:annotation-driven/》

  然后在我們的事件監聽器中添加@Async注解

  /***************我們可以在類名上添加****************/@AsyncpublicclassRegisterListenerimplementsApplicationListener{。。.。。. } /****************也可以在方法體上添加************/@AsyncpublicclassRegisterListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ApplicationEvent event) { 。。.。。 } }

  然后,再調用我們的同樣的測試方法,這次我們的結果變成:

  注冊成功!

  正在向zenghao@google.com發送郵件……

  處理注冊相關業務耗時201ms ————此時郵件發送還沒有結束,和郵件發送異步了

  正在向12345678911發送短信…。。 ————–短信發送和郵件發送和主業務處理程序都異步了!

  處理其他業務邏輯開始。。

  處理其他業務邏輯結束。。

  處理所有業務耗時701ms

  向客戶端發送注冊成功響應 ——客戶端耗時701ms就收到響應了。

  郵件發送成功! —-這個時候郵箱才發完

  短信發送成功!

  從以上的測試結果我們,我們的郵箱發送和短信發送都 分別單獨地異步完成了,大大縮短了我們主業務處理事件,也提高了用戶體驗

  小結

  從本例可以看出,不同業務功能的生硬組合,會出現邏輯處理混亂的嚴重耦合現象,比如userService類既處理自己的用戶邏輯,還要處理郵箱等發送的邏輯,這是不是也意味著,如果以后我們拓展更多的功能,我們的userService類還要出現更多的邏輯處理,來個大雜燴?,這同時還可能會為我們主要業務處理帶來不必要的阻塞。當然,為了防止阻塞,我們還可以創建新的線程來異步,但這樣原來的類就顯得更加雜亂臃腫了。使用spring事件機制能很好地幫助我們消除不同業務間的深耦合關系。它強大的任務調度還能幫助我們簡潔地實現事件異步。關于事件的一些其他用法可參考我的下一篇博文《趣談spring 事件機制[2]:多監聽器流水線式順序處理》 關于任務調度的相關框架和使用可參考我的專欄《深入淺出Quartz任務調度》。

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

      發表評論

      用戶評論
      評價:好評中評差評

      發表評論,獲取積分! 請遵守相關規定!

      ?