我們知道觀察者模式可以實現代碼的解耦,而spring的event模型就是這種設計模式的極佳體現。一個事件包含:事件發佈、監聽、和事件源。在spring中我們可以通過ApplicationContext的publishEvent方法去發佈事件;通過實現ApplicationListener接口來自定義自己的監聽器;繼承ApplicationEvent類來實現事件源。下面以一個實例來說明:
1.spring下使用event模型
1.1 定義event
/**
* event的基類
*
* @author 94977
* @create 2018/7/22
*/
public abstract class BaseEvent extends ApplicationEvent {
public BaseEvent(Object source) {
super(source);
}
}
public class FaceEvent extends BaseEvent {
/**
* @author 94977
* @time 2018/7/22 15:50
* @param * @param null
* @return
* @description 必須要實現的構造方法
*/
public FaceEvent(User user) {
super(user);
}
}
1.2 event的監聽處理類。監聽類實現ApplicationListener 裏onApplicationEvent方法即可
@Component
public class FaceEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof FaceEvent){
User user = (User) event.getSource();
LOGGER.info("===> 收到人臉事件: {}",user);
// .....
System.out.println("人臉事件處理結束。。。");
}
}
}
當然通過event instanceof FaceEvent判斷事件源來處理的方式不是很優雅。有更好的方式,接口ApplicationListener支持泛型,可以通過泛型來判斷處理的事件源。如下只處理FaceEvent源。
@Component
public class FaceEventListener extends BaseEventListener implements ApplicationListener<FaceEvent> {
@Override
public void onApplicationEvent(FaceEvent event) {
User user = (User) event.getSource();
LOGGER.info("===> 收到人臉事件: {}",user);
// .....
System.out.println("人臉事件處理結束。。。");
}
}
如果要實現有序的監聽,實現SmartApplicationListener 接口即可
1.3 發佈事件
@Service
public class FaceHandler {
@Autowired
private ApplicationContext applicationContext;
public void handle(){
User user = new User();
user.setAge(34);
user.setUsername("人臉事件");
user.setHobby("抓拍");
//發佈事件
applicationContext.publishEvent(new FaceEvent(user));
//進行其他業務處理
}
}
以上即可。
2.evnet模型的注意點
事件沒要處理的監聽器,就會被拋棄。
一個事件可以同時被多個監聽處理類監聽處理。
以上處理事件都是同步的,如果發佈事件處的業務存在事務,監聽器處理也會在相同的事務中。這個一定要注意!如果對於事件的處理不想受到影響,可以onApplicationEvent方法上加@Aync支持異步(參考taskExecutor的使用)。
- 一種更優雅的方式——@EventListener
3.1定義事件源
public abstract class BaseEvent<T> extends ApplicationEvent {
private static final long serialVersionUID = 895628808370649881L;
protected T eventData;
public BaseEvent(Object source, T eventData){
super(source);
this.eventData = eventData;
}
public BaseEvent(T eventData){
super(eventData);
}
public T getEventData() {
return eventData;
}
public void setEventData(T eventData) {
this.eventData = eventData;
}
}
3.2需要發佈的事件繼承此BaseEvent
public class FaceEvent extends BaseEvent<User> {
public FaceEvent(User user) {
super(user);
}
public FaceEvent(Object source, User user){
super(source,user);
}
}
如果代碼結構較複雜,多處發佈相同的事件,建議發佈事件時將this作爲source傳遞,便於通過分析日誌確定發佈源。
3.3 監聽事件@EventListener
在spring4.2中我們可以以更加簡潔的方式來監聽event的發佈,監聽事件我們不必再實現ApplicationListener接口了,只要在方法上添加註解@EventListener即可:
@EventListener
public void onApplicationEvent(FaceEvent event) {
User user = (User) event.getSource();
String name = Thread.currentThread().getName();
LOGGER.info("===> 收到人臉事件: {},線程名爲: {}",user,name);
}
會根據方法參數類型來自動監聽相應事件的發佈。
如果要監聽多個事件類型的發佈,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring會多次調用此方法來處理多個事件。但是注意此時,方法參數不能有多個,否則會發生轉換異常,可以將使用多個事件的父類作爲唯一的方法參數來接收處理事件,但除非必要否則並不推薦監聽多個事件的發佈。
如果有多個監聽器監聽同一事件,我們可以在方法上使用spring的@order註解來定義多個監聽器的順序,如:
@EventListener
@Order(4)
public void onApplicationEvent(FaceEvent event) {
User user = (User) event.getSource();
LOGGER.info("===> A 收到人臉事件: {}",user);
}
@EventListener({FaceEvent.class,ArmEvent.class})
@Order(3)
public void onApplicationEvent3(Object event) {
if(event instanceof FaceEvent){
LOGGER.info("===> B 收到人臉事件: {}",((FaceEvent) event).getEventData());
}else if(event instanceof ArmEvent){
ArmEvent armEvent = (ArmEvent) event;
LOGGER.info("===> B 收到臂膀事件: {}",armEvent.getEventData());
}
}
這真的是很方便。
@EventListener還有一個屬性,condition()裏可以使用SPEL表達式來過濾監聽到事件,即只有符合某種條件的才進行接收處理。暫時還用不到。
3.4 監聽事件時的事務隔離
@TransactionalEventListener和@EventListener都可以監聽事件,但前者可以對發佈事件和監聽事件進行一些事務上的隔離。@TransactionalEventListenerr指不和發佈事件的方法在同一個事務內,發佈事件的方法事務結束後纔會執行本監聽方法,監聽邏輯內發生異常不會回滾發佈事件方法的事務。
@Transactional(rollbackFor = Exception.class)
public void handle(){
User user = new User();
user.setAge(34);
user.setUsername("人臉事件");
user.setHobby("抓拍");
//處理完上面的邏輯後,發佈事件
EventPublisherUtil.publishEvent(new FaceEvent(user));
//數據庫添加操作
Integer integer = deviceAlarmService.addDevice();
}
可以看到發佈事件的方法處在事務控制中,我們使用@TransactionalEventListener來監聽事件:
@TransactionalEventListener(fallbackExecution = true)
public void onApplicationEvent(FaceEvent event) {
User user = event.getEventData();
LOGGER.info("===> A 收到人臉事件: {}}",user);
//@TransactionalEventListener指不和發佈事件的在同一個事務內,發佈事件的方法事務結束後纔會執行本方法
// ,本方法發生異常不會回滾發佈事件的事務,
throw new RuntimeException("監聽事件拋出異常");
}
運行結果,addDevice正常在數據庫插入數據,但是修改爲@EventListener監聽則插入數據失敗。
@TransactionalEventListener有一個屬性爲fallbackExecution,默認爲false,指發佈事件的方法沒有事務控制時,監聽器不進行監聽事件,此爲默認情況! fallbackExecution=true,則指發佈事件的方法沒有事務控制時,監聽方法仍可以監聽事件進行處理。
/**
* Whether the event should be processed if no transaction is running.
*/
boolean fallbackExecution() default false;
剛纔我們說到使用@TransactionalEventListener會在發佈事件的方法事務結束後執行監聽方法,但其實我們還可以進行細化的控制。它有一個屬性爲TransactionPhase,默認爲TransactionPhase.AFTER_COMMIT,即事務提交後。還可以根據需要選擇AFTER_COMPLETION、BEFORE_COMMIT、AFTER_ROLLBACK。
但仍需注意,如果fallbackExecution=false,且發佈事件的方法沒有事務控制時,監聽器根本不會監聽到事件,此處的TransactionPhase也就沒有意義了。