Spring核心——全局事件管理

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();
	}
}

在例子代碼中,ContextStartedListenerContextStopListener類都實現了ApplicationListener接口,而後經過onApplicationEvent的方法參數來指定監聽的事件類型。在ConfigurableApplicationContext接口中已經爲「start」和「stop」事件提供對應的發佈方法。除了StartedEventStoppedEventSpring還爲其餘幾項操做提供了標準事件:app

  1. ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被調用後觸發。事件發出的時機是全部的後置處理器已經執行、全部的Bean已經被加載、全部的ApplicationContext接口方法均可以提供服務。
  2. ContextStartedEvent:ConfigurableApplicationContext::start方法被調用後觸發。
  3. ContextStoppedEvent:ConfigurableApplicationContext::stop方法被調用後觸發。
  4. ContextClosedEvent:ConfigurableApplicationContext::close方法被調用後觸發。
  5. RequestHandledEvent:這是一個用於Web容器的事件(例如啓用了DispatcherServlet),當接收到前端請求時觸發。

自定義事件

除了使用標準事件,咱們還能夠定義各類各樣的事件。實現前面提到的三個接口/抽象類便可。框架

繼承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));
	}
}

使用@EventListener實現訂閱者

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註解來指定他們的執行順序,數值越小越優先執行。

EL表達式設定事件監聽的條件

經過註解還可使用SpringEL表達式來更細粒度的控制監聽的範圍,好比下面的例子僅僅當事件的實例中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的方式來組合功能,而事件的訂閱/發佈僅僅用於輔助。

相關文章
相關標籤/搜索