事件和平時所用的回撥思想在與GUI(JavaScript,Swing)相關的技術中非常流行。而在Web應用程式的伺服器端,我們很少去直接使用。但這並不意味著我們無法在服務端去實現一個面向事件的體系結構。
在本文中,我們將重點介紹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中處理請求的方法processRequest。在這個方法結束的時候,呼叫了 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(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);
}
}
測試通過沒有任何問題。它證明了我們所設定的許多假設。
首先,我們看到事件程式設計包括在訊號傳送到應用程式時觸發並執行某些操作。這個訊號必須有一個監聽器在監聽。在Spring中,由於監聽器中的泛型定義( void onApplicationEvent(E event); ),事件可以很容易地被 listeners 所捕獲。通過它,如果所觸發的事件對應於監聽器所預期的事件,我們無須多餘的檢查(說的囉嗦了,就是符合所需求的型別即可,省去很多麻煩,我們可以直接根據泛型就可以實現很多不同的處理)。我們還發現,預設情況下,監聽器是以同步方式執行的。所以在呼叫執行緒同時執行比如檢視生成或資料庫處理的操作是不行的。
最後,要說的是,算是一個前後端通用的思想吧,所謂的事件,其實想來,不過是一個介面而已,把這個介面派發出去(event multicaster),由誰來實現,這是他們的事情,這裡就有一個裝飾類(這麼理解就好),其名字叫listener,拿到這個派發的事件介面,然後呼叫相應的實現,這裡為了程式的更加靈活和高可用,我們會呼叫相應的adapter介面卡,最後呼叫其相應的Handler實現,然後Handler會呼叫相應的service,service呼叫dao。
同樣這個思想用在前端就是元件對外派發一個事件,這個事件由其父元件或者實現,或者繼續向外派發,最後用一個具體的方法將之實現即可
其實對應於我們的數學來講就是,我們定義一個數學公式f(x)*p(y)一樣,這個派發出去,無論我們先實現了f(x)還是先實現了p(y),還是一次全實現,還是分幾次派發出去,終究我們會在最後去將整個公式完全解答出來,這也就是所謂的事件機制,難麼?