spring event的事件驱动模型的最佳实践@EventListener
文章目錄
- 1.spring下使用event模型
- 1.1 定義event
- 1.2 event的監(jiān)聽處理類。監(jiān)聽類實現(xiàn)ApplicationListener 里onApplicationEvent方法即可
- 1.3 發(fā)布事件
- 2.evnet模型的注意點
- 3. 一種更優(yōu)雅的方式——@EventListener
- 3.1 發(fā)布事件
- 3.2 定義事件源
- 3.3 監(jiān)聽事件@EventListener
- 3.4 監(jiān)聽事件時的事務(wù)隔離
? ? ? ?我們知道觀察者模式可以實現(xiàn)代碼的解耦,而spring的event模型就是這種設(shè)計模式的極佳體現(xiàn)。一個事件包含:事件發(fā)布、監(jiān)聽、和事件源。在spring中我們可以通過ApplicationContext的publishEvent方法去發(fā)布事件;通過實現(xiàn)ApplicationListener接口來自定義自己的監(jiān)聽器;繼承ApplicationEvent類來實現(xiàn)事件源。下面以一個實例來說明:
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 必須要實現(xiàn)的構(gòu)造方法*/public FaceEvent(User user) {super(user);} }1.2 event的監(jiān)聽處理類。監(jiān)聽類實現(xiàn)ApplicationListener 里onApplicationEvent方法即可
@Component public class FaceEventListener implements ApplicationListener {@Overridepublic void onApplicationEvent(ApplicationEvent event) {if(event instanceof FaceEvent){User user = (User) event.getSource();LOGGER.info("===> 收到人臉事件: {}",user);// .....System.out.println("人臉事件處理結(jié)束。。。");}} }? ? ? ?當(dāng)然通過event instanceof FaceEvent判斷事件源來處理的方式不是很優(yōu)雅。有更好的方式,接口ApplicationListener支持泛型,可以通過泛型來判斷處理的事件源。如下只處理FaceEvent源。
@Component public class FaceEventListener extends BaseEventListener implements ApplicationListener<FaceEvent> {@Overridepublic void onApplicationEvent(FaceEvent event) {User user = (User) event.getSource();LOGGER.info("===> 收到人臉事件: {}",user);// .....System.out.println("人臉事件處理結(jié)束。。。");} }如果要實現(xiàn)有序的監(jiān)聽,實現(xiàn)SmartApplicationListener 接口即可
1.3 發(fā)布事件
@Service public class FaceHandler {@Autowiredprivate ApplicationContext applicationContext;public void handle(){User user = new User();user.setAge(34);user.setUsername("人臉事件");user.setHobby("抓拍");//發(fā)布事件applicationContext.publishEvent(new FaceEvent(user));//進行其他業(yè)務(wù)處理}以上即可。
2.evnet模型的注意點
- 事件沒要處理的監(jiān)聽器,就會被拋棄。
- 一個事件可以同時被多個監(jiān)聽處理類監(jiān)聽處理。
- 以上處理事件都是同步的,如果發(fā)布事件處的業(yè)務(wù)存在事務(wù),監(jiān)聽器處理也會在相同的事務(wù)中。這個一定要注意!如果對于事件的處理不想受到影響,可以onApplicationEvent方法上加@Aync支持異步(參考taskExecutor的使用)。
- 原理部分可以參考 博客 事件體系
3. 一種更優(yōu)雅的方式——@EventListener
3.1 發(fā)布事件
? ? ? ?我們可以通過工具類發(fā)布來避免在代碼耦合注入ApplicationContext,工具類實現(xiàn)ApplicationEventPublisherAware 接口,具體可參考spring的aware學(xué)習(xí)。
? ? ? ?這里有一個小細節(jié),如果通過注入ApplicationContext的方式來發(fā)布事件,idea在代碼左邊會有一個類似耳機的小圖標(biāo),點擊可以跳到監(jiān)聽此發(fā)布事件的監(jiān)聽者位置,用工具類發(fā)布事件就沒有此提示了。
3.2 定義事件源
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;} }需要發(fā)布的事件繼承此BaseEvent
public class FaceEvent extends BaseEvent<User> {public FaceEvent(User user) {super(user);}public FaceEvent(Object source, User user){super(source,user);}}如果代碼結(jié)構(gòu)較復(fù)雜,多處發(fā)布相同的事件,建議發(fā)布事件時將this作為source傳遞,便于通過分析日志確定發(fā)布源。
3.3 監(jiān)聽事件@EventListener
? ? ? ?在spring4.2中我們可以以更加簡潔的方式來監(jiān)聽event的發(fā)布,監(jiān)聽事件我們不必再實現(xiàn)ApplicationListener接口了,只要在方法上添加注解@EventListener即可:
@EventListenerpublic void onApplicationEvent(FaceEvent event) {User user = (User) event.getSource();String name = Thread.currentThread().getName();LOGGER.info("===> 收到人臉事件: {},線程名為: {}",user,name);}會根據(jù)方法參數(shù)類型來自動監(jiān)聽相應(yīng)事件的發(fā)布。
? ? ? ?如果要監(jiān)聽多個事件類型的發(fā)布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring會多次調(diào)用此方法來處理多個事件。但是注意此時,方法參數(shù)不能有多個,否則會發(fā)生轉(zhuǎn)換異常,可以將使用多個事件的父類作為唯一的方法參數(shù)來接收處理事件,但除非必要否則并不推薦監(jiān)聽多個事件的發(fā)布。
- 如果有多個監(jiān)聽器監(jiān)聽同一事件,我們可以在方法上使用spring的@order注解來定義多個監(jiān)聽器的順序,如:
這真的是很方便。
- @EventListener還有一個屬性,condition()里可以使用SPEL表達式來過濾監(jiān)聽到事件,即只有符合某種條件的才進行接收處理。暫時還用不到。
3.4 監(jiān)聽事件時的事務(wù)隔離
- @TransactionalEventListener和@EventListener都可以監(jiān)聽事件,但前者可以對發(fā)布事件和監(jiān)聽事件進行一些事務(wù)上的隔離。@TransactionalEventListenerr指不和發(fā)布事件的方法在同一個事務(wù)內(nèi),發(fā)布事件的方法事務(wù)結(jié)束后才會執(zhí)行本監(jiān)聽方法,監(jiān)聽邏輯內(nèi)發(fā)生異常不會回滾發(fā)布事件方法的事務(wù)。
可以看到發(fā)布事件的方法處在事務(wù)控制中,我們使用@TransactionalEventListener來監(jiān)聽事件:
@TransactionalEventListener(fallbackExecution = true)public void onApplicationEvent(FaceEvent event) {User user = event.getEventData();LOGGER.info("===> A 收到人臉事件: {}}",user);//@TransactionalEventListener指不和發(fā)布事件的在同一個事務(wù)內(nèi),發(fā)布事件的方法事務(wù)結(jié)束后才會執(zhí)行本方法// ,本方法發(fā)生異常不會回滾發(fā)布事件的事務(wù),throw new RuntimeException("監(jiān)聽事件拋出異常");}運行結(jié)果,addDevice正常在數(shù)據(jù)庫插入數(shù)據(jù),但是修改為@EventListener監(jiān)聽則插入數(shù)據(jù)失敗。
- @TransactionalEventListener有一個屬性為fallbackExecution,默認為false,指發(fā)布事件的方法沒有事務(wù)控制時,監(jiān)聽器不進行監(jiān)聽事件,此為默認情況! fallbackExecution=true,則指發(fā)布事件的方法沒有事務(wù)控制時,監(jiān)聽方法仍可以監(jiān)聽事件進行處理。
- 剛才我們說到使用@TransactionalEventListener會在發(fā)布事件的方法事務(wù)結(jié)束后執(zhí)行監(jiān)聽方法,但其實我們還可以進行細化的控制。它有一個屬性為TransactionPhase,默認為TransactionPhase.AFTER_COMMIT,即事務(wù)提交后。還可以根據(jù)需要選擇AFTER_COMPLETION、BEFORE_COMMIT、AFTER_ROLLBACK。
? ? ? ?但仍需注意,如果fallbackExecution=false,且發(fā)布事件的方法沒有事務(wù)控制時,監(jiān)聽器根本不會監(jiān)聽到事件,此處的TransactionPhase也就沒有意義了。
總結(jié)
以上是生活随笔為你收集整理的spring event的事件驱动模型的最佳实践@EventListener的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面向切面编程AOP的最佳入门示例
- 下一篇: Tomcat入门