今天來簡單地聊聊事件驅動,其實寫這篇文章挺令我挺苦惱的,因為事件驅動這個名詞,我沒有找到很好的定性解釋,擔心自己的表述有誤,而說到事件驅動可能立刻聯想到如此眾多的概念:觀察者模式,發布訂閱模式,消息隊列MQ,消息驅動,事件,EventSourcing…為了不產生歧義,筆者把自己所了解的這些模棱兩可的概念都列了出來,再開始今天的分享。
- 在 設計模式 中,觀察者模式可以算得上是一個非常經典的行為型設計模式,貓叫了,主人醒了,老鼠跑了,這一經典的例子,是事件驅動模型在設計層面的體現。
- 另一模式,發布訂閱模式往往被人們等同於觀察者模式,但我的理解是兩者唯一區別,是發布訂閱模式需要有一個調度中心,而觀察者模式不需要,例如觀察者的列表可以直接由被觀察者維護。不過兩者即使被混用,互相替代,通常不影響表達。
- MQ,中間件級別的消息隊列(e.g. ActiveMQ,RabbitMQ),可以認為是發布訂閱模式的一個具體體現。事件驅動->發布訂閱->MQ,從抽象到具體。
- Java和spring中都擁有Event的抽象,分別代表了語言級別和三方框架級別對事件的支持。
- EventSourcing這個概念就要關聯到領域驅動設計,DDD對事件驅動也是非常地青睞,領域對象的狀態完全是由事件驅動來控制,由其衍生出了CQRS架構,具體實現框架有AxonFramework。
- Nginx可以作為高性能的應用服務器(e.g. openResty),以及Nodejs事件驅動的特性,這些也是都是事件驅動的體現。
本文涵蓋的內容主要是前面4點。
Spring對Event的支持
Spring的文檔對Event的支持翻譯之後描述如下:
ApplicationContext通過ApplicationEvent類和ApplicationListener接口進行事件處理。 如果將實現ApplicationListener接口的bean註入到上下文中,則每次使用ApplicationContext發布ApplicationEvent時,都會通知該bean。 本質上,這是標準的觀察者設計模式。
而在spring4.2之後,提供了註解式的支持,我們可以使用任意的java對象配合註解達到同樣的效果,首先來看看不適用註解如何在Spring中使用事件驅動機制。
定義業務需求:用戶註冊後,系統需要給用戶發送郵件告知用戶註冊成功,需要給用戶初始化積分;隱含的設計需求,用戶註冊後,後續需求可能會添加其他操作,如再發送一條短信等等,希望程序具有擴展性,以及符合開閉原則。
如果不使用事件驅動,代碼可能會像這樣子:
public class UserService { @Autowired EmailService emailService; @Autowired ScoreService scoreService; @Autowired OtherService otherService; public void register(String name) { system.out.println("用戶:" + name + " 已註冊!"); emailService.sendEmail(name); scoreService.initScore(name); otherService.execute(name); } }
要說有什麽毛病,其實也不算有,因為可能大多數人在開發中都會這麽寫,喜歡寫同步代碼。但這麽寫,實際上並不是特別的符合隱含的設計需求,假設增加更多的註冊項service,我們需要修改register的方法,並且讓UserService註入對應的Service。而實際上,register並不關心這些“額外”的操作,如何將這些多余的代碼抽取出去呢?便可以使用Spring提供的Event機制。
定義用戶註冊事件
public class UserRegisterEvent extends ApplicationEvent{ public UserRegisterEvent(String name) { //name即source super(name); } }
ApplicationEvent是由Spring提供的所有Event類的基類,為了簡單起見,註冊事件只傳遞了name(可以復雜的對象,但註意要了解清楚序列化機制)。
定義用戶註冊服務(事件發布者)
@Service // <1> public class UserService implements ApplicationEventPublisherAware { // <2> public void register(String name) { System.out.println("用戶:" + name + " 已註冊!"); applicationEventPublisher.publishEvent(new UserRegisterEvent(name));// <3> } private ApplicationEventPublisher applicationEventPublisher; // <2> @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { // <2> this.applicationEventPublisher = applicationEventPublisher; } }
<1> 服務必須交給Spring容器托管
<2> ApplicationEventPublisherAware是由Spring提供的用於為Service註入ApplicationEventPublisher事件發布器的接口,使用這個接口,我們自己的Service就擁有了發布事件的能力。
<3> 用戶註冊後,不再是顯示調用其他的業務Service,而是發布一個用戶註冊事件。
定義郵件服務,積分服務,其他服務(事件訂閱者)
@Service // <1> public class EmailService implements ApplicationListener<UserRegisterEvent> { // <2> @Override public void onApplicationEvent(UserRegisterEvent userRegisterEvent) { System.out.println("郵件服務接到通知,給 " + userRegisterEvent.getSource() + " 發送郵件...");// <3> } }
<1> 事件訂閱者的服務同樣需要托管於Spring容器
<2> ApplicationListener<E extends ApplicationEvent>接口是由Spring提供的事件訂閱者必須實現的接口,我們一般把該Service關心的事件類型作為泛型傳入。
<3> 處理事件,通過event.getSource()即可拿到事件的具體內容,在本例中便是用戶的姓名。
其他兩個Service,也同樣編寫,實際的業務操作僅僅是打印一句內容即可,篇幅限制,這裏省略。
編寫啟動類
@SpringBootApplication @RestController public class EventDemoApp { public static void main(String[] args) { SpringApplication.run(EventDemoApp.class, args); } @Autowired UserService userService; @RequestMapping("/register") public String register(){ userService.register("kirito"); return "success"; } }
當我們調用userService.register(“kirito”);方法時,控制臺打印信息如下:
他們的順序是無序的,如果需要控制順序,需要重寫order接口,這點不做介紹。其次,我們完成了用戶註冊和其他服務的解耦,這也是事件驅動的最大特性之一,如果需要在用戶註冊時完成其他操作,只需要再添加相應的事件訂閱者即可。
Spring 對Event的註解支持
上述的幾個接口已經非常清爽了,如果習慣使用註解,Spring也提供了,不再需要顯示實現
註解式的事件發布者
@Service public class UserService { public void register(String name) { System.out.println("用戶:" + name + " 已註冊!"); applicationEventPublisher.publishEvent(new UserRegisterEvent(name)); } @Autowired private ApplicationEventPublisher applicationEventPublisher; }
Spring4.2之後,ApplicationEventPublisher自動被註入到容器中,采用Autowired即可獲取。
註解式的事件訂閱者
@Service public class EmailService { @EventListener public void listenUserRegisterEvent(UserRegisterEvent userRegisterEvent) { System.out.println("郵件服務接到通知,給 " + userRegisterEvent.getSource() + " 發送郵件..."); } }
@EventListener註解完成了ApplicationListener<E extends ApplicationEvent>接口的使命。
更多的特性可以參考SpringFramework的文檔。
Spring中事件的應用
在以往閱讀Spring源碼的經驗中,接觸了不少使用事件的地方,大概列了以下幾個,加深以下印象:
- Spring Security中使用AuthenticationEventPublisher處理用戶認證成功,認證失敗的消息處理。
public interface AuthenticationEventPublisher { void publishAuthenticationSuccess(Authentication authentication); void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); }
- Hibernate中持久化對象屬性的修改是如何被框架得知的?正是采用了一系列持久化相關的事件,如DefaultSaveEventListener,DefaultupdateEventListener,事件非常多,有興趣可以去org.hibernate.event包下查看。
- Spring Cloud Zuul中刷新路由信息使用到的ZuulRefreshListener
private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { ... public void onApplicationEvent(ApplicationEvent event) { if(!(event instanceof ContextRefreshedEvent) && !(event instanceof RefreshScopeRefreshedEvent) && !(event instanceof RoutesRefreshedEvent)) { if(event instanceof HeartbeatEvent && this.heartbeatMonitor.update(((HeartbeatEvent)event).getValue())) { this.zuulHandlerMapping.setDirty(true); } } else { this.zuulHandlerMapping.setDirty(true); } } }
- Spring容器生命周期相關的一些默認Event
ContextRefreshedEvent,ContextStartedEvent,ContextStoppedEvent,ContextClosedEvent,RequestHandledEvent
。。。其實吧,非常多。。。
總結
本文暫時只介紹了Spring中的一些簡單的事件驅動機制,相信如果之後再看到Event,Publisher,EventListener一類的單詞後綴時,也能立刻和事件機制聯系上了。再閱讀Spring源碼時,如果發現出現了某個Event,但由於不是同步調用,所以很容易被忽視,我一般習慣下意識的去尋找有沒有提供默認的Listener,這樣不至於漏掉一些“隱藏”的特性。下一篇文章打算聊一聊分布式場景下,事件驅動使用的註意點。
公眾號剛剛創立,如果覺得文章不錯,希望能分享到您的朋友圈,如果對文章有什麽想法和建議,可以與我溝通。
Tags: 驅動 事件 觀察者 模式 訂閱 發布
文章來源: