ApplicationContext是一個Context策略(見上下文與IoC),他除了提供最基礎的IoC容器功能,還提供了MessageSource實現的國際化、全局事件、資源層級管理等等功能。本文將詳細介紹Spring核心模塊的事件管理機制。前端
Spring核心模塊的事件機制和常規意義上的「事件」並無太大區別(例如瀏覽器上的用戶操做事件)都是經過訂閱/發佈模式實現的。java
Spring事件管理的內容包括標準事件、自定義事件、註解標記處理器、異步事件處理、通用實體包裝。下面將經過幾個例子來講明這些內容,可執行代碼請到本人的gitee庫下載,本文的內容在包chkui.springcore.example.javabase.event中。git
咱們都知道在訂閱/發佈模式中至少要涉及三個部分——發佈者(publisher)、訂閱者(listener/subscriber)和事件(event)。針對這個模型Spring也提供了對應的兩個接口——ApplicationEventPublisher、ApplicationListener以及一個抽象類ApplicationEvent。基本上,要使用Spring事件的功能,只要實現/繼承這這三個接口/抽象類並按照Spring定好的規則來使用便可。掌握這個原則那麼接下來的內容就好理解了。spring
Spring爲一些比較常規的事件制定了標準的事件類型和固定的發佈方法,咱們只須要定製好訂閱者(listener/subscriber)就能夠監聽這些事件。後端
先指定2個訂閱者:瀏覽器
package chkui.springcore.example.javabase.event.standard; public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> { @Override public void onApplicationEvent(ContextStartedEvent event) { System.out.println("Start Listener: I am start"); } }
package chkui.springcore.example.javabase.event.standard; public class ContextStopListener implements ApplicationListener<ContextStoppedEvent> { @Override public void onApplicationEvent(ContextStoppedEvent event) { System.out.println("Stop Listener: I am stop"); } }
而後運行使用他們:多線程
package chkui.springcore.example.javabase.event; @Configuration public class EventApp { @Bean ContextStopListener contextStopListener() { return new ContextStopListener(); } @Bean ContextStartedListener contextStartedListener() { return new ContextStartedListener(); } public static void main(String[] args) { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(EventApp.class); //發佈start事件 context.start(); //發佈stop事件 context.stop(); //關閉容器 context.close(); } }
在例子代碼中,ContextStartedListener和ContextStopListener類都實現了ApplicationListener接口,而後經過onApplicationEvent的方法參數來指定監聽的事件類型。在ConfigurableApplicationContext接口中已經爲「start」和「stop」事件提供對應的發佈方法。除了StartedEvent和StoppedEvent,Spring還爲其餘幾項操做提供了標準事件:app
除了使用標準事件,咱們還能夠定義各類各樣的事件。實現前面提到的三個接口/抽象類便可。框架
繼承ApplicationEvent實現自定義事件:異步
package chkui.springcore.example.javabase.event.custom; public class MyEvent extends ApplicationEvent { private String value = "This is my event!"; public MyEvent(Object source,String value) { super(source); this.value = value; } public String getValue() { return value; } }
定義事件對應的Listener:
package chkui.springcore.example.javabase.event.custom; public class MyEventListener implements ApplicationListener<MyEvent> { public void onApplicationEvent(MyEvent event) { System.out.println("MyEventListener :" + event.getValue()); } }
而後經過ApplicationEventPublisher接口發佈事件:
package chkui.springcore.example.javabase.event.custom; @Service public class MyEventService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { publisher = applicationEventPublisher; } public void publish(String value) { publisher.publishEvent(new MyEvent(this, value)); } }
在Spring Framework4.2以後能夠直接使用@EventListener註解來指定事件的處理器,咱們將上面的MyEventListener類進行簡單的修改:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerAnnotation{ @EventListener public void handleMyEvent(MyEvent event) { System.out.println("MyEventListenerAnnotation :" + event.getValue()); } }
使用@EventListener能夠沒必要實現ApplicationListener,只要添加爲一個Bean便可。Spring會根據方法的參數類型訂閱對應的事件。
咱們也可使用註解指定綁定的事件:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerAnnotation{ @EventListener(ContextStartedEvent.class}) public void handleMyEvent() { //---- } }
還能夠指定一次性監聽多個事件:
package chkui.springcore.example.javabase.event.standard; public class MultiEventListener { @EventListener({ContextStartedEvent.class, ContextStoppedEvent.class}) @Order(2) void contenxtStandadrEventHandle(ApplicationContextEvent event) { System.out.println("MultiEventListener:" + event.getClass().getSimpleName()); } }
注意上面代碼中的@Order註解,同一個事件能夠被多個訂閱者訂閱。在多個定於者存在的狀況下可使用@Order註解來指定他們的執行順序,數值越小越優先執行。
經過註解還可使用Spring的EL表達式來更細粒度的控制監聽的範圍,好比下面的例子僅僅當事件的實例中MyEvent.value == "Second publish!"才觸發處理器:
事件:
package chkui.springcore.example.javabase.event.custom; public class MyEvent extends ApplicationEvent { private String value = "This is my event!"; public MyEvent(Object source,String value) { super(source); this.value = value; } public String getValue() { return value; } }
經過EL表達式指定監聽的數據:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerElSp { @EventListener(condition="#p0.value == 'Second publish!'") public void handleMyEvent(MyEvent event) { System.out.println("MyEventListenerElSp :" + event.getValue()); } }
這樣,當這個事件被髮布,並且其中的成員變量value值等於"Second publish!",對應的MyEventListenerElSp::handleMyEvent方法纔會被觸發。EL表達式還可使用通配符等等豐富的表現形式來設定過濾規則,後續介紹EL表達式時會詳細說明。
Spring還提供一個方式使用事件來包裝實體類,起到傳遞數據可是不用重複定義多個事件的做用。看下面的例子。
咱們先定義2個實體類:
package chkui.springcore.example.javabase.event.generics; class PES { public String toString() { return "PRO EVOLUTION SOCCER"; } } class WOW { public String toString() { return "World Of Warcraft"; } }
定義能夠用於包裝任何實體的事件,須要實現ResolvableTypeProvider接口:
package chkui.springcore.example.javabase.event.generics; public class EntityWrapperEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityWrapperEvent(T entity) { super(entity); } public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); } }
訂閱者能夠根據被包裹的entity的不一樣來監聽不一樣的事件:
package chkui.springcore.example.javabase.event.generics; public class EntiryWrapperEventListener { @EventListener public void handlePES(EntityWrapperEvent<PES> evnet) { System.out.println("EntiryWrapper PES: " + evnet); } @EventListener public void handleWOW(EntityWrapperEvent<WOW> evnet) { System.out.println("EntiryWrapper WOW: " + evnet); } }
上面的代碼起到最用的主要是ResolvableType.forInstance(getSource())這一行代碼,getSource()方法來自於EventObject類,它實際上就是返回構造方法中super(entity)設定的entity實例。
訂閱/發佈模式是幾乎全部軟件程序都會觸及的問題,不管是瀏覽器前端、仍是古老的winMFC程序。而在後端應用中,對於使用過MQ工具或者Vertx這種純事件輪詢驅動的框架碼友,應該已經請清楚這種訂閱/發佈+事件驅動的價值。它除了可以下降各層的耦合度,還能更有效的利用多線程而大大的提執行效率(固然對開發人員的要求也會高很多)。
對於Spring核心框架來講,事件的訂閱/發佈只是IoC容器的一個附屬功能,Spring的核心價值並不在這個地方。Spring的訂閱發佈功能在實現層面至少如今並無使用EventLoop的方式,仍是類與類之間的直接調用,因此在性能上是徹底沒法向Vertx看齊的。不過Spring事件的機制仍是可以起到事件驅動的效果,能夠用來全局控制一些狀態。若是選用Spring生態中的框架(boot等)做爲咱們的底層框架,現階段仍是應該使用IoC的方式來組合功能,而事件的訂閱/發佈僅僅用於輔助。