事件和平時(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)編程的編程方面之前柒竞,先來說一個(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基于實(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ī)制,難么鸟顺?