SpringBoot使用ApplicationEvent&Listener完成業(yè)務(wù)解耦
引言
ApplicationEvent以及Listener是Spring為我們提供的一個(gè)事件監(jiān)聽(tīng)、訂閱的實(shí)現(xiàn),內(nèi)部實(shí)現(xiàn)原理是觀察者設(shè)計(jì)模式,設(shè)計(jì)初衷也是為了系統(tǒng)業(yè)務(wù)邏輯之間的解耦,提高可擴(kuò)展性以及可維護(hù)性。事件發(fā)布者并不需要考慮誰(shuí)去監(jiān)聽(tīng),監(jiān)聽(tīng)具體的實(shí)現(xiàn)內(nèi)容是什么,發(fā)布者的工作只是為了發(fā)布事件而已。
我們平時(shí)日常生活中也是經(jīng)常會(huì)有這種情況存在,如:我們?cè)谄綍r(shí)拔河比賽中,裁判員給我們吹響了開(kāi)始的信號(hào),也就是給我們發(fā)布了一個(gè)開(kāi)始的事件,而拔河雙方人員都在監(jiān)聽(tīng)著這個(gè)事件,一旦事件發(fā)布后雙方人員就開(kāi)始往自己方使勁。而裁判并不關(guān)心你比賽的過(guò)程,只是給你發(fā)布事件你執(zhí)行就可以了。
本章目標(biāo)
我們本章在SpringBoot平臺(tái)上通過(guò)ApplicationEvents以及Listener來(lái)完成簡(jiǎn)單的注冊(cè)事件流程。
構(gòu)建項(xiàng)目
我們本章只是簡(jiǎn)單的講解如何使用ApplicationEvent以及Listener來(lái)完成業(yè)務(wù)邏輯的解耦,不涉及到數(shù)據(jù)交互所以依賴(lài)需要引入的也比較少,項(xiàng)目pom.xml配置文件如下所示:
.....//省略
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
.....//省略其中l(wèi)ombok依賴(lài)大家有興趣可以去深研究下,這是一個(gè)很好的工具,它可以結(jié)合Idea開(kāi)發(fā)工具完成對(duì)實(shí)體的動(dòng)態(tài)添加構(gòu)造函數(shù)、Getter/Setter方法、toString方法等。
創(chuàng)建UserRegisterEvent事件
我們先來(lái)創(chuàng)建一個(gè)事件,監(jiān)聽(tīng)都是圍繞著事件來(lái)掛起的。事件代碼如下所示:
package com.yuqiyu.chapter27.event;
import com.yuqiyu.chapter27.bean.UserBean;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:08
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public class UserRegisterEvent extends ApplicationEvent
{
//注冊(cè)用戶(hù)對(duì)象
private UserBean user;
/**
* 重寫(xiě)構(gòu)造函數(shù)
* @param source 發(fā)生事件的對(duì)象
* @param user 注冊(cè)用戶(hù)對(duì)象
*/
public UserRegisterEvent(Object source,UserBean user) {
super(source);
this.user = user;
}
}我們自定義事件UserRegisterEvent繼承了ApplicationEvent,繼承后必須重載構(gòu)造函數(shù),構(gòu)造函數(shù)的參數(shù)可以任意指定,其中source參數(shù)指的是發(fā)生事件的對(duì)象,一般我們?cè)诎l(fā)布事件時(shí)使用的是this關(guān)鍵字代替本類(lèi)對(duì)象,而user參數(shù)是我們自定義的注冊(cè)用戶(hù)對(duì)象,該對(duì)象可以在監(jiān)聽(tīng)內(nèi)被獲取。
在Spring內(nèi)部中有多種方式實(shí)現(xiàn)監(jiān)聽(tīng)如:@EventListener注解、實(shí)現(xiàn)ApplicationListener泛型接口、實(shí)現(xiàn)SmartApplicationListener接口等,我們下面來(lái)講解下這三種方式分別如何實(shí)現(xiàn)。
創(chuàng)建UserBean
我們簡(jiǎn)單創(chuàng)建一個(gè)用戶(hù)實(shí)體,并添加兩個(gè)字段:用戶(hù)名、密碼。實(shí)體代碼如下所示:
package com.yuqiyu.chapter27.bean;
import lombok.Data;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:05
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Data
public class UserBean
{
//用戶(hù)名
private String name;
//密碼
private String password;
}創(chuàng)建UserService
UserService內(nèi)添加一個(gè)注冊(cè)方法,該方法只是實(shí)現(xiàn)注冊(cè)事件發(fā)布功能,代碼如下所示:
package com.yuqiyu.chapter27.service;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:11
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Service
public class UserService
{
@Autowired
ApplicationContext applicationContext;
/**
* 用戶(hù)注冊(cè)方法
* @param user
*/
public void register(UserBean user)
{
//../省略其他邏輯
//發(fā)布UserRegisterEvent事件
applicationContext.publishEvent(new UserRegisterEvent(this,user));
}
}事件發(fā)布是由ApplicationContext對(duì)象管控的,我們發(fā)布事件前需要注入ApplicationContext對(duì)象調(diào)用publishEvent方法完成事件發(fā)布。
創(chuàng)建UserController
創(chuàng)建一個(gè)@RestController控制器,對(duì)應(yīng)添加一個(gè)注冊(cè)方法簡(jiǎn)單實(shí)現(xiàn),代碼如下所示:
package com.yuqiyu.chapter27.controller;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用戶(hù)控制器
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:05
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@RestController
public class UserController
{
//用戶(hù)業(yè)務(wù)邏輯實(shí)現(xiàn)
@Autowired
private UserService userService;
/**
* 注冊(cè)控制方法
* @param user 用戶(hù)對(duì)象
* @return
*/
@RequestMapping(value = "/register")
public String register
(
UserBean user
)
{
//調(diào)用注冊(cè)業(yè)務(wù)邏輯
userService.register(user);
return "注冊(cè)成功.";
}
}@EventListener實(shí)現(xiàn)監(jiān)聽(tīng)
注解方式比較簡(jiǎn)單,并不需要實(shí)現(xiàn)任何接口,具體代碼實(shí)現(xiàn)如下所示:
package com.yuqiyu.chapter27.listener;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 使用@EventListener方法實(shí)現(xiàn)注冊(cè)事件監(jiān)聽(tīng)
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:50
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class AnnotationRegisterListener {
/**
* 注冊(cè)監(jiān)聽(tīng)實(shí)現(xiàn)方法
* @param userRegisterEvent 用戶(hù)注冊(cè)事件
*/
@EventListener
public void register(UserRegisterEvent userRegisterEvent)
{
//獲取注冊(cè)用戶(hù)對(duì)象
UserBean user = userRegisterEvent.getUser();
//../省略邏輯
//輸出注冊(cè)用戶(hù)信息
System.out.println("@EventListener注冊(cè)信息,用戶(hù)名:"+user.getName()+",密碼:"+user.getPassword());
}
}我們只需要讓我們的監(jiān)聽(tīng)類(lèi)被Spring所管理即可,在我們用戶(hù)注冊(cè)監(jiān)聽(tīng)實(shí)現(xiàn)方法上添加@EventListener注解,該注解會(huì)根據(jù)方法內(nèi)配置的事件完成監(jiān)聽(tīng)。下面我們啟動(dòng)項(xiàng)目來(lái)測(cè)試下我們事件發(fā)布時(shí)是否被監(jiān)聽(tīng)者所感知。
測(cè)試事件監(jiān)聽(tīng)
使用SpringBootApplication方式啟動(dòng)成功后,我們來(lái)訪(fǎng)問(wèn)下地址:http://127.0.0.1:8080/register?name=admin&password=123456
界面輸出內(nèi)容肯定是“注冊(cè)成功”,這個(gè)是沒(méi)有問(wèn)題的,我們直接查看控制臺(tái)輸出內(nèi)容,如下所示:
2017-07-21 11:09:52.532 INFO 10460 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 11:09:52.532 INFO 10460 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 11:09:52.545 INFO 10460 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms
@EventListener注冊(cè)信息,用戶(hù)名:admin,密碼:123456
可以看到我們使用@EventListener注解配置的監(jiān)聽(tīng)已經(jīng)生效了,當(dāng)我們?cè)赨serService內(nèi)發(fā)布了注冊(cè)事件時(shí),監(jiān)聽(tīng)方法自動(dòng)被調(diào)用并且輸出內(nèi)信息到控制臺(tái)。
ApplicationListener實(shí)現(xiàn)監(jiān)聽(tīng)
這種方式也是Spring之前比較常用的監(jiān)聽(tīng)事件方式,在實(shí)現(xiàn)ApplicationListener接口時(shí)需要將監(jiān)聽(tīng)事件作為泛型傳遞,監(jiān)聽(tīng)實(shí)現(xiàn)代碼如下所示:
package com.yuqiyu.chapter27.listener;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 原始方式實(shí)現(xiàn)
* 用戶(hù)注冊(cè)監(jiān)聽(tīng)
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:24
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class RegisterListener implements ApplicationListener<UserRegisterEvent>
{
/**
* 實(shí)現(xiàn)監(jiān)聽(tīng)
* @param userRegisterEvent
*/
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
//獲取注冊(cè)用戶(hù)對(duì)象
UserBean user = userRegisterEvent.getUser();
//../省略邏輯
//輸出注冊(cè)用戶(hù)信息
System.out.println("注冊(cè)信息,用戶(hù)名:"+user.getName()+",密碼:"+user.getPassword());
}
}我們實(shí)現(xiàn)接口后需要使用@Component注解來(lái)聲明該監(jiān)聽(tīng)需要被Spring注入管理,當(dāng)有UserRegisterEvent事件發(fā)布時(shí)監(jiān)聽(tīng)程序會(huì)自動(dòng)調(diào)用onApplicationEvent方法并且將UserRegisterEvent對(duì)象作為參數(shù)傳遞。
我們UserService內(nèi)的發(fā)布事件不需要修改,我們重啟下項(xiàng)目再次訪(fǎng)問(wèn)之前的地址查看控制臺(tái)輸出的內(nèi)容如下所示:
2017-07-21 13:03:35.399 INFO 4324 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 13:03:35.399 INFO 4324 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 13:03:35.411 INFO 4324 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 12 ms
注冊(cè)信息,用戶(hù)名:admin,密碼:123456
我們看到了控制臺(tái)打印了我們監(jiān)聽(tīng)內(nèi)輸出用戶(hù)信息,事件發(fā)布后就不會(huì)考慮具體哪個(gè)監(jiān)聽(tīng)去處理業(yè)務(wù),甚至可以存在多個(gè)監(jiān)聽(tīng)同時(shí)需要處理業(yè)務(wù)邏輯。
我們?cè)谧?cè)時(shí)如果不僅僅是記錄注冊(cè)信息到數(shù)據(jù)庫(kù),還需要發(fā)送郵件通知用戶(hù),當(dāng)然我們可以創(chuàng)建多個(gè)監(jiān)聽(tīng)同時(shí)監(jiān)聽(tīng)UserRegisterEvent事件,接下來(lái)我們先來(lái)實(shí)現(xiàn)這個(gè)需求。
郵件通知監(jiān)聽(tīng)
我們使用注解的方式來(lái)完成郵件發(fā)送監(jiān)聽(tīng)實(shí)現(xiàn),代碼如下所示:
package com.yuqiyu.chapter27.listener;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 注冊(cè)用戶(hù)事件發(fā)送郵件監(jiān)聽(tīng)
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:13:08
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class RegisterUserEmailListener
{
/**
* 發(fā)送郵件監(jiān)聽(tīng)實(shí)現(xiàn)
* @param userRegisterEvent 用戶(hù)注冊(cè)事件
*/
@EventListener
public void sendMail(UserRegisterEvent userRegisterEvent)
{
System.out.println("用戶(hù)注冊(cè)成功,發(fā)送郵件。");
}
}監(jiān)聽(tīng)編寫(xiě)完成后,我們重啟項(xiàng)目,再次訪(fǎng)問(wèn)注冊(cè)請(qǐng)求地址查看控制臺(tái)輸出內(nèi)容如下所示:
2017-07-21 13:09:20.671 INFO 7808 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 13:09:20.671 INFO 7808 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 13:09:20.685 INFO 7808 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
用戶(hù)注冊(cè)成功,發(fā)送郵件。
注冊(cè)信息,用戶(hù)名:admin,密碼:123456
我們看到控制臺(tái)輸出的內(nèi)容感到比較疑惑,我注冊(cè)時(shí)用戶(hù)信息寫(xiě)入數(shù)據(jù)庫(kù)應(yīng)該在發(fā)送郵件前面,為什么沒(méi)有在第一步執(zhí)行呢?
好了,證明了一點(diǎn),事件監(jiān)聽(tīng)是無(wú)序的,監(jiān)聽(tīng)到的事件先后順序完全隨機(jī)出現(xiàn)的。我們接下來(lái)使用SmartApplicationListener實(shí)現(xiàn)監(jiān)聽(tīng)方式來(lái)實(shí)現(xiàn)該邏輯。
SmartApplicationListener實(shí)現(xiàn)有序監(jiān)聽(tīng)
我們對(duì)注冊(cè)用戶(hù)以及發(fā)送郵件的監(jiān)聽(tīng)重新編寫(xiě),注冊(cè)用戶(hù)寫(xiě)入數(shù)據(jù)庫(kù)監(jiān)聽(tīng)代碼如下所示:
package com.yuqiyu.chapter27.listener;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import com.yuqiyu.chapter27.service.UserService;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
/**
* 用戶(hù)注冊(cè)>>>保存用戶(hù)信息監(jiān)聽(tīng)
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:10:09
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class UserRegisterListener implements SmartApplicationListener
{
/**
* 該方法返回true&supportsSourceType同樣返回true時(shí),才會(huì)調(diào)用該監(jiān)聽(tīng)內(nèi)的onApplicationEvent方法
* @param aClass 接收到的監(jiān)聽(tīng)事件類(lèi)型
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
//只有UserRegisterEvent監(jiān)聽(tīng)類(lèi)型才會(huì)執(zhí)行下面邏輯
return aClass == UserRegisterEvent.class;
}
/**
* 該方法返回true&supportsEventType同樣返回true時(shí),才會(huì)調(diào)用該監(jiān)聽(tīng)內(nèi)的onApplicationEvent方法
* @param aClass
* @return
*/
@Override
public boolean supportsSourceType(Class<?> aClass) {
//只有在UserService內(nèi)發(fā)布的UserRegisterEvent事件時(shí)才會(huì)執(zhí)行下面邏輯
return aClass == UserService.class;
}
/**
* supportsEventType & supportsSourceType 兩個(gè)方法返回true時(shí)調(diào)用該方法執(zhí)行業(yè)務(wù)邏輯
* @param applicationEvent 具體監(jiān)聽(tīng)實(shí)例,這里是UserRegisterEvent
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//轉(zhuǎn)換事件類(lèi)型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//獲取注冊(cè)用戶(hù)對(duì)象信息
UserBean user = userRegisterEvent.getUser();
//.../完成注冊(cè)業(yè)務(wù)邏輯
System.out.println("注冊(cè)信息,用戶(hù)名:"+user.getName()+",密碼:"+user.getPassword());
}
/**
* 同步情況下監(jiān)聽(tīng)執(zhí)行的順序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}SmartApplicationListener接口繼承了全局監(jiān)聽(tīng)ApplicationListener,并且泛型對(duì)象使用的ApplicationEvent來(lái)作為全局監(jiān)聽(tīng),可以理解為使用SmartApplicationListener作為監(jiān)聽(tīng)父接口的實(shí)現(xiàn),監(jiān)聽(tīng)所有事件發(fā)布。
既然是監(jiān)聽(tīng)所有的事件發(fā)布,那么SmartApplicationListener接口添加了兩個(gè)方法supportsEventType、supportsSourceType來(lái)作為區(qū)分是否是我們監(jiān)聽(tīng)的事件,只有這兩個(gè)方法同時(shí)返回true時(shí)才會(huì)執(zhí)行onApplicationEvent方法。
可以看到除了上面的方法,還提供了一個(gè)getOrder方法,這個(gè)方法就可以解決執(zhí)行監(jiān)聽(tīng)的順序問(wèn)題,return的數(shù)值越小證明優(yōu)先級(jí)越高,執(zhí)行順序越靠前。
注冊(cè)成功發(fā)送郵件通知監(jiān)聽(tīng)代碼如下所示:
package com.yuqiyu.chapter27.listener.order;
import com.yuqiyu.chapter27.bean.UserBean;
import com.yuqiyu.chapter27.event.UserRegisterEvent;
import com.yuqiyu.chapter27.service.UserService;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:13:38
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class UserRegisterSendMailListener implements SmartApplicationListener
{
/**
* 該方法返回true&supportsSourceType同樣返回true時(shí),才會(huì)調(diào)用該監(jiān)聽(tīng)內(nèi)的onApplicationEvent方法
* @param aClass 接收到的監(jiān)聽(tīng)事件類(lèi)型
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
//只有UserRegisterEvent監(jiān)聽(tīng)類(lèi)型才會(huì)執(zhí)行下面邏輯
return aClass == UserRegisterEvent.class;
}
/**
* 該方法返回true&supportsEventType同樣返回true時(shí),才會(huì)調(diào)用該監(jiān)聽(tīng)內(nèi)的onApplicationEvent方法
* @param aClass
* @return
*/
@Override
public boolean supportsSourceType(Class<?> aClass) {
//只有在UserService內(nèi)發(fā)布的UserRegisterEvent事件時(shí)才會(huì)執(zhí)行下面邏輯
return aClass == UserService.class;
}
/**
* supportsEventType & supportsSourceType 兩個(gè)方法返回true時(shí)調(diào)用該方法執(zhí)行業(yè)務(wù)邏輯
* @param applicationEvent 具體監(jiān)聽(tīng)實(shí)例,這里是UserRegisterEvent
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//轉(zhuǎn)換事件類(lèi)型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//獲取注冊(cè)用戶(hù)對(duì)象信息
UserBean user = userRegisterEvent.getUser();
System.out.println("用戶(hù):"+user.getName()+",注冊(cè)成功,發(fā)送郵件通知。");
}
/**
* 同步情況下監(jiān)聽(tīng)執(zhí)行的順序
* @return
*/
@Override
public int getOrder() {
return 1;
}
}在getOrder方法內(nèi)我們返回的數(shù)值為“1”,這就證明了需要在保存注冊(cè)用戶(hù)信息監(jiān)聽(tīng)后執(zhí)行,下面我們重啟項(xiàng)目訪(fǎng)問(wèn)注冊(cè)地址查看控制臺(tái)輸出內(nèi)容如下所示:
2017-07-21 13:40:43.104 INFO 10128 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 13:40:43.104 INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 13:40:43.119 INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
注冊(cè)信息,用戶(hù)名:admin,密碼:123456
用戶(hù):admin,注冊(cè)成功,發(fā)送郵件通知。
這次我們看到了輸出的順序就是正確的了,先保存信息然后再發(fā)送郵件通知。
如果說(shuō)我們不希望在執(zhí)行監(jiān)聽(tīng)時(shí)等待監(jiān)聽(tīng)業(yè)務(wù)邏輯耗時(shí),發(fā)布監(jiān)聽(tīng)后立即要對(duì)接口或者界面做出反映,我們?cè)撛趺醋瞿兀?/p>
使用@Async實(shí)現(xiàn)異步監(jiān)聽(tīng)
@Aysnc其實(shí)是Spring內(nèi)的一個(gè)組件,可以完成對(duì)類(lèi)內(nèi)單個(gè)或者多個(gè)方法實(shí)現(xiàn)異步調(diào)用,這樣可以大大的節(jié)省等待耗時(shí)。內(nèi)部實(shí)現(xiàn)機(jī)制是線(xiàn)程池任務(wù)ThreadPoolTaskExecutor,通過(guò)線(xiàn)程池來(lái)對(duì)配置@Async的方法或者類(lèi)做出執(zhí)行動(dòng)作。
線(xiàn)程任務(wù)池配置
我們創(chuàng)建一個(gè)ListenerAsyncConfiguration,并且使用@EnableAsync注解開(kāi)啟支持異步處理,具體代碼如下所示:
package com.yuqiyu.chapter27;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 異步監(jiān)聽(tīng)配置
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/21
* Time:14:04
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Configuration
@EnableAsync
public class ListenerAsyncConfiguration implements AsyncConfigurer
{
/**
* 獲取異步線(xiàn)程池執(zhí)行對(duì)象
* @return
*/
@Override
public Executor getAsyncExecutor() {
//使用Spring內(nèi)置線(xiàn)程池任務(wù)對(duì)象
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//設(shè)置線(xiàn)程池參數(shù)
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}我們自定義的監(jiān)聽(tīng)異步配置類(lèi)實(shí)現(xiàn)了AsyncConfigurer接口并且實(shí)現(xiàn)內(nèi)getAsyncExecutor方法以提供線(xiàn)程任務(wù)池對(duì)象的獲取。
我們只需要在異步方法上添加@Async注解就可以實(shí)現(xiàn)方法的異步調(diào)用,為了證明這一點(diǎn),我們?cè)诎l(fā)送郵件onApplicationEvent方法內(nèi)添加線(xiàn)程阻塞3秒,修改后的代碼如下所示:
/**
* supportsEventType & supportsSourceType 兩個(gè)方法返回true時(shí)調(diào)用該方法執(zhí)行業(yè)務(wù)邏輯
* @param applicationEvent 具體監(jiān)聽(tīng)實(shí)例,這里是UserRegisterEvent
*/
@Override
@Async
public void onApplicationEvent(ApplicationEvent applicationEvent) {
try {
Thread.sleep(3000);//靜靜的沉睡3秒鐘
}catch (Exception e)
{
e.printStackTrace();
}
//轉(zhuǎn)換事件類(lèi)型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//獲取注冊(cè)用戶(hù)對(duì)象信息
UserBean user = userRegisterEvent.getUser();
System.out.println("用戶(hù):"+user.getName()+",注冊(cè)成功,發(fā)送郵件通知。");
}下面我們重啟下項(xiàng)目,訪(fǎng)問(wèn)注冊(cè)地址,查看界面反映是否也有延遲。
我們測(cè)試發(fā)現(xiàn)訪(fǎng)問(wèn)界面時(shí)反映速度要不之前還要快一些,我們?nèi)ゲ榭纯刂婆_(tái)時(shí),可以看到注冊(cè)信息輸出后等待3秒后再才輸出郵件發(fā)送通知,而在這之前界面已經(jīng)做出了反映。
注意:如果存在多個(gè)監(jiān)聽(tīng)同一個(gè)事件時(shí),并且存在異步與同步同時(shí)存在時(shí)則不存在執(zhí)行順序。
總結(jié)
我們?cè)趥鹘y(tǒng)項(xiàng)目中往往各個(gè)業(yè)務(wù)邏輯之間耦合性較強(qiáng),因?yàn)槲覀冊(cè)趕ervice都是直接引用的關(guān)聯(lián)service或者jpa來(lái)作為協(xié)作處理邏輯,然而這種方式在后期更新、維護(hù)性難度都是大大提高了。然而我們采用事件通知、事件監(jiān)聽(tīng)形式來(lái)處理邏輯時(shí)耦合性則是可以降到最小。
SpringBoot相關(guān)系列文章請(qǐng)?jiān)L問(wèn):SpringBoot學(xué)習(xí)目錄
QueryDSL相關(guān)系列文章請(qǐng)?jiān)L問(wèn):QueryDSL通用查詢(xún)框架學(xué)習(xí)目錄
以上就是SpringBoot使用ApplicationEvent&Listener完成業(yè)務(wù)解耦的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot業(yè)務(wù)解耦的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring數(shù)據(jù)庫(kù)多數(shù)據(jù)源路由配置過(guò)程圖解
這篇文章主要介紹了Spring數(shù)據(jù)庫(kù)多數(shù)據(jù)源路由配置過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Java實(shí)現(xiàn)對(duì)一行英文進(jìn)行單詞提取功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)對(duì)一行英文進(jìn)行單詞提取功能,結(jié)合實(shí)例形式分析了java基于StringTokenizer類(lèi)進(jìn)行字符串分割的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Mybatis?大數(shù)據(jù)量批量寫(xiě)優(yōu)化的案例詳解
這篇文章主要介紹了Mybatis?大數(shù)據(jù)量批量寫(xiě)優(yōu)化的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
Flink結(jié)合Kafka實(shí)現(xiàn)通用流式數(shù)據(jù)處理
這篇文章將和大家一起深入探討Flink和Kafka的關(guān)系以及它們?cè)跀?shù)據(jù)流處理中的應(yīng)用,并提供一些最佳實(shí)踐和實(shí)際案例,希望對(duì)大家有一定的幫助2025-03-03
java8新特性-Stream入門(mén)學(xué)習(xí)心得
這篇文章主要介紹了java8新特性-Stream入門(mén)學(xué)習(xí)心得,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Dubbo+zookeeper搭配分布式服務(wù)的過(guò)程詳解
Dubbo作為分布式架構(gòu)比較后的框架,同時(shí)也是比較容易入手的框架,適合作為分布式的入手框架,下面是簡(jiǎn)單的搭建過(guò)程,對(duì)Dubbo+zookeeper分布式服務(wù)搭建過(guò)程感興趣的朋友一起看看吧2022-04-04
IDEA插件之彩虹括號(hào)Rainbow?Brackets使用介紹
這篇文章主要為大家介紹了IDEA插件之彩虹括號(hào)Rainbow?Brackets使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
java+Okhttp3調(diào)用接口的實(shí)例
這篇文章主要介紹了java+Okhttp3調(diào)用接口的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
解決maven打包排除類(lèi)不生效maven-compiler-plugin問(wèn)題
總結(jié):在Spring Boot項(xiàng)目B中作為項(xiàng)目A的依賴(lài)時(shí),排除啟動(dòng)類(lèi)不生效的原因是被其他類(lèi)引用或父POM引入,解決方法是跳過(guò)test編譯或注釋掉@SpringBootTest(classes={BApplication.class})2024-11-11

