前言
在前端中我們經常使用事件機制,如滑鼠點選事件等,而在java中,java也為我們設定了事件機制。
我們先模仿前端的事件機制,使用程式碼進行簡單實現滑鼠點選事件。
class Mouse {
private List<mouselistener> listeners = new ArrayList<mouselistener>();
private int listenerCallbacks = 0;
public void addListenerCallback() {
listenerCallbacks++;
}
public int getListenerCallbacks() {
return listenerCallbacks;
}
public void addListener(MouseListener listener) {
listeners.add(listener);
}
public void click() {
System.out.println("Clicked !");
for (MouseListener listener : listeners) {
listener.onClick(this);
}
}
}
interface MouseListener {
public void onClick(Mouse source);
}
public class EventBasedTest {
@Test
public void test() {
Mouse mouse = new Mouse();
mouse.addListener(new MouseListener() {
@Override
public void onClick(Mouse mouse) {
System.out.println("Listener#1 called");
mouse.addListenerCallback();
}
});
mouse.addListener(new MouseListener() {
@Override
public void onClick(Mouse mouse) {
System.out.println("Listener#2 called");
mouse.addListenerCallback();
}
});
mouse.click();
}
}
列印輸出如下所示
Clicked !
Listener#1 called
Listener#2 called
準備
實現java的Event需要實現三部分
- 事件類:事件的載體。
- 釋出者:釋出事件。
- 監聽者:監聽並處理事件。
我們透過一個業務場景來具體實現java事件。假如我們有個需求,使用者建立成功後給使用者傳送一個郵件。這裡有兩個事情要做:
- 建立使用者
給使用者傳送郵件
這個很簡單。@Service public class EmailService { @Transactional public void sendEmail(String email) { //send email } } @Service public class UserService { private final EmailService emailService; private final UserRepository userRepository; public UserService(EmailService emailService, UserRepository userRepository) { this.emailService = emailService; this.userRepository = userRepository; } @Transactional public User createUser(User user) { User newUser = this.userRepository.save(user); this.emailService.sendEmail(user.getEmail()); return newUser; } }
但這種實現是有問題的。我們想一下,這個功能的核心是建立使用者,而傳送郵件是一個副作用(傳送郵件不能影響使用者的建立),如果把這兩個操作放在一個事務中會有什麼問題?其實很明顯,如果建立使用者時丟擲異常,事務回滾,方法提前退出,那麼也不會傳送郵件,這是正常的。但是下面兩個場景是不可接受的:
- 如果郵件傳送失敗,事務發生回滾,使用者建立失敗。
- 如果郵件傳送成功後,事務提交失敗,使用者收到了郵件,可是使用者建立失敗,這不是我們所希望的。
我們先透過event機制進行業務解耦,同時達到易擴充套件的原則。
事件類
事件類為事件的載體,也就是所釋出者釋出的東西和監聽者接收的東西都只能是事件,我們所要在釋出者和監聽者之間傳遞的東西,也定義在我們的自定義事件類裡。
只要繼承ApplicationEvent
,便是一個事件類。
public class UserCreatedEvent {
private final User user;
public UserCreatedEvent(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
釋出者
釋出者釋出事件類,我們可以呼叫ApplicationEventPublisher
的publishEvent()
方法釋出事件類。
@Service
public class CustomerService {
private final UserRepository userRepository;
private final ApplicationEventPublisher applicationEventPublisher;
public CustomerService(UserRepository userRepository, ApplicationEventPublisher applicationEventPublisher) {
this.userRepository = userRepository;
this.applicationEventPublisher = applicationEventPublisher;
}
@Transactional
public Customer createCustomer(User user) {
User newUser = this.userRepository.save(user);
final UserCreatedEvent event = new UserCreatedEvent(newUser);
this.applicationEventPublisher.publishEvent(event);
return newUser;
}
}
監聽者
監聽者接受釋出者釋出的事件並處理。
實現也很簡單,在方法上加入@EventListener
註解並接收事件類引數。
@Component
public class UserCreatedEventListener {
private final EmailService emailService;
public UserCreatedEventListener(EmailService emailService) {
this.emailService = emailService;
}
@EventListener
public void processUserCreatedEvent(UserCreatedEvent event) {
this.emailService.sendEmail(event.getUser().getEmail());
}
}
如果我們有兩個監聽者,可以透過@Order註解來控制兩個監聽者的執行順序。
雖然我們用EventListener的方式解耦了業務程式碼,看著很高大上,但是有興趣研究原始碼就可以發現,這在底層還是一個普通的方法呼叫,這並沒有達到我們的目的。
如果我們在Listener方法上加@Async讓其非同步執行,這跟不同的非同步呼叫並無二異,可能會導致先傳送簡訊後提交事務。這也不符合我們提出的要求。
事務隔離
使用@TransactionalEventListener註解,TransactionalEventListener註解是對EventListener的增強,被註解的方法可以在事務的不同階段去觸發執行,如果事件未在啟用的事務中釋出,除非顯式設定了 fallbackExecution() 標誌為true,否則該事件將被丟棄;如果事務正在執行,則根據其 TransactionPhase 處理該事件。
- AFTER_COMMIT - 預設設定,在事務提交後執行
- AFTER_ROLLBACK - 在事務回滾後執行
- AFTER_COMPLETION - 在事務完成後執行(不管是否成功)
- BEFORE_COMMIT - 在事務提交前執行
@Component
public class UserCreatedEventListener {
private final EmailService emailService;
public UserCreatedEventListener(EmailService emailService) {
this.emailService = emailService;
}
@TransactionalEventListener
public void processUserCreatedEvent(UserCreatedEvent event) {
this.emailService.sendEmail(event.getUser().getEmail());
}
}
這樣,我們在事務提交後執行傳送郵件方法。即保證了傳送郵件功能不對儲存使用者功能進行影響,也不會使得傳送郵件功能和儲存使用者功能沒有關聯。
總結
對修改關閉,對擴充套件開放。
參考:
Spring5原始碼解析-Spring框架中的事件和監聽器TransactionalEventListener使用場景以及實現原理,最後要躲個大坑
Spring Event的初步講解
訂閱釋出模式和觀察者模式的區別