業(yè)務(wù)需求場景:按照一定的順序做一些事情扼菠,例如向A表插入數(shù)據(jù)事物提交之后摄杂,向B表中插入歷史記錄,最后向C表插入循榆。
事件機制
事件監(jiān)聽機制可以理解為是一種觀察者模式析恢,有數(shù)據(jù)發(fā)布者(事件源)和數(shù)據(jù)接受者(監(jiān)聽器);在Java中秧饮,事件對象都是繼承java.util.EventObject對象映挂,事件監(jiān)聽器都是java.util.EventListener實例;Spring中
Java事件
EventObject
java.util.EventObject是事件狀態(tài)對象的基類,它封裝了事件源對象以及和事件相關(guān)的信息盗尸。所有java的事件類都需要繼承該類柑船。
EventListener
java.util.EventListener是一個標(biāo)記接口,就是說該接口內(nèi)是沒有任何方法的泼各。所有事件監(jiān)聽器都需要實現(xiàn)該接口鞍时。事件監(jiān)聽器注冊在事件源上,當(dāng)事件源的屬性或狀態(tài)改變的時候扣蜻,調(diào)用相應(yīng)監(jiān)聽器內(nèi)的回調(diào)方法寸癌。
Source
事件源不需要實現(xiàn)或繼承任何接口或類,它是事件最初發(fā)生的地方弱贼。因為事件源需要注冊事件監(jiān)聽器蒸苇,所以事件源內(nèi)需要有相應(yīng)的盛放事件監(jiān)聽器的容器。
Spring 事件
在 Spring 中吮旅,初始化容器時會調(diào)用 org.springframework.context.ConfigurableApplicationContext 接口中的 reFresh() 方法進行 Bean的加載溪烤,該方法會進行事件的監(jiān)聽注冊。
代碼實例
UserEvent:user事件對象庇勃,繼承ApplicationEvent
public class UserEvent<T> extends ApplicationEvent {
? ? private T data;
? ? public UserEvent(T source) {
? ? ? ? super(source);
? ? ? ? this.data =source;
? ? }
? ? public T getData() {
? ? ? ? return this.data;
? ? }
? ? public void setData(final T data) {
? ? ? ? this.data = data;
? ? }
}
發(fā)布插入用戶的事件
@Autowired
private ApplicationEventPublisher publisher;
/**
? * 發(fā)布插入user表的事件
? */
private void sendInsertUser() {
? ? User user = new User();
? ? user.setId(20210317);
? ? user.setUsername("測試張三");
? ? UserEvent<User> userEvent = new UserEvent<>(user);
? ? // 發(fā)布事件
? ? publisher.publishEvent(userEvent);
}
插入用戶監(jiān)聽器:同步操作
/**
* 監(jiān)聽插入User的事件
*/
@EventListener
public void insertUserLinster(UserEvent<User> userEvent) {
User data = userEvent.getData();
String message = String.format("get user message: %s", JSON.toJSONString(data));
log.info(message);
// 后續(xù)操作檬嘀;繼續(xù)處理
}
測試代碼:
/**
? * SpringBoot Event 使用
? *
? * @return
? */
@Override
public BackResult testEvent() {
? ? BackResult backResult = new BackResult();
? ? try {
? ? ? ? // 向user 表插入數(shù)據(jù)
? ? ? ? insertUsers();
? ? ? ? // 添加user插入對象操作
? ? ? ? sendInsertUser();
? ? ? ? backResult.setSuccess(true);
? ? ? ? backResult.setMessage("操作成功!");
? ? } catch (Exception e) {
? ? ? ? backResult.setMessage(e.getMessage());
? ? ? ? backResult.setSuccess(false);
? ? }
? ? return backResult;
}
insertUsers:開啟事務(wù)
/**
? * 插入開啟事物
? */
@Transactional(rollbackFor = Exception.class)
public void insertUsers() {
? ? insertUserBatch();
}
思考
測試代碼中,方法執(zhí)行順序應(yīng)該是insertUsers()執(zhí)行完成责嚷,事務(wù)提交之后在執(zhí)行sendInsertUser()鸳兽,所以事件監(jiān)聽器因該改進一下,在事務(wù)提交之后去執(zhí)行罕拂。改進代碼如下揍异。
插入用戶監(jiān)聽器:
/**
? * 監(jiān)聽插入User的事件 : 在上個事務(wù)提交之后在執(zhí)行
? */
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,fallbackExecution = true)
public void insertUserLinster(UserEvent<User> userEvent) {
User data = userEvent.getData();
String message = String.format("get user message: %s", JSON.toJSONString(data));
log.info(message);
// 后續(xù)操作全陨;繼續(xù)處理
}
還有一個問題就是沒有考慮,同步和異步的問題衷掷,上面的方式是同步的辱姨,這樣會影響正常的業(yè)務(wù)邏輯,為了不影響正常的業(yè)務(wù)操作戚嗅,可以將監(jiān)聽器修改為異步執(zhí)行的即可雨涛。使用 @Async 標(biāo)記即可,注意前提條件是:使用 @EnableAsync 開啟 Spring 異步懦胞。
/**
? * 監(jiān)聽插入User的事件 : 在上個事務(wù)提交之后在執(zhí)行
? */
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,fallbackExecution = true)
public void insertUserLinster(UserEvent<User> userEvent) {
? ? User data = userEvent.getData();
? ? String message = String.format("get user message: %s", JSON.toJSONString(data));
? ? log.info(message);
? ? // 后續(xù)操作替久;繼續(xù)處理
}
完整代碼地址 :https://gitee.com/Marlon_Brando/back/commit/de586caed15004d0e8e3ae2e9a59f6f170d726d1
————————————————
版權(quán)聲明:本文為CSDN博主「MarlonBrando1998」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議躏尉,轉(zhuǎn)載請附上原文出處鏈接及本聲明侣肄。
原文鏈接:https://blog.csdn.net/qq_37248504/article/details/115269995