ApplicationContext是一個(gè)Context策略(見上下文與IoC),他除了提供最基礎(chǔ)的IoC容器功能,還提供了MessageSource實(shí)現(xiàn)的國(guó)際化、全局事件、資源層級(jí)管理等等功能。本文將詳細(xì)介紹Spring核心模塊的事件管理機(jī)制。
Spring核心模塊的事件機(jī)制和常規(guī)意義上的“事件”并沒有太大區(qū)別(例如瀏覽器上的用戶操作事件)都是通過訂閱/發(fā)布模式實(shí)現(xiàn)的。
Spring事件管理的內(nèi)容包括標(biāo)準(zhǔn)事件、自定義事件、注解標(biāo)記處理器、異步事件處理、通用實(shí)體包裝。
我們都知道在訂閱/發(fā)布模式中至少要涉及三個(gè)部分——發(fā)布者(publisher)、訂閱者(listener/subscriber)和事件(event)。針對(duì)這個(gè)模型Spring也提供了對(duì)應(yīng)的兩個(gè)接口——ApplicationEventPublisher、ApplicationListener以及一個(gè)抽象類ApplicationEvent。基本上,要使用Spring事件的功能,只要實(shí)現(xiàn)/繼承這這三個(gè)接口/抽象類并按照Spring定好的規(guī)則來使用即可。掌握這個(gè)原則那么接下來的內(nèi)容就好理解了。
標(biāo)準(zhǔn)事件
Spring為一些比較常規(guī)的事件制定了標(biāo)準(zhǔn)的事件類型和固定的發(fā)布方法,我們只需要定制好訂閱者(listener/subscriber)就可以監(jiān)聽這些事件。
先指定2個(gè)訂閱者:
packagechkui.springcore.example.javabase.event.standard;publicclassContextStartedListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ContextStartedEvent event){System.out.println("Start Listener: I am start");}}
packagechkui.springcore.example.javabase.event.standard;publicclassContextStopListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ContextStoppedEvent event){System.out.println("Stop Listener: I am stop");}}
?然后運(yùn)行使用他們:
packagechkui.springcore.example.javabase.event;@ConfigurationpublicclassEventApp{@BeanContextStopListenercontextStopListener(){returnnewContextStopListener();}@BeanContextStartedListenercontextStartedListener(){returnnewContextStartedListener();}publicstaticvoidmain(String[] args){ConfigurableApplicationContext context =newAnnotationConfigApplicationContext(EventApp.class);//發(fā)布start事件context.start();//發(fā)布stop事件context.stop();//關(guān)閉容器context.close();}}
在例子代碼中,ContextStartedListener和ContextStopListener類都實(shí)現(xiàn)了ApplicationListener接口,然后通過onApplicationEvent的方法參數(shù)來指定監(jiān)聽的事件類型。在ConfigurableApplicationContext接口中已經(jīng)為“start”和“stop”事件提供對(duì)應(yīng)的發(fā)布方法。除了StartedEvent和StoppedEvent,Spring還為其他幾項(xiàng)操作提供了標(biāo)準(zhǔn)事件:
ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被調(diào)用后觸發(fā)。事件發(fā)出的時(shí)機(jī)是所有的后置處理器已經(jīng)執(zhí)行、所有的Bean已經(jīng)被加載、所有的ApplicationContext接口方法都可以提供服務(wù)。
ContextStartedEvent:ConfigurableApplicationContext::start方法被調(diào)用后觸發(fā)。
ContextStoppedEvent:ConfigurableApplicationContext::stop方法被調(diào)用后觸發(fā)。
ContextClosedEvent:ConfigurableApplicationContext::close方法被調(diào)用后觸發(fā)。
RequestHandledEvent:這是一個(gè)用于Web容器的事件(例如啟用了DispatcherServlet),當(dāng)接收到前端請(qǐng)求時(shí)觸發(fā)。
自定義事件
除了使用標(biāo)準(zhǔn)事件,我們還可以定義各種各樣的事件。實(shí)現(xiàn)前面提到的三個(gè)接口/抽象類即可。
繼承ApplicationEvent實(shí)現(xiàn)自定義事件:
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventextendsApplicationEvent{privateString value ="This is my event!";publicMyEvent(Object source,String value){super(source);this.value = value;}publicStringgetValue(){returnvalue;}}
定義事件對(duì)應(yīng)的Listener:
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerimplementsApplicationListener{publicvoidonApplicationEvent(MyEvent event){System.out.println("MyEventListener :"+ event.getValue());}}
然后通過ApplicationEventPublisher接口發(fā)布事件:
packagechkui.springcore.example.javabase.event.custom;@ServicepublicclassMyEventServiceimplementsApplicationEventPublisherAware{privateApplicationEventPublisher publisher;@OverridepublicvoidsetApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher){publisher = applicationEventPublisher;}publicvoidpublish(String value){publisher.publishEvent(newMyEvent(this, value));}}
使用@EventListener實(shí)現(xiàn)訂閱者
在Spring Framework4.2之后可以直接使用@EventListener注解來指定事件的處理器,我們將上面的MyEventListener類進(jìn)行簡(jiǎn)單的修改:
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerAnnotation{@EventListenerpublicvoidhandleMyEvent(MyEvent event){System.out.println("MyEventListenerAnnotation :"+ event.getValue());? ? }}
使用@EventListener可以不必實(shí)現(xiàn)ApplicationListener,只要添加為一個(gè)Bean即可。Spring會(huì)根據(jù)方法的參數(shù)類型訂閱對(duì)應(yīng)的事件。
我們也可以使用注解指定綁定的事件:
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerAnnotation{@EventListener(ContextStartedEvent.class})publicvoidhandleMyEvent(){//----}}
?還可以指定一次性監(jiān)聽多個(gè)事件:
packagechkui.springcore.example.javabase.event.standard;publicclassMultiEventListener{@EventListener({ContextStartedEvent.class, ContextStoppedEvent.class})@Order(2)voidcontenxtStandadrEventHandle(ApplicationContextEvent event){System.out.println("MultiEventListener:"+ event.getClass().getSimpleName());}}
注意上面代碼中的@Order注解,同一個(gè)事件可以被多個(gè)訂閱者訂閱。在多個(gè)定于者存在的情況下可以使用@Order注解來指定他們的執(zhí)行順序,數(shù)值越小越優(yōu)先執(zhí)行。
EL表達(dá)式設(shè)定事件監(jiān)聽的條件
通過注解還可以使用Spring的EL表達(dá)式來更細(xì)粒度的控制監(jiān)聽的范圍,比如下面的例子僅僅當(dāng)事件的實(shí)例中MyEvent.value == "Second publish!"才觸發(fā)處理器:
事件:
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventextendsApplicationEvent{privateString value ="This is my event!";publicMyEvent(Object source,String value){super(source);this.value = value;}publicStringgetValue(){returnvalue;}}
通過EL表達(dá)式指定監(jiān)聽的數(shù)據(jù):
packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerElSp{@EventListener(condition="#p0.value == 'Second publish!'")publicvoidhandleMyEvent(MyEvent event){System.out.println("MyEventListenerElSp :"+ event.getValue());? ? }}
這樣,當(dāng)這個(gè)事件被發(fā)布,而且其中的成員變量value值等于"Second publish!",對(duì)應(yīng)的MyEventListenerElSp::handleMyEvent方法才會(huì)被觸發(fā)。EL表達(dá)式還可以使用通配符等等豐富的表現(xiàn)形式來設(shè)定過濾規(guī)則,后續(xù)介紹EL表達(dá)式時(shí)會(huì)詳細(xì)說明。
通用包裝事件
Spring還提供一個(gè)方式使用事件來包裝實(shí)體類,起到傳遞數(shù)據(jù)但是不用重復(fù)定義多個(gè)事件的作用。看下面的例子。
我們先定義2個(gè)實(shí)體類:
packagechkui.springcore.example.javabase.event.generics;classPES{publicStringtoString(){return"PRO EVOLUTION SOCCER";}}classWOW{publicStringtoString(){return"World Of Warcraft";}}
定義可以用于包裝任何實(shí)體的事件,需要實(shí)現(xiàn)ResolvableTypeProvider接口:
packagechkui.springcore.example.javabase.event.generics;publicclassEntityWrapperEventextendsApplicationEventimplementsResolvableTypeProvider{publicEntityWrapperEvent(T entity){super(entity);}publicResolvableTypegetResolvableType(){returnResolvableType.forClassWithGenerics(getClass(),? ? ? ? ? ? ? ? ResolvableType.forInstance(getSource()));}}
訂閱者可以根據(jù)被包裹的entity的不同來監(jiān)聽不同的事件:
packagechkui.springcore.example.javabase.event.generics;publicclassEntiryWrapperEventListener{@EventListenerpublicvoidhandlePES(EntityWrapperEvent<PES> evnet){System.out.println("EntiryWrapper PES: "+? evnet);}@EventListenerpublicvoidhandleWOW(EntityWrapperEvent<WOW> evnet){System.out.println("EntiryWrapper WOW: "+? evnet);}}
上面的代碼起到最用的主要是ResolvableType.forInstance(getSource())這一行代碼,getSource()方法來自于EventObject類,它實(shí)際上就是返回構(gòu)造方法中super(entity)設(shè)定的entity實(shí)例。
寫在最后的
訂閱/發(fā)布模式是幾乎所有軟件程序都會(huì)觸及的問題,無論是瀏覽器前端、還是古老的winMFC程序。而在后端應(yīng)用中,對(duì)于使用過MQ工具或者Vertx這種純事件輪詢驅(qū)動(dòng)的框架碼友,應(yīng)該已經(jīng)請(qǐng)清楚這種訂閱/發(fā)布+事件驅(qū)動(dòng)的價(jià)值。它除了能夠降低各層的耦合度,還能更有效的利用多線程而大大的提執(zhí)行效率(當(dāng)然對(duì)開發(fā)人員的要求也會(huì)高不少)。
對(duì)于Spring核心框架來說,事件的訂閱/發(fā)布只是IoC容器的一個(gè)附屬功能,Spring的核心價(jià)值并不在這個(gè)地方。Spring的訂閱發(fā)布功能在實(shí)現(xiàn)層面至少現(xiàn)在并沒有使用EventLoop的方式,還是類與類之間的直接調(diào)用,所以在性能上是完全無法向Vertx看齊的。不過Spring事件的機(jī)制還是能夠起到事件驅(qū)動(dòng)的效果,可以用來全局控制一些狀態(tài)。如果選用Spring生態(tài)中的框架(boot等)作為我們的底層框架,現(xiàn)階段還是應(yīng)該使用IoC的方式來組合功能,而事件的訂閱/發(fā)布僅僅用于輔助。