SpringBoot事件發(fā)布與監(jiān)聽超詳細講解
應(yīng)用場景
比如: 我們現(xiàn)在的應(yīng)用是Maven聚合工程的多模塊方式開發(fā)?,F(xiàn)在有biz模塊、bill模塊、pay模塊。biz模塊有一個用戶注冊功能,bill模塊有一個發(fā)生郵件的功能,每次用戶注冊都需要調(diào)用bill模塊的發(fā)送郵件功能。我們只需要在pom引入bill模塊的依賴,注入bill模塊發(fā)送郵件的service接口,即可以完成操作。
現(xiàn)在業(yè)務(wù)有調(diào)整,pay模塊也需要完成支付業(yè)務(wù)發(fā)送郵件,也需要和上面操作一樣,操作會很費時費力。有沒有一種辦法可以把它們解耦出來?
答案是有的,我們可以使用事件的方式,biz模塊只要注冊成功,發(fā)布一個用戶注冊成功的事件,pay模塊支付成功,發(fā)布一個支付成功的事件。讓bill模塊監(jiān)聽了此事件的去做剩下的發(fā)送郵件事件就好了。對于事件發(fā)布者而言,不需要關(guān)心誰監(jiān)聽了該事件,以此來解耦業(yè)務(wù)。注意:必須在同一個Spring容器內(nèi),不能完成跨容器的通信,跨容器的通信可以考慮中間件消息隊列
Spring的事件應(yīng)該是在3.x版本就發(fā)布的功能了,并越來越完善,其為bean和bean之間的消息通信提供了支持。
概述
ApplicationEvent以及Listener是Spring為我們提供的一個事件監(jiān)聽、訂閱的實現(xiàn),內(nèi)部實現(xiàn)原理是觀察者設(shè)計模式,設(shè)計初衷也是為了系統(tǒng)業(yè)務(wù)邏輯之間的解耦,提高可擴展性以及可維護性。
- ApplicationEvent就是Spring的事件接口
- ApplicationListener就是Spring的事件監(jiān)聽器接口,所有的監(jiān)聽器都實現(xiàn)該接口
- ApplicationEventPublisher是Spring的事件發(fā)布接口,ApplicationContext實現(xiàn)了該接口
其執(zhí)行的流程大致為:
當(dāng)一個事件源產(chǎn)生事件時,它通過事件發(fā)布器ApplicationEventPublisher發(fā)布事件,然后事件廣播器ApplicationEventMulticaster會去事件注冊表ApplicationContext中找到事件監(jiān)聽器ApplicationListnener,并且逐個執(zhí)行監(jiān)聽器的onApplicationEvent方法,從而完成事件監(jiān)聽器的邏輯。
自定義事件發(fā)布和監(jiān)聽
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
自定義事件源和實體
Spring中,事件源不強迫繼承ApplicationEvent接口的,也就是可以直接發(fā)布任意一個對象類(實體類,Map,List,String等任意對象類)。但內(nèi)部其實是使用PayloadApplicationEvent類進行包裝了一層。
@TableName("user")
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID,value = "cate_id")
private String cateId;
@TableField("cate_code")
private String cateCode;
@TableField("user_name")
private String username;
@TableField("age")
private Integer age;
@TableField("sex")
private String sex;
}
public class SendEmailEvent extends ApplicationEvent {
private User user;
public SendEmailEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "SendEmailEvent{" +
"user=" + user +
'}';
}
}發(fā)布事件
這里我手動處理的事務(wù),也可以使用@Transactional處理。這里我主要模擬發(fā)布事件發(fā)生異常和監(jiān)聽事件中發(fā)生異常的情況。
默認情況下,發(fā)布事件和監(jiān)聽事件都是在一個線程中同步執(zhí)行的。業(yè)務(wù)發(fā)布事件類中,當(dāng)try代碼塊int a = 1 / 0;模擬保存用戶異常,會進入到catch塊中回滾保存事務(wù),不會發(fā)布事件。
當(dāng)業(yè)務(wù)類保存用戶,并執(zhí)行事件發(fā)布邏輯成功,使用@EventListener的監(jiān)聽類中發(fā)生異常,也會到發(fā)布事件的catch塊中回滾保存的事務(wù)。即證明了發(fā)布事件和監(jiān)聽事件都是在一個線程中同步執(zhí)行的。事務(wù)是一致性的。
@Service
@Slf4j
//@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private ApplicationEventPublisher eventPublisher;
@Resource
private DataSourceTransactionManager transactionManager;
@Override
public Boolean saveUser(User user) {
// 手動開啟事務(wù) start
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transaction = transactionManager.getTransaction(definition);
// 手動開啟事務(wù) end
log.info("[UserService] start insert saveUser");
// 保存用戶
Boolean result = false;
try {
if (Objects.nonNull(user)) {
// 保存用戶
result = this.save(user);
// 模擬異常回滾
// int a = 1 / 0;
}
// 1、發(fā)布ApplicationEvent對象
eventPublisher.publishEvent(new SendEmailEvent(this,user));
// 2、發(fā)送郵件 (發(fā)布任意一個對象類)
eventPublisher.publishEvent(user.getUsername());
log.info("[UserService] finish insert saveUser");
// 手動提交事務(wù) end
transactionManager.commit(transaction);
// 手動提交事務(wù) start
}
// 異?;貪L
catch (Exception e) {
log.error("saveUser異常 fail:{}", e);
// 手動回滾事務(wù) start
transactionManager.rollback(transaction);
// 手動回滾事務(wù) end
}
return result;
}
}監(jiān)聽類使用ApplicationListener方式
@Component
@Slf4j
public class CustomListener implements ApplicationListener<SendEmailEvent> {
@Override
public void onApplicationEvent(SendEmailEvent event) {
send(event.getUser().getUsername());
}
public void send(String username) {
String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4);
log.info("生成驗證碼: {}",code);
// 可能由于網(wǎng)絡(luò)帶寬,發(fā)送驗證碼異常,模擬監(jiān)聽事件異常
int i = 1/0;
log.info("【{}】本次登陸驗證碼為:{},請在5分鐘內(nèi)完成驗證,請勿將驗證碼泄露給他人。",username,code);
}
}
監(jiān)聽類使用@EventListener方式
使用@EventListener的condition可以實現(xiàn)更加精細的事件監(jiān)聽,condition支持SpEL表達式,可根據(jù)事件源的參數(shù)來判斷是否監(jiān)聽。事件源為對象,可以使用對象.屬性。如: @EventListener(condition = "#customEvent.billType=='pay'")
@EventListener(condition = "#username.equals('風(fēng)雨')")
public void sendEmail(String username) {
log.info("transactionEventListener start");
// 發(fā)生驗證碼
send(username);
log.info("transactionEventListener finish");
}
public void send(String username) {
String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4);
log.info("生成驗證碼: {}",code);
// 可能由于網(wǎng)絡(luò)帶寬,發(fā)送驗證碼異常,模擬監(jiān)聽事件異常
int i = 1/0;
log.info("【{}】本次登陸驗證碼為:{},請在5分鐘內(nèi)完成驗證,請勿將驗證碼泄露給他人。",username,code);
}Spring事件最佳實踐
通用泛型類事件
事件類可以不繼承ApplicationEvent類,定義通用泛型類事件對象,其他事件對象繼承該對象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GenericEvent<T> {
/**
* 業(yè)務(wù)類型
*/
private String billType;
private T data;
}發(fā)送銀行成功事件
public class SendBankSuccessEvent extends GenericEvent<SendBank>{
public SendBankSuccessEvent(String billType, SendBank data) {
super(billType, data);
}
}發(fā)送銀行失敗事件
public class SendBankFailEvent extends GenericEvent<SendBank>{
public SendBankFailEvent(String billType, SendBank data) {
super(billType, data);
}
}發(fā)送銀行退回事件
public class SendBankGoBackEvent extends GenericEvent<Map>{
public SendBankGoBackEvent(String billType, Map data) {
super(billType, data);
}
}@Data
public class SendBank {
private String username;
private String paymentAccount;
private String money;
}
發(fā)布事件類
發(fā)送事件類一般可以直接是Service層的實現(xiàn)類
@Service
public class SendBankServiceImpl {
@Resource
private ApplicationContext applicationContext;
public void sendBank(String event, String billType) {
SendBank sendBank = new SendBank();
sendBank.setUsername("風(fēng)雨修");
sendBank.setPaymentAccount("中國建設(shè)銀行");
sendBank.setMoney("2000");
if ("success".equals(event)) {
if ("DYZC".equals(billType)) {
applicationContext.publishEvent(new SendBankSuccessEvent("DYZC", sendBank));
}
if ("ZYSL".equals(billType)) {
applicationContext.publishEvent(new SendBankSuccessEvent("ZYSL", sendBank));
}
if ("SLHZ".equals(billType)) {
applicationContext.publishEvent(new SendBankSuccessEvent("SLHZ", sendBank));
}
} else if ("fail".equals(event)){
applicationContext.publishEvent(new SendBankFailEvent("ZYSL", sendBank));
} else if ("goBack".equals(event)){
HashMap<Object, Object> map = Maps.newHashMap();
map.put("username", "喜羊羊sk");
map.put("age", 23);
map.put("sex","男");
map.put("hobby","hello world");
applicationContext.publishEvent(new SendBankGoBackEvent("SLHZ", map));
}
}
}
事件監(jiān)聽類
@EventListener注解屬性
- classes: 此偵 聽器處理的事件類。
- condition:用于使事件處理成為條件的SpEL表達式,如果表達式的計算結(jié)果為布爾值true,則將處理該事件
一個事件可以有很多個監(jiān)聽者,可以通過classes屬性指定具體監(jiān)聽事件類,通過condition可以指定事件類的屬性值滿足條件才生效執(zhí)行
@Component
@Slf4j
public class GenericEventListener {
/**
* 同一種事件,不同業(yè)務(wù)類型。實現(xiàn)不同的監(jiān)聽處理
* 通過 @EventListener注解屬性
* classes: 此 偵聽器處理的事件類。
* condition:用于使事件處理成為條件的SpEL表達式,如果表達式的計算結(jié)果為布爾值true,則將處理該事件
*/
@EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('dyzc')")
public void DYZCsendBankSuccessListener(GenericEvent<SendBank> event) {
log.info("event: {}", event);
log.info("data: {}", event.getData());
}
@EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('zysl')")
public void ZYSLsendBankSuccessListener(GenericEvent<SendBank> event) {
log.info("event: {}", event);
log.info("data: {}", event.getData());
}
@EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('slhz')")
public void SLHZsendBankSuccessListener(GenericEvent<SendBank> event) {
log.info("event: {}", event);
log.info("data: {}", event.getData());
}
/**
* end
*
*/
@EventListener(classes = SendBankFailEvent.class)
public void sendBankFailListener(GenericEvent<SendBank> event) {
log.info("event: {}", event);
log.info("data: {}", event.getData());
}
@EventListener(classes = SendBankGoBackEvent.class)
public void sendGoBackListener(GenericEvent<Map> event) {
log.info("event: {}", event);
log.info("data: {}", event.getData());
}
}異步監(jiān)聽處理
默認情況下,發(fā)布事件和監(jiān)聽事件都是同步執(zhí)行,在一個線程中的。在需要異步處理時,可以在方法上加上@Async進行異步化操作。此時,可以定義一個線程池,同時開啟異步功能,加入@EnableAsync。
@Async
@EventListener
public void sendEmail(String username) {
log.info("transactionEventListener start");
// 發(fā)生驗證碼
send(username);
log.info("transactionEventListener finish");
}
事務(wù)綁定事件
當(dāng)一些場景下,比如在用戶注冊成功后,即數(shù)據(jù)庫事務(wù)提交了,之后再異步發(fā)送郵件等,不然會發(fā)生數(shù)據(jù)庫插入失敗,但事件卻發(fā)布了,也就是郵件發(fā)送成功了的情況。此時,我們可以使用@TransactionalEventListener注解或者TransactionSynchronizationManager類來解決此類問題,也就是:事務(wù)成功提交后,再執(zhí)行事件。
TransactionSynchronizationManager類
@EventListener
public void afterRegisterSendMail(SendEmailEvent event) {
// Spring 4.2 之前
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
send(username);
}
}
);
}
上面的代碼將在事務(wù)提交后執(zhí)行.如果在非事務(wù)context中將拋出java.lang.IllegalStateException: Transaction synchronization is not active
@EventListener
public void afterRegisterSendMail(SendEmailEvent event) {
// Spring 4.2 之前
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
send(event);
}
});
} else {
send(username);
}
}這樣無論是否有事務(wù)都能兼容啦.
@TransactionalEventListener
Spring 4.2除了EventListener之外,額外提供了新的注解@TransactionalEventListener
@TransactionalEventListener
public void sendEmail(String username) {
log.info("transactionEventListener start");
// 發(fā)生驗證碼
send(username);
log.info("transactionEventListener finish");
}
這個注解的強大之處在于可一直控制事務(wù)的 before/after commit, after rollback ,after completion (commit或 rollback). 默認情況下,在事務(wù)中的Event將會被執(zhí)行,
到此這篇關(guān)于SpringBoot事件發(fā)布與監(jiān)聽超詳細講解的文章就介紹到這了,更多相關(guān)SpringBoot事件發(fā)布與監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解SpringBoot實現(xiàn)ApplicationEvent事件的監(jiān)聽與發(fā)布
- spring event 事件異步處理方式(發(fā)布,監(jiān)聽,異步處理)
- Spring事件發(fā)布監(jiān)聽,順序監(jiān)聽,異步監(jiān)聽方式
- SpringBoot事件發(fā)布和監(jiān)聽詳解
- 詳解Spring事件發(fā)布與監(jiān)聽機制
- 解析Spring事件發(fā)布與監(jiān)聽機制
- 詳解SpringBoot 發(fā)布ApplicationEventPublisher和監(jiān)聽ApplicationEvent事件
- Spring的事件發(fā)布與監(jiān)聽方式案例講解
相關(guān)文章
Spring?Boot?@Autowired?@Resource屬性賦值時機探究
這篇文章主要為大家介紹了Spring?Boot?@Autowired?@Resource屬性賦值時機,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07
OpenTelemetry初識及調(diào)用鏈Trace詳解
這篇文章主要為為大家介紹了OpenTelemetry初識及調(diào)用鏈Trace詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
Java并發(fā)之原子性 有序性 可見性及Happen Before原則
一提到happens-before原則,就讓人有點“丈二和尚摸不著頭腦”。這個涵蓋了整個JMM中可見性原則的規(guī)則,究竟如何理解,把我個人一些理解記錄下來。下面可以和小編一起學(xué)習(xí)Java 并發(fā)四個原則2021-09-09
SpringBoot整合Gson 整合Fastjson的實例詳解
這篇文章主要介紹了SpringBoot整合Gson 整合Fastjson的實例詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
spring-data-jpa中findOne與getOne的區(qū)別說明
這篇文章主要介紹了spring-data-jpa中findOne與getOne的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

