Spring5源碼解析-Spring框架中的事件和監(jiān)聽器

事件和平時(shí)所用的回調(diào)思想在與GUI(JavaScript,Swing)相關(guān)的技術(shù)中非常流行颅围。而在Web應(yīng)用程序的服務(wù)器端宪祥,我們很少去直接使用。但這并不意味著我們無法在服務(wù)端去實(shí)現(xiàn)一個(gè)面向事件的體系結(jié)構(gòu)蛤虐。

在本文中,我們將重點(diǎn)介紹Spring框架中的事件處理肝陪。首先,會(huì)先介紹下事件驅(qū)動(dòng)編程這個(gè)概念刑顺。接著氯窍,我們會(huì)將精力放在專門用于Spring框架中的事件處理之上。然后我們會(huì)看到實(shí)現(xiàn)事件調(diào)度和監(jiān)聽的主要方法蹲堂。最后狼讨,我們將在Spring應(yīng)用程序中展示如何使用基本的監(jiān)聽器。

事件驅(qū)動(dòng)編程

在開始討論事件驅(qū)動(dòng)編程的編程方面之前柒竞,先來說一個(gè)場(chǎng)景政供,用來幫助大家更好地理解 event-driven 這個(gè)概念。在一個(gè)地方只有兩個(gè)賣衣服的商店A和B.在A店中朽基,我們的消費(fèi)者需要一個(gè)一個(gè)的接受服務(wù)布隔,即,同一時(shí)間只有一個(gè)客戶可以購物稼虎。在B店里衅檀,可以允許幾個(gè)客戶同時(shí)進(jìn)行購物,當(dāng)有客戶需要賣家的幫助時(shí)霎俩,他需要舉起他的右手示意一下哀军。賣家看到后會(huì)來找他沉眶,幫助其做出更好的選擇。關(guān)于事件驅(qū)動(dòng)( event-driven )編程這個(gè)概念通過上述場(chǎng)景描述總結(jié)后就是:通過做一些動(dòng)作來作為對(duì)一些行為的回應(yīng)杉适。

如上所見谎倔,事件驅(qū)動(dòng)的編程(也稱為基于事件的編程)是基于對(duì)接收到的信號(hào)的反應(yīng)的編程形式。這些信號(hào)必須以某種形式來傳輸信息猿推。舉一個(gè)簡(jiǎn)單例子: 點(diǎn)擊按鈕 传藏。我們將這些信號(hào)稱為事件。這些事件可以通過用戶操作(鼠標(biāo)點(diǎn)擊彤守,觸摸)或程序條件執(zhí)行觸發(fā)(例如:一個(gè)元素的加載結(jié)束可以啟動(dòng)另一個(gè)操作)來產(chǎn)生毯侦。

為了更好地了解,請(qǐng)看以下示例具垫,模仿用戶在GUI界面中的操作:

publicclassEventBasedTest{

@Test

publicvoidtest(){

Mouse mouse =newMouse();

mouse.addListener(newMouseListener() {

@Override

publicvoidonClick(Mouse mouse){

System.out.println("Listener#1 called");

? ? ? ? mouse.addListenerCallback();

? ? ? }

? ? });

mouse.addListener(newMouseListener() {

@Override

publicvoidonClick(Mouse mouse){

System.out.println("Listener#2 called");

? ? ? ? mouse.addListenerCallback();

? ? ? }

? ? });

? ? mouse.click();

assertTrue("2 listeners should be invoked but only "+mouse.getListenerCallbacks()+" were", mouse.getListenerCallbacks() ==2);

? }

}

classMouse{

privateList listeners =newArrayList();

privateintlistenerCallbacks =0;


publicvoidaddListenerCallback(){

? ? listenerCallbacks++;

? }


publicintgetListenerCallbacks(){

returnlistenerCallbacks;

? }


publicvoidaddListener(MouseListener listener){

? ? listeners.add(listener);

? }


publicvoidclick(){

System.out.println("Clicked !");

for(MouseListener listener : listeners) {

listener.onClick(this);

? ? }

? }

}

interfaceMouseListener{

publicvoidonClick(Mouse source);

}

打印輸出如下所示:

Clicked !

Listener#1 called

Listener#2 called

Spring中的Events

Spring基于實(shí)現(xiàn)org.springframework.context.ApplicationListener接口的bean來進(jìn)行事件處理侈离。這個(gè)接口中只有一個(gè)方法,onApplicationEvent用來當(dāng)一個(gè)事件發(fā)送過來時(shí)這個(gè)方法來觸發(fā)相應(yīng)的處理筝蚕。該接口可以通過指定需要接收的事件來實(shí)現(xiàn)(不懂看源碼咯卦碾,源碼里方法接收一個(gè) event 作為參數(shù))。由此起宽,Spring會(huì)自動(dòng)過濾篩選可以用來接收給定事件的監(jiān)聽器( listeners )洲胖。

/**

* Interface to be implemented by application event listeners.

* Based on the standard {@codejava.util.EventListener} interface

* for the Observer design pattern.

*

*

As of Spring 3.0, an ApplicationListener can generically declare the event type

* that it is interested in. When registered with a Spring ApplicationContext, events

* will be filtered accordingly, with the listener getting invoked for matching event

* objects only.

*

*@authorRod Johnson

*@authorJuergen Hoeller

*@param the specific ApplicationEvent subclass to listen to

*@seeorg.springframework.context.event.ApplicationEventMulticaster

*/

@FunctionalInterface

publicinterfaceApplicationListenerextendsEventListener{

/**

* Handle an application event.

*@paramevent the event to respond to

*/

voidonApplicationEvent(E event);

}

事件通過org.springframework.context.ApplicationEvent實(shí)例來表示。這個(gè)抽象類繼承擴(kuò)展了java.util.EventObject坯沪,可以使用EventObject中的getSource方法绿映,我們可以很容易地獲得所發(fā)生的給定事件的對(duì)象。這里腐晾,事件存在兩種類型:

與應(yīng)用程序上下文相關(guān)聯(lián):所有這種類型的事件都繼承自 org.springframework.context.event.ApplicationContextEvent 類叉弦。它們應(yīng)用于由org.springframework.context.ApplicationContext引發(fā)的事件(其構(gòu)造函數(shù)傳入的是 ApplicationContext 類型的參數(shù))。這樣藻糖,我們就可以直接通過應(yīng)用程序上下文的生命周期來得到所發(fā)生的事件: ContextStartedEvent 在上下文啟動(dòng)時(shí)被啟動(dòng)淹冰,當(dāng)它停止時(shí)啟動(dòng) ContextStoppedEvent 栈幸,當(dāng)上下文被刷新時(shí)產(chǎn)生 ContextRefreshedEvent 烤送,最后在上下文關(guān)閉時(shí)產(chǎn)生 ContextClosedEvent 。

/**

* Base class for events raised for an {@codeApplicationContext}.

*

*@authorJuergen Hoeller

*@since2.5

*/

@SuppressWarnings("serial")

publicabstractclassApplicationContextEventextendsApplicationEvent{

/**

* Create a new ContextStartedEvent.

*@paramsource the {@codeApplicationContext} that the event is raised for

* (must not be {@codenull})

*/

publicApplicationContextEvent(ApplicationContext source){

super(source);

}

/**

* Get the {@codeApplicationContext} that the event was raised for.

*/

publicfinalApplicationContextgetApplicationContext(){

return(ApplicationContext) getSource();

}

}

/**

* Event raised when an {@codeApplicationContext} gets started.

*

*@authorMark Fisher

*@authorJuergen Hoeller

*@since2.5

*@seeContextStoppedEvent

*/

@SuppressWarnings("serial")

publicclassContextStartedEventextendsApplicationContextEvent{

/**

* Create a new ContextStartedEvent.

*@paramsource the {@codeApplicationContext} that has been started

* (must not be {@codenull})

*/

publicContextStartedEvent(ApplicationContext source){

super(source);

}

}

/**

* Event raised when an {@codeApplicationContext} gets stopped.

*

*@authorMark Fisher

*@authorJuergen Hoeller

*@since2.5

*@seeContextStartedEvent

*/

@SuppressWarnings("serial")

publicclassContextStoppedEventextendsApplicationContextEvent{

/**

* Create a new ContextStoppedEvent.

*@paramsource the {@codeApplicationContext} that has been stopped

* (must not be {@codenull})

*/

publicContextStoppedEvent(ApplicationContext source){

super(source);

}

}

/**

* Event raised when an {@codeApplicationContext} gets initialized or refreshed.

*

*@authorJuergen Hoeller

*@since04.03.2003

*@seeContextClosedEvent

*/

@SuppressWarnings("serial")

publicclassContextRefreshedEventextendsApplicationContextEvent{

/**

* Create a new ContextRefreshedEvent.

*@paramsource the {@codeApplicationContext} that has been initialized

* or refreshed (must not be {@codenull})

*/

publicContextRefreshedEvent(ApplicationContext source){

super(source);

}

}

/**

* Event raised when an {@codeApplicationContext} gets closed.

*

*@authorJuergen Hoeller

*@since12.08.2003

*@seeContextRefreshedEvent

*/

@SuppressWarnings("serial")

publicclassContextClosedEventextendsApplicationContextEvent{

/**

* Creates a new ContextClosedEvent.

*@paramsource the {@codeApplicationContext} that has been closed

* (must not be {@codenull})

*/

publicContextClosedEvent(ApplicationContext source){

super(source);

}

}

與request 請(qǐng)求相關(guān)聯(lián):由 org.springframework.web.context.support.RequestHandledEvent 實(shí)例來表示畔濒,當(dāng)在ApplicationContext中處理請(qǐng)求時(shí)洋满,它們被引發(fā)晶乔。

Spring如何將事件分配給專門的監(jiān)聽器?這個(gè)過程由事件廣播器( event multicaster )來實(shí)現(xiàn)芦岂,由 org.springframework.context.event.ApplicationEventMulticaster 接口的實(shí)現(xiàn)表示瘪弓。此接口定義了3種方法,用于:

添加新的監(jiān)聽器:定義了兩種方法來添加新的監(jiān)聽器: addApplicationListener(ApplicationListener listener) 和 addApplicationListenerBean(String listenerBeanName) 禽最。當(dāng)監(jiān)聽器對(duì)象已知時(shí)腺怯,可以應(yīng)用第一個(gè)袱饭。如果使用第二個(gè),我們需要將bean name 得到listener對(duì)象( 依賴查找DL )呛占,然后再將其添加到 listener 列表中虑乖。在這里順便給大家推薦一個(gè)架構(gòu)交流群:617434785,里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring晾虑,MyBatis疹味,Netty源碼分析,高并發(fā)帜篇、高性能糙捺、分布式、微服務(wù)架構(gòu)的原理笙隙,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識(shí)體系洪灯。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源。相信對(duì)于已經(jīng)工作和遇到技術(shù)瓶頸的碼友竟痰,在這個(gè)群里會(huì)有你需要的內(nèi)容签钩。

刪除監(jiān)聽器:添加方法一樣,我們可以通過傳遞對(duì)象來刪除一個(gè)監(jiān)聽器( removeApplicationListener(ApplicationListener listener) 或通過傳遞bean名稱( removeApplicationListenerBean(String listenerBeanName)) 坏快, 第三種方法铅檩,removeAllListeners()用來刪除所有已注冊(cè)的監(jiān)聽器

將事件發(fā)送到已注冊(cè)的監(jiān)聽器:由multicastEvent(ApplicationEvent event)源碼注釋可知,它用來向所有注冊(cè)的監(jiān)聽器發(fā)送事件莽鸿。實(shí)現(xiàn)可以從 org.springframework.context.event.SimpleApplicationEventMulticaster中 找到昧旨,如下所示:

@Override

publicvoidmulticastEvent(ApplicationEvent event){

multicastEvent(event, resolveDefaultEventType(event));

}

@Override

publicvoidmulticastEvent(finalApplicationEvent event, @Nullable ResolvableType eventType){

ResolvableType type = (eventType !=null? eventType : resolveDefaultEventType(event));

for(finalApplicationListener listener : getApplicationListeners(event, type)) {

Executor executor = getTaskExecutor();

if(executor !=null) {

executor.execute(() -> invokeListener(listener, event));

}

else{

invokeListener(listener, event);

}

}

}

privateResolvableTyperesolveDefaultEventType(ApplicationEvent event){

returnResolvableType.forInstance(event);

}

/**

* Invoke the given listener with the given event.

*@paramlistener the ApplicationListener to invoke

*@paramevent the current event to propagate

*@since4.1

*/

@SuppressWarnings({"unchecked","rawtypes"})

protectedvoidinvokeListener(ApplicationListener listener, ApplicationEvent event){

ErrorHandler errorHandler = getErrorHandler();

if(errorHandler !=null) {

try{

listener.onApplicationEvent(event);

}

catch(Throwable err) {

errorHandler.handleError(err);

}

}

else{

try{

listener.onApplicationEvent(event);

}

catch(ClassCastException ex) {

String msg = ex.getMessage();

if(msg ==null|| msg.startsWith(event.getClass().getName())) {

// Possibly a lambda-defined listener which we could not resolve the generic event type for

Log logger = LogFactory.getLog(getClass());

if(logger.isDebugEnabled()) {

logger.debug("Non-matching event type for listener: "+ listener, ex);

}

}

else{

throwex;

}

}

}

}

我們來看看 event multicaster 在應(yīng)用程序上下文中所在的位置。在 AbstractApplicationContext 中定義的一些方法可以看到其中包含調(diào)用public void publishEvent方法富拗。通過這種方法的注釋可知臼予,它負(fù)責(zé)向所有監(jiān)聽器發(fā)送給定的事件:

/**

* Publish the given event to all listeners.

*

Note: Listeners get initialized after the MessageSource, to be able

* to access it within listener implementations. Thus, MessageSource

* implementations cannot publish events.

*@paramevent the event to publish (may be application-specific or a

* standard framework event)

*/

@Override

publicvoidpublishEvent(ApplicationEvent event){

publishEvent(event,null);

}

/**

* Publish the given event to all listeners.

*

Note: Listeners get initialized after the MessageSource, to be able

* to access it within listener implementations. Thus, MessageSource

* implementations cannot publish events.

*@paramevent the event to publish (may be an {@linkApplicationEvent}

* or a payload object to be turned into a {@linkPayloadApplicationEvent})

*/

@Override

publicvoidpublishEvent(Object event){

publishEvent(event,null);

}

/**

* Publish the given event to all listeners.

*@paramevent the event to publish (may be an {@linkApplicationEvent}

* or a payload object to be turned into a {@linkPayloadApplicationEvent})

*@parameventType the resolved event type, if known

*@since4.2

*/

protectedvoidpublishEvent(Object event, @Nullable ResolvableType eventType){

Assert.notNull(event,"Event must not be null");

if(logger.isTraceEnabled()) {

logger.trace("Publishing event in "+ getDisplayName() +": "+ event);

}

// Decorate event as an ApplicationEvent if necessary

ApplicationEvent applicationEvent;

if(eventinstanceofApplicationEvent) {

applicationEvent = (ApplicationEvent) event;

}

else{

applicationEvent =newPayloadApplicationEvent<>(this, event);

if(eventType ==null) {

eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();

}

}

// Multicast right now if possible - or lazily once the multicaster is initialized

if(this.earlyApplicationEvents !=null) {

this.earlyApplicationEvents.add(applicationEvent);

}

else{

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

}

// Publish event via parent context as well...

if(this.parent !=null) {

if(this.parentinstanceofAbstractApplicationContext) {

((AbstractApplicationContext)this.parent).publishEvent(event, eventType);

}

else{

this.parent.publishEvent(event);

}

}

}

該方法由以下方法調(diào)用:?jiǎn)?dòng)上下文(啟動(dòng)后發(fā)布 ContextStartedEvent ),停止上下文(停止后發(fā)布 ContextStoppedEvent )啃沪,刷新上下文(刷新后發(fā)布 ContextRefreshedEvent )并關(guān)閉上下文(關(guān)閉后發(fā)布 ContextClosedEvent ):

/**

* Finish the refresh of this context, invoking the LifecycleProcessor's

* onRefresh() method and publishing the

* {@linkorg.springframework.context.event.ContextRefreshedEvent}.

*/

protectedvoidfinishRefresh(){

// Clear context-level resource caches (such as ASM metadata from scanning).

clearResourceCaches();

// Initialize lifecycle processor for this context.

initLifecycleProcessor();

// Propagate refresh to lifecycle processor first.

getLifecycleProcessor().onRefresh();

// Publish the final event.生命周期Refreshed事件

publishEvent(newContextRefreshedEvent(this));

// Participate in LiveBeansView MBean, if active.

LiveBeansView.registerApedplicationContext(this);

}

/**

* Actually performs context closing: publishes a ContextClosedEvent and

* destroys the singletons in the bean factory of this application context.

*

Called by both {@codeclose()} and a JVM shutdown hook, if any.

*@seeorg.springframework.context.event.ContextClosedEvent

*@see#destroyBeans()

*@see#close()

*@see#registerShutdownHook()

*/

protectedvoiddoClose(){

if(this.active.get() &&this.closed.compareAndSet(false,true)) {

if(logger.isInfoEnabled()) {

logger.info("Closing "+this);

}

LiveBeansView.unregisterApplicationContext(this);

try{

// Publish shutdown event. ContextClosed事件

publishEvent(newContextClosedEvent(this));

}

catch(Throwable ex) {

logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);

}

// Stop all Lifecycle beans, to avoid delays during individual destruction.

try{

getLifecycleProcessor().onClose();

}

? ? ? ? ...

? ? ? }? ? ?


//---------------------------------------------------------------------

// Implementation of Lifecycle interface

//---------------------------------------------------------------------

@Override

publicvoidstart(){

getLifecycleProcessor().start();

publishEvent(newContextStartedEvent(this));

}

@Override

publicvoidstop(){

getLifecycleProcessor().stop();

publishEvent(newContextStoppedEvent(this));

}

使用Spring的Web應(yīng)用程序也可以處理與請(qǐng)求相關(guān)聯(lián)的另一種類型的事件(之前說到的 RequestHandledEvent )。它的處理方式和面向上下文的事件類似窄锅。首先创千,我們可以找到org.springframework.web.servlet.FrameworkServlet中處理請(qǐng)求的方法processRequest。在這個(gè)方法結(jié)束的時(shí)候入偷,調(diào)用了 private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) 方法追驴。如其名稱所表達(dá)的,此方法將向所有監(jiān)聽器發(fā)布給定的 RequestHandledEvent 疏之。事件在傳遞給應(yīng)用程序上下文的 publishEvent 方法后殿雪,將由 event multicaster 發(fā)送。這里沒毛病锋爪,因?yàn)?RequestHandledEvent 擴(kuò)展了與 ApplicationContextEvent 相同的類丙曙,即 ApplicationEvent 爸业。來看看 publishRequestHandledEvent 方法的源碼:

privatevoidpublishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,

longstartTime, @Nullable Throwable failureCause) {

//很多人問我Spring5和4的代碼有什么區(qū)別,就在很多細(xì)微的地方亏镰,Spring一直在做不懈的改進(jìn)和封裝扯旷,不多說,沒事可自行 //對(duì)比索抓,能學(xué)到很多東西

if(this.publishEvents &&this.webApplicationContext !=null) {

// Whether or not we succeeded, publish an event.

longprocessingTime = System.currentTimeMillis() - startTime;

this.webApplicationContext.publishEvent(

newServletRequestHandledEvent(this,

request.getRequestURI(), request.getRemoteAddr(),

request.getMethod(), getServletConfig().getServletName(),

WebUtils.getSessionId(request), getUsernameForRequest(request),

processingTime, failureCause, response.getStatus()));

}

}

需要注意的是钧忽,你可以關(guān)閉基于請(qǐng)求的事件的調(diào)度。 FrameworkServlet的setPublishEvents(boolean publishEvents) 允許禁用事件分派逼肯,例如改進(jìn)應(yīng)用程序性能(看代碼注釋耸黑,當(dāng)沒有監(jiān)聽器來管理相應(yīng)事件的時(shí)候,干嘛要浪費(fèi)性能)篮幢。默認(rèn)情況下大刊,事件調(diào)度被激活(默認(rèn)為true)。

/** Should we publish a ServletRequestHandledEvent at the end of each request? */

privatebooleanpublishEvents =true;

/**

* Set whether this servlet should publish a ServletRequestHandledEvent at the end

* of each request. Default is "true"; can be turned off for a slight performance

* improvement, provided that no ApplicationListeners rely on such events.

*@seeorg.springframework.web.context.support.ServletRequestHandledEvent

*/

publicvoidsetPublishEvents(booleanpublishEvents){

this.publishEvents = publishEvents;

}

假如有思考的話洲拇,從上面的代碼中可以知道奈揍,事件在應(yīng)用程序響應(yīng)性上的表現(xiàn)會(huì)很差(大都是一個(gè)調(diào)用另一個(gè))。這是因?yàn)槟J(rèn)情況下赋续,它們是同步調(diào)用線程(即使用同一線程去處理事務(wù)男翰,處理請(qǐng)求,以及準(zhǔn)備視圖的輸出)纽乱。因此蛾绎,如果一個(gè)監(jiān)聽器需要幾秒鐘的時(shí)間來響應(yīng),整個(gè)應(yīng)用程序可能會(huì)受到慢的要死鸦列。幸運(yùn)的是租冠,我們可以指定事件處理的異步執(zhí)行(參考上面的 multicastEvent 源碼)。需要注意的是薯嗤,所處理的事件將無法與調(diào)用者的上下文(類加載器或事務(wù))進(jìn)行交互顽爹。這里參考 multicastEvent 方法源碼即可。默認(rèn)情況下骆姐,org.springframework.core.task.SyncTaskExecutor用來調(diào)用相應(yīng)監(jiān)聽器镜粤。

publicclassSyncTaskExecutorimplementsTaskExecutor,Serializable{

/**

* Executes the given {@codetask} synchronously, through direct

* invocation of it's {@linkRunnable#run() run()} method.

*@throwsIllegalArgumentException if the given {@codetask} is {@codenull}

*/

@Override

publicvoidexecute(Runnable task){

Assert.notNull(task,"Runnable must not be null");

task.run();

}

}

在Spring中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的監(jiān)聽器

為了更好的理解事件監(jiān)聽器,我們來寫一個(gè)小的測(cè)試用例玻褪。通過這個(gè)例子肉渴,我們要證明默認(rèn)情況下,監(jiān)聽器 listeners 在其調(diào)用者線程中執(zhí)行了分發(fā)的事件带射。所以同规,為了不立即得到結(jié)果,我們?cè)诒O(jiān)聽器中休眠5秒(調(diào)用Thread.sleep(5000))。測(cè)試檢查是否達(dá)到3個(gè)目的:如果controller 的返回結(jié)果和所預(yù)期的視圖名稱相匹配券勺,如果事件監(jiān)聽器花了5秒鐘的時(shí)間才響應(yīng)(Thread.sleep執(zhí)行沒有任何問題)绪钥,并且如果controller 的同樣花了5秒鐘來生成視圖(因?yàn)楸O(jiān)聽器的休眠)。

第二個(gè)定義的測(cè)試將驗(yàn)證我們的監(jiān)聽器是否在另一個(gè)事件中被捕獲(和之前的類繼承同一個(gè)類型)朱灿。首先昧识,在配置文件中對(duì)bean的定義:

<--ThisbeanwillcatchSampleCustomEventlaunchedintestedcontroller-->

<--Thankstothisbeanwe'llabletogettheexecutiontimesoftestedcontrollerandlistener-->

事件和監(jiān)聽器的代碼:

publicclassSampleCustomEventextendsApplicationContextEvent{

privatestaticfinallongserialVersionUID =4236181525834402987L;

publicSampleCustomEvent(ApplicationContext source){

super(source);

? }

}

publicclassOtherCustomEventextendsApplicationContextEvent{

privatestaticfinallongserialVersionUID =5236181525834402987L;

publicOtherCustomEvent(ApplicationContext source){

super(source);

? }

}

publicclassSampleCustomEventListenerimplementsApplicationListener{

@Override

publicvoidonApplicationEvent(SampleCustomEvent event){

longstart = System.currentTimeMillis();

try{

Thread.sleep(5000);

}catch(Exception e) {

? ? ? e.printStackTrace();

? ? }

longend = System.currentTimeMillis();

inttestTime = Math.round((end - start) /1000);

((TimeExecutorHolder) event.getApplicationContext().getBean("timeExecutorHolder")).addNewTime("sampleCustomEventListener",newInteger(testTime));

? }

}

沒什么復(fù)雜的,事件只能被用來初始化盗扒。監(jiān)聽器通過獲取當(dāng)前時(shí)間(以毫秒為單位)來測(cè)試所執(zhí)行時(shí)間跪楞,并在轉(zhuǎn)換后保存(以秒為單位)。監(jiān)聽器使用的 TimeExecutorHolder 也不復(fù)雜:

publicclassTimeExecutorHolder{

privateMap testTimes =newHashMap();


publicvoidaddNewTime(String key, Integer value){

? ? testTimes.put(key, value);

? }


publicIntegergetTestTime(String key){

returntestTimes.get(key);

? }

}

此對(duì)象只保留測(cè)試元素的執(zhí)行時(shí)間一個(gè)Map侣灶。測(cè)試的controller實(shí)現(xiàn)看起來類似于監(jiān)聽器甸祭。唯一的區(qū)別是它發(fā)布一個(gè)事件(接著被已定義的監(jiān)聽器捕獲)并返回一個(gè)名為“success”的視圖:

@Controller

publicclassTestController{

@Autowired

privateApplicationContext context;


@RequestMapping(value ="/testEvent")

publicStringtestEvent(){

longstart = System.currentTimeMillis();

context.publishEvent(newSampleCustomEvent(context));

longend = System.currentTimeMillis();

inttestTime = (int)((end - start) /1000);

((TimeExecutorHolder) context.getBean("timeExecutorHolder")).addNewTime("testController",newInteger(testTime));

return"success";

? }

@RequestMapping(value ="/testOtherEvent")

publicStringtestOtherEvent(){

context.publishEvent(newOtherCustomEvent(context));

return"success";

? }

}

最后,寫一個(gè)測(cè)試用例褥影,它調(diào)用/testEvent并在 TimeExecutorHolder bean 之后檢查以驗(yàn)證兩個(gè)部分的執(zhí)行時(shí)間:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"file:applicationContext-test.xml"})

@WebAppConfiguration

publicclassSpringEventsTest{

@Autowired

privateWebApplicationContext wac;

privateMockMvc mockMvc;

@Before

publicvoidsetUp(){

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();

? }

@Test

publicvoidtest(){

try{

MvcResult result = mockMvc.perform(get("/testEvent")).andReturn();

? ? ? ModelAndView view = result.getModelAndView();

String expectedView ="success";

assertTrue("View name from /testEvent should be '"+expectedView+"' but was '"+view.getViewName()+"'", view.getViewName().equals(expectedView));

}catch(Exception e) {

? ? ? e.printStackTrace();

? ? }

TimeExecutorHolder timeHolder = (TimeExecutorHolder)this.wac.getBean("timeExecutorHolder");

intcontrollerSec = timeHolder.getTestTime("testController").intValue();

inteventSec = timeHolder.getTestTime("sampleCustomEventListener").intValue();

assertTrue("Listener for SampleCustomEvent should take 5 seconds before treating the request but it took "+eventSec+" instead",? eventSec ==5);

assertTrue("Because listener took 5 seconds to response, controller should also take 5 seconds before generating the view, but it took "+controllerSec+" instead", controllerSec ==5);

? }

@Test

publicvoidotherTest(){

TimeExecutorHolder timeHolder = (TimeExecutorHolder)this.wac.getBean("timeExecutorHolder");

timeHolder.addNewTime("sampleCustomEventListener", -34);

try{

MvcResult result = mockMvc.perform(get("/testOtherEvent")).andReturn();

? ? ? ModelAndView view = result.getModelAndView();

String expectedView ="success";

assertTrue("View name from /testEvent should be '"+expectedView+"' but was '"+view.getViewName()+"'", view.getViewName().equals(expectedView));

}catch(Exception e) {

? ? ? e.printStackTrace();

? ? }

Integer eventSecObject = timeHolder.getTestTime("sampleCustomEventListener");

assertTrue("SampleCustomEventListener shouldn't be trigerred on OtherEvent but it was", eventSecObject.intValue() == -34);

? }

}

測(cè)試通過沒有任何問題池户。它證明了我們所設(shè)定的許多假設(shè)。

首先凡怎,我們看到事件編程包括在信號(hào)發(fā)送到應(yīng)用程序時(shí)觸發(fā)并執(zhí)行某些操作校焦。這個(gè)信號(hào)必須有一個(gè)監(jiān)聽器在監(jiān)聽。在Spring中统倒,由于監(jiān)聽器中的泛型定義( void onApplicationEvent(E event); )寨典,事件可以很容易地被 listeners 所捕獲。通過它房匆,如果所觸發(fā)的事件對(duì)應(yīng)于監(jiān)聽器所預(yù)期的事件耸成,我們無須多余的檢查(說的啰嗦了,就是符合所需求的類型即可浴鸿,省去很多麻煩井氢,我們可以直接根據(jù)泛型就可以實(shí)現(xiàn)很多不同的處理)。我們還發(fā)現(xiàn)岳链,默認(rèn)情況下花竞,監(jiān)聽器是以同步方式執(zhí)行的。所以在調(diào)用線程同時(shí)執(zhí)行比如視圖生成或數(shù)據(jù)庫處理的操作是不行的掸哑。

最后左胞,要說的是,算是一個(gè)前后端通用的思想吧举户,所謂的事件,其實(shí)想來遍烦,不過是一個(gè)接口而已俭嘁,把這個(gè)接口派發(fā)出去(event multicaster),由誰來實(shí)現(xiàn)服猪,這是他們的事情供填,這里就有一個(gè)裝飾類(這么理解就好)拐云,其名字叫l(wèi)istener,拿到這個(gè)派發(fā)的事件接口近她,然后調(diào)用相應(yīng)的實(shí)現(xiàn)叉瘩,這里為了程序的更加靈活和高可用,我們會(huì)調(diào)用相應(yīng)的adapter適配器粘捎,最后調(diào)用其相應(yīng)的Handler實(shí)現(xiàn)薇缅,然后Handler會(huì)調(diào)用相應(yīng)的service,service調(diào)用dao攒磨。

同樣這個(gè)思想用在前端就是組件對(duì)外派發(fā)一個(gè)事件泳桦,這個(gè)事件由其父組件或者實(shí)現(xiàn),或者繼續(xù)向外派發(fā)娩缰,最后用一個(gè)具體的方法將之實(shí)現(xiàn)即可

其實(shí)對(duì)應(yīng)于我們的數(shù)學(xué)來講就是灸撰,我們定義一個(gè)數(shù)學(xué)公式f(x)*p(y)一樣,這個(gè)派發(fā)出去拼坎,無論我們先實(shí)現(xiàn)了f(x)還是先實(shí)現(xiàn)了p(y)浮毯,還是一次全實(shí)現(xiàn),還是分幾次派發(fā)出去泰鸡,終究我們會(huì)在最后去將整個(gè)公式完全解答出來债蓝,這也就是所謂的事件機(jī)制,難么鸟顺?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惦蚊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子讯嫂,更是在濱河造成了極大的恐慌蹦锋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欧芽,死亡現(xiàn)場(chǎng)離奇詭異莉掂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)千扔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門憎妙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曲楚,你說我怎么就攤上這事厘唾。” “怎么了龙誊?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵抚垃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)鹤树,這世上最難降的妖魔是什么铣焊? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮罕伯,結(jié)果婚禮上曲伊,老公的妹妹穿的比我還像新娘。我一直安慰自己追他,他們只是感情好坟募,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著湿酸,像睡著了一般婿屹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上推溃,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天昂利,我揣著相機(jī)與錄音,去河邊找鬼铁坎。 笑死蜂奸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的硬萍。 我是一名探鬼主播扩所,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼朴乖!你這毒婦竟也來了祖屏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤买羞,失蹤者是張志新(化名)和其女友劉穎袁勺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畜普,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡期丰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吃挑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝荡。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舶衬,靈堂內(nèi)的尸體忽然破棺而出埠通,到底是詐尸還是另有隱情,我是刑警寧澤逛犹,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布植阴,位于F島的核電站蟹瘾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掠手。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一狸捕、第九天 我趴在偏房一處隱蔽的房頂上張望喷鸽。 院中可真熱鬧,春花似錦灸拍、人聲如沸做祝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混槐。三九已至,卻和暖如春轩性,著一層夾襖步出監(jiān)牢的瞬間声登,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工揣苏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悯嗓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓卸察,卻偏偏與公主長(zhǎng)得像脯厨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坑质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容