Spring5源碼解析-Spring框架中的事件和監聽器
在本文中,我們將重點介紹Spring框架中的事件處理。首先,會先介紹下事件驅動編程這個概念。接著,我們會將精力放在專門用於Spring框架中的事件處理之上。然後我們會看到實現事件調度和監聽的主要方法。最後,我們將在Spring應用程序中展示如何使用基本的監聽器。
事件驅動編程
在開始討論事件驅動編程的編程方面之前,先來說一個場景,用來幫助大家更好地理解 event-driven 這個概念。在一個地方只有兩個賣衣服的商店A和B.在A店中,我們的消費者需要一個一個的接受服務,即,同一時間只有一個客戶可以購物。在B店裏,可以允許幾個客戶同時進行購物,當有客戶需要賣家的幫助時,他需要舉起他的右手示意一下。賣家看到後會來找他,幫助其做出更好的選擇。關於事件驅動( event-driven )編程這個概念通過上述場景描述總結後就是:通過做一些動作來作為對一些行為的回應。
如上所見,事件驅動的編程(也稱為基於事件的編程)是基於對接收到的信號的反應的編程形式。這些信號必須以某種形式來傳輸信息。舉一個簡單例子: 點擊按鈕 。我們將這些信號稱為事件。這些事件可以通過用戶操作(鼠標點擊,觸摸)或程序條件執行觸發(例如:一個元素的加載結束可以啟動另一個操作)來產生。
為了更好地了解,請看以下示例,模仿用戶在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基於實現org.springframework.context.ApplicationListener接口的bean來進行事件處理。這個接口中只有一個方法,onApplicationEvent用來當一個事件發送過來時這個方法來觸發相應的處理。該接口可以通過指定需要接收的事件來實現(不懂看源碼咯,源碼裏方法接收一個 event 作為參數)。由此,Spring會自動過濾篩選可以用來接收給定事件的監聽器( 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實例來表示。這個抽象類繼承擴展了java.util.EventObject,可以使用EventObject中的getSource方法,我們可以很容易地獲得所發生的給定事件的對象。這裏,事件存在兩種類型:
與應用程序上下文相關聯:所有這種類型的事件都繼承自 org.springframework.context.event.ApplicationContextEvent 類。它們應用於由org.springframework.context.ApplicationContext引發的事件(其構造函數傳入的是 ApplicationContext 類型的參數)。這樣,我們就可以直接通過應用程序上下文的生命周期來得到所發生的事件: ContextStartedEvent 在上下文啟動時被啟動,當它停止時啟動 ContextStoppedEvent ,當上下文被刷新時產生 ContextRefreshedEvent ,最後在上下文關閉時產生 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 請求相關聯:由 org.springframework.web.context.support.RequestHandledEvent 實例來表示,當在ApplicationContext中處理請求時,它們被引發。
Spring如何將事件分配給專門的監聽器?這個過程由事件廣播器( event multicaster )來實現,由 org.springframework.context.event.ApplicationEventMulticaster 接口的實現表示。此接口定義了3種方法,用於:
添加新的監聽器:定義了兩種方法來添加新的監聽器: addApplicationListener(ApplicationListener listener) 和 addApplicationListenerBean(String listenerBeanName) 。當監聽器對象已知時,可以應用第一個。如果使用第二個,我們需要將bean name 得到listener對象( 依賴查找DL ),然後再將其添加到 listener 列表中。在這裏順便給大家推薦一個架構交流群:617434785,裏面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高並發、高性能、分布式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。還能領取免費的學習資源。相信對於已經工作和遇到技術瓶頸的碼友,在這個群裏會有你需要的內容。
刪除監聽器:添加方法一樣,我們可以通過傳遞對象來刪除一個監聽器( removeApplicationListener(ApplicationListener listener) 或通過傳遞bean名稱( removeApplicationListenerBean(String listenerBeanName)) , 第三種方法,removeAllListeners()用來刪除所有已註冊的監聽器
將事件發送到已註冊的監聽器:由multicastEvent(ApplicationEvent event)源碼註釋可知,它用來向所有註冊的監聽器發送事件。實現可以從 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 在應用程序上下文中所在的位置。在 AbstractApplicationContext 中定義的一些方法可以看到其中包含調用public void publishEvent方法。通過這種方法的註釋可知,它負責向所有監聽器發送給定的事件:
/** * 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); } } }
該方法由以下方法調用:啟動上下文(啟動後發布 ContextStartedEvent ),停止上下文(停止後發布 ContextStoppedEvent ),刷新上下文(刷新後發布 ContextRefreshedEvent )並關閉上下文(關閉後發布 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應用程序也可以處理與請求相關聯的另一種類型的事件(之前說到的 RequestHandledEvent )。它的處理方式和面向上下文的事件類似。首先,我們可以找到org.springframework.web.servlet.FrameworkServlet中處理請求的方法proce***equest。在這個方法結束的時候,調用了 private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) 方法。如其名稱所表達的,此方法將向所有監聽器發布給定的 RequestHandledEvent 。事件在傳遞給應用程序上下文的 publishEvent 方法後,將由 event multicaster 發送。這裏沒毛病,因為 RequestHandledEvent 擴展了與 ApplicationContextEvent 相同的類,即 ApplicationEvent 。來看看 publishRequestHandledEvent 方法的源碼:
privatevoidpublishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, longstartTime, @Nullable Throwable failureCause) { //很多人問我Spring5和4的代碼有什麽區別,就在很多細微的地方,Spring一直在做不懈的改進和封裝,不多說,沒事可自行 //對比,能學到很多東西 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())); } }
需要註意的是,你可以關閉基於請求的事件的調度。 FrameworkServlet的setPublishEvents(boolean publishEvents) 允許禁用事件分派,例如改進應用程序性能(看代碼註釋,當沒有監聽器來管理相應事件的時候,幹嘛要浪費性能)。默認情況下,事件調度被激活(默認為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; }
假如有思考的話,從上面的代碼中可以知道,事件在應用程序響應性上的表現會很差(大都是一個調用另一個)。這是因為默認情況下,它們是同步調用線程(即使用同一線程去處理事務,處理請求,以及準備視圖的輸出)。因此,如果一個監聽器需要幾秒鐘的時間來響應,整個應用程序可能會受到慢的要死。幸運的是,我們可以指定事件處理的異步執行(參考上面的 multicastEvent 源碼)。需要註意的是,所處理的事件將無法與調用者的上下文(類加載器或事務)進行交互。這裏參考 multicastEvent 方法源碼即可。默認情況下,org.springframework.core.task.SyncTaskExecutor用來調用相應監聽器。
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中實現一個簡單的監聽器
為了更好的理解事件監聽器,我們來寫一個小的測試用例。通過這個例子,我們要證明默認情況下,監聽器 listeners 在其調用者線程中執行了分發的事件。所以,為了不立即得到結果,我們在監聽器中休眠5秒(調用Thread.sleep(5000))。測試檢查是否達到3個目的:如果controller 的返回結果和所預期的視圖名稱相匹配,如果事件監聽器花了5秒鐘的時間才響應(Thread.sleep執行沒有任何問題),並且如果controller 的同樣花了5秒鐘來生成視圖(因為監聽器的休眠)。
第二個定義的測試將驗證我們的監聽器是否在另一個事件中被捕獲(和之前的類繼承同一個類型)。首先,在配置文件中對bean的定義:
<--ThisbeanwillcatchSampleCustomEventlaunchedintestedcontroller--> <--Thankstothisbeanwe'llabletogettheexecutiontimesoftestedcontrollerandlistener-->
事件和監聽器的代碼:
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)); } }
沒什麽復雜的,事件只能被用來初始化。監聽器通過獲取當前時間(以毫秒為單位)來測試所執行時間,並在轉換後保存(以秒為單位)。監聽器使用的 TimeExecutorHolder 也不復雜:
publicclassTimeExecutorHolder{ privateMap testTimes =newHashMap(); publicvoidaddNewTime(String key, Integer value){ testTimes.put(key, value); } publicIntegergetTestTime(String key){ returntestTimes.get(key); } }
此對象只保留測試元素的執行時間一個Map。測試的controller實現看起來類似於監聽器。唯一的區別是它發布一個事件(接著被已定義的監聽器捕獲)並返回一個名為“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"; } }
最後,寫一個測試用例,它調用/testEvent並在 TimeExecutorHolder bean 之後檢查以驗證兩個部分的執行時間:
@RunWith(SpringJUnit4Cla***unner.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); } }
測試通過沒有任何問題。它證明了我們所設定的許多假設。
首先,我們看到事件編程包括在信號發送到應用程序時觸發並執行某些操作。這個信號必須有一個監聽器在監聽。在Spring中,由於監聽器中的泛型定義( void onApplicationEvent(E event); ),事件可以很容易地被 listeners 所捕獲。通過它,如果所觸發的事件對應於監聽器所預期的事件,我們無須多余的檢查(說的啰嗦了,就是符合所需求的類型即可,省去很多麻煩,我們可以直接根據泛型就可以實現很多不同的處理)。我們還發現,默認情況下,監聽器是以同步方式執行的。所以在調用線程同時執行比如視圖生成或數據庫處理的操作是不行的。
最後,要說的是,算是一個前後端通用的思想吧,所謂的事件,其實想來,不過是一個接口而已,把這個接口派發出去(event multicaster),由誰來實現,這是他們的事情,這裏就有一個裝飾類(這麽理解就好),其名字叫listener,拿到這個派發的事件接口,然後調用相應的實現,這裏為了程序的更加靈活和高可用,我們會調用相應的adapter適配器,最後調用其相應的Handler實現,然後Handler會調用相應的service,service調用dao。
同樣這個思想用在前端就是組件對外派發一個事件,這個事件由其父組件或者實現,或者繼續向外派發,最後用一個具體的方法將之實現即可
其實對應於我們的數學來講就是,我們定義一個數學公式f(x)*p(y)一樣,這個派發出去,無論我們先實現了f(x)還是先實現了p(y),還是一次全實現,還是分幾次派發出去,終究我們會在最後去將整個公式完全解答出來,這也就是所謂的事件機制,難麽?
Spring5源碼解析-Spring框架中的事件和監聽器