1. 程式人生 > >設計模式:觀察者模式(有利於代碼解耦)

設計模式:觀察者模式(有利於代碼解耦)

attribute 處理 script refresh 含義 復制 重新 ecif nec

什麽是ApplicationContext?
它是Spring的核心,Context我們通常解釋為上下文環境,但是理解成容器會更好些。
ApplicationContext則是應用的容器。

Spring把Bean(object)放在容器中,需要用就通過get方法取出來。

ApplicationEvent

是個抽象類,裏面只有一個構造函數和一個長整型的timestamp。

ApplicationListener

是一個接口,裏面只有一個onApplicationEvent方法。

所以自己的類在實現該接口的時候,要實裝該方法。

如果在上下文中部署一個實現了ApplicationListener接口的bean,

那麽每當在一個ApplicationEvent發布到 ApplicationContext時,
這個bean得到通知。其實這就是標準的Oberver設計模式。

一個Demo:

技術分享圖片
package com;

import org.springframework.context.ApplicationEvent;

/**
 * Created by MyWorld on 2016/7/31.
 */
public class DemoEvent extends ApplicationEvent{
    public DemoEvent(Object source) {
        super(source);
    }
}
技術分享圖片

技術分享圖片
package com;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * Created by MyWorld on 2016/7/31.
 */
@Component
public class DemoObserver1 implements ApplicationListener<DemoEvent> {
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        System.out.println(this.getClass().getCanonicalName() + " receive: " + demoEvent.getSource());
    }
}
技術分享圖片

技術分享圖片
package com;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * Created by MyWorld on 2016/7/31.
 */
@Component
public class DemoObserver2 implements ApplicationListener<DemoEvent> {
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        System.out.println(this.getClass().getCanonicalName() + " receive: " + demoEvent.getSource());
    }
}
技術分享圖片

技術分享圖片
package com;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

/**
 * Created by MyWorld on 2016/7/31.
 */
@Component
public class DemoSubject implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher eventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.eventPublisher = applicationEventPublisher;
    }

    public void publish() {
        eventPublisher.publishEvent(new DemoEvent("Hello world!"));
    }
}
技術分享圖片

測試類:

技術分享圖片
package com;

import com.DemoConfig;
import com.DemoSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by MyWorld on 2016/7/31.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DemoConfig.class})
public class DemoSubjectTest {

    @Autowired
    private DemoSubject demoSubject;

    @Test

    public void testPublish() throws Exception {
        demoSubject.publish();
    }
}
技術分享圖片

執行結果:

技術分享圖片
"C:\Program Files\Java\jdk1.8.0_65\bin\java" -ea -Didea.launcher.port=7535 "-Didea.launcher.bin.path=D:\Program Files\JetBrains\IntelliJ IDEA 14.1.6\bin" -Dfile.encoding=GBK -classpath "D:\Program Files\JetBrains\IntelliJ IDEA 14.1.6\lib\idea_rt.jar;D:\Program Files\JetBrains\IntelliJ IDEA 14.1.6\plugins\junit\lib\junit-rt.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\access-bridge.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext\zipfs.jar;D:\source\git\EventDemo\target\test-classes;D:\source\git\EventDemo\target\classes;G:\java\repository\org\springframework\spring-context\4.1.6.RELEASE\spring-context-4.1.6.RELEASE.jar;G:\java\repository\org\springframework\spring-aop\4.1.6.RELEASE\spring-aop-4.1.6.RELEASE.jar;G:\java\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;G:\java\repository\org\springframework\spring-beans\4.1.6.RELEASE\spring-beans-4.1.6.RELEASE.jar;G:\java\repository\org\springframework\spring-core\4.1.6.RELEASE\spring-core-4.1.6.RELEASE.jar;G:\java\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;G:\java\repository\org\springframework\spring-expression\4.1.6.RELEASE\spring-expression-4.1.6.RELEASE.jar;G:\java\repository\org\springframework\spring-test\4.1.6.RELEASE\spring-test-4.1.6.RELEASE.jar;G:\java\repository\junit\junit\4.12\junit-4.12.jar;G:\java\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.execution.application.AppMain com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 com.DemoSubjectTest
七月 31, 2016 1:50:40 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
七月 31, 2016 1:50:40 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
七月 31, 2016 1:50:40 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
七月 31, 2016 1:50:40 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
七月 31, 2016 1:50:40 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@17246de, org.springframework.test.context.support.DirtiesContextTestExecutionListener@f67ac7]
七月 31, 2016 1:50:40 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@1b6a447: startup date [Sun Jul 31 13:50:40 CST 2016]; root of context hierarchy
com.DemoObserver1 receive: Hello world!
com.DemoObserver2 receive: Hello world!

Process finished with exit code 0
技術分享圖片

代碼:https://github.com/helloworldtang/EventDemo.git



首先,什麽是觀察者模式:多個觀察者去監聽主題,當主題發生變化的時候,主題會通知所有的觀察者
盜用網上的一個圖:

技術分享圖片

從上圖的結構可以看出,主題維護了一個觀察者類型的鏈表,每當主題變化的時候,就會循環調用各個觀察者的對應方法(這就是通知)。
在觀察者模式中,又分為 推模型 和 拉模型

  • 推模型:主題向觀察者推送詳細信息。
  • 拉模型:主題把自身作為一個參數發送給觀察者,觀察者需要什麽信息,那麽就 主題.getXX() 。

Java中的觀察者模式


再來看看 Java中的觀察者模式,最後再提一下 個人在 SpringBoot 中對於觀察者模式的實際使用。

Java 提供了 Observer接口(觀察者接口) 和 Observable 接口(被觀察者接口 / 主題接口)。源碼如下:

Observable 接口(被觀察者接口 / 主題接口):

public class Observable {  
    private boolean changed = false;  
    private Vector<Observer> obs;  
    public Observable() {  
        obs = new Vector<>();  
    }  
    public synchronized void addObserver(Observer o) {  
        if (o == null)  
            throw new NullPointerException();  
        if (!obs.contains(o)) {  
            obs.addElement(o);  
        }  
    }  
    public synchronized void deleteObserver(Observer o) {  
        obs.removeElement(o);  
    }  
    public void notifyObservers() {  
        notifyObservers(null);  
    }  
    public void notifyObservers(Object arg) {  

        Object[] arrLocal;  

        synchronized (this) {  
            if (!changed)  
                return;  
            arrLocal = obs.toArray();  
           ·            clearChanged();  
        }  

        for (int i = arrLocal.length-1; i>=0; i--)  
            ((Observer)arrLocal[i]).update(this, arg);  
    }  

    public synchronized void deleteObservers() {  
        obs.removeAllElements();  
    }  
    protected synchronized void setChanged() {  
        changed = true;  
    }  
    protected synchronized void clearChanged() {  
        changed = false;  
    }  
    public synchronized boolean hasChanged() {  
        return changed;  
    }  
    public synchronized int countObservers() {  
        return obs.size();  
    }  
}  

如上代碼:通過 Vector 維護一個 觀察者類型的數組。通過調用 notifyObeservers(Object arg) 方法 來通過觀察者。在實現中,也是通過for 循環 通知。
Ps:註意:從代碼上看,需要先設changed。

Observer接口(觀察者接口):

public interface Observer {  
    void update(Observable o, Object arg);  
} 

這兩個參數的含義為:

* @param   o     the observable object.  
* @param   arg   an argument passed to the <code>notifyObservers</code>  

所以,此時即實現了 推模型,也實現了 拉模型。如果我們使用,那麽分別實現這兩個接口即可。

SpringBoot事件機制對於觀察者模式的運用


那麽在個人的實際運用中,做的是一個記賬的服務,讓別人來調用。當然,可以讓別人直接在他們的業務處理後面,例如購買了XX東西,馬上就直接調用我的記賬服務,但是這樣其實是一個緊耦合,由於是兩個不同的業務,所以緊耦合感覺不太好。那麽 觀察者模式就有利於解耦

對於Spring Boot 的事件機制,同樣離不開 這2個東西-主題,觀察者。 但是 ,spring boot 把 之前所說的通知,包裝成了一個 Event。下面分析這三者。

SpringBoot的主題

Spring boot 的主題 可以 由 ApplicationContext 來充當。ApplicaitonContext 繼承於 ApplicationEventPublisher。ApplicaiotnEventPublisher 源碼如下:

public interface ApplicationEventPublisher {  

    /** 
     * Notify all listeners registered with this application of an application 
     * event. Events may be framework events (such as RequestHandledEvent) 
     * or application-specific events. 
     * @param event the event to publish 
     * @see org.springframework.web.context.support.RequestHandledEvent 
     */  
    void publishEvent(ApplicationEvent event);  

}  

其實該接口就是我們 發布事件的接口。

SpringBoot 的觀察者

Spring Boot 的觀察者由 ApplicationListener 來進行充當。源碼如下:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {  

    /** 
     * Handle an application event. 
     * @param event the event to respond to 
     */  
    void onApplicationEvent(E event);  

} 

可以看到, onApplicaiton(E event) 方法即 上文所說的 update 方法。

SpringBoot的Event

自定義事件 繼承ApplicationEvent抽象類

是Subject和Object傳遞消息的介質。因為這個類型的特殊性、唯一性,可以作為標識的依據。

就像Button按鈕的點擊事件、Move事件

上文所說的 主題 和 觀察者 都有體現,傳輸的消息 Spring Boot 使用了一個 ApplicationEvent 進行了封裝
源碼如下:

public abstract class ApplicationEvent extends EventObject {  

    /** use serialVersionUID from Spring 1.2 for interoperability */  
    private static final long serialVersionUID = 7099057708183571937L;  

    /** System time when the event happened */  
    private final long timestamp;  

    public ApplicationEvent(Object source) {  
        super(source);  
        this.timestamp = System.currentTimeMillis();  
    }  

    public final long getTimestamp() {  
        return this.timestamp;  
    }  

}  

EventObject 源碼:

public class EventObject implements java.io.Serializable {  

    private static final long serialVersionUID = 5516075349620653480L;  

    /** 
     * The object on which the Event initially occurred. 
     */  
    protected transient Object  source;  

    public EventObject(Object source) {  
        if (source == null)  
            throw new IllegalArgumentException("null source");  

        this.source = source;  
    }  

    public Object getSource() {  
        return source;  
    }  

    public String toString() {  
        return getClass().getName() + "[source=" + source + "]";  
    }  
}  

由上面的代碼 可知,其實 ApplicationEvent 就是 把需要傳輸的消息 封裝起來。這個消息並沒有想 Java 的實現那樣推拉模型都實現了,而是 只實現了 拉模型

最後,我們程序中只需要 註入ApplicaitonContext (Subject)發送消息publishEvent;
實現 ApplicationListener 接口的Observer進行相應的處理即可。

註冊事件監聽器,我們只需要把它配置成一個Bean即可,ApplicationContext容器會自動將其註冊

總結


觀察者模式實質是 有兩個 東西:

  • 一個是 主題
  • 一個是觀察者

主題中維護了 觀察者列表的引用。當主題有變更的時候,循環調用觀察者,通知其做相應的處理。另外,不論是 Java,還是 Spring ,都是利用這個原理,只是有不同的類充當 主題 和 觀察者。
另外,觀察者模式有一個好處:解耦

http://blog.csdn.net/allen215902/article/details/50747438

有事件,即有事件監聽器. 有人問你spring監聽器有哪些你看了下文即也知道了。 事件傳播 ApplicationContext基於Observer模式(java.util包中有對應實現),提供了針對Bean的事件傳播功能。
通過Application. publishEvent方法,我們可以將事件通知系統內所有的ApplicationListener。 事件傳播的一個典型應用是,當Bean中的操作發生異常(如數據庫連接失敗),則通過事件傳播機制通知異常監聽器進行處理。在筆者的一個項目中,就曾經借助事件機制,較好的實現了當系統異常時在監視終端上報警,同時發送報警SMS至管理員手機的功能。

ApplicationContext容器提供了容器內部事件發布功能,是繼承自JavaSE標準自定義事件類而實現的。

JavaSE標準自定義事件結構不在此詳細描述,一張圖很直觀的描述清楚:

技術分享圖片

EventObject,為JavaSE提供的事件類型基類,任何自定義的事件都繼承自該類,例如上圖中右側灰色的各個事件。Spring中提供了該接口的子類ApplicationEvent。

EventListener為JavaSE提供的事件監聽者接口(Observer),任何自定義的事件監聽者都實現了該接口,如上圖左側的各個事件監聽者。
Spring中提供了該接口的子類ApplicationListener接口。

JavaSE中未提供事件發布者這一角色類,由各個應用程序自行實現事件發布者這一角色。Spring中提供了ApplicationEventPublisher接口作為事件發布者,並且ApplicationContext(Subject)實現了這個接口,擔當起了事件發布者這一角色。
但ApplicationContext在具體實現上有所差異,Spring提供了ApplicationEventMulticaster接口,負責管理ApplicationListener和發布ApplicationEvent。ApplicationContext會把相應的事件相關工作委派給ApplicationEventMulticaster接口實現類來做。
類圖如下所示:

技術分享圖片

事件發布時序圖如下:

技術分享圖片 -------------------------------------------------------------------------------------------------

Spring中提供一些Aware相關的接口,BeanFactoryAware、 ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等,其中最常用到的是ApplicationContextAware。
實現ApplicationContextAware的Bean,在Bean被初始後,將會被註入ApplicationContext的實例
ApplicationContextAware提供了publishEvent()方法,實現Observer(觀察者)設計模式的事件傳播機,提供了針對Bean的事件傳播功能。
通過Application.publishEvent方法,我們可以將事件通知系統內所有的ApplicationListener。

Spring事件處理一般過程:

◆定義Event類(Subject和Observer傳遞消息的介質),繼承org.springframework.context.ApplicationEvent。
◆編寫發布事件類Publisher(Subject),實現org.springframework.context.ApplicationContextAware接口。
◆覆蓋方法setApplicationContext(ApplicationContext applicationContext)和發布方法publish(Object obj)。
◆定義時間監聽類EventListener(Observer),實現ApplicationListener接口,實現方法onApplicationEvent(ApplicationEvent event)。

1.發布 1.1事件的發布者需要實現的接口 org.springframework.context.ApplicationEventPublisherAware 1.2 代碼示例 技術分享圖片
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

/**
 * 
 * @author zq
 *
 */
public class HelloWorld implements ApplicationEventPublisherAware{
 private String word;
 private ApplicationEventPublisher tradeEventPublisher;

 public void setWord(String w){
  this.word = w;
 }
 
 public void say(){
  System.out.println("say : "+ this.word);
  //construct a TradeEvent instance and publish it
  TradeEvent tradeEvent = new TradeEvent(new String("tradeEvent"));
  this.tradeEventPublisher.publishEvent(tradeEvent);
 }

 @Override
 public void setApplicationEventPublisher(
   ApplicationEventPublisher applicationEventPublisher) {
  // TODO Auto-generated method stub
  this.tradeEventPublisher = applicationEventPublisher;
 }
}
技術分享圖片

2.接受事件 2.1需要實現的接口org.springframework.context.ApplicationListener 2.2代碼示例 技術分享圖片
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;

public class TradeContextListener implements ApplicationListener{

 @Override
 public void onApplicationEvent(ApplicationEvent e) {
  
  System.out.println(e.getClass().toString());
  // TODO Auto-generated method stub
  if (e instanceof ContextStartedEvent){
   System.out.println("it was contextStartedEvent");
  }
  
  if (e instanceof TradeEvent){
   System.out.println(e.getSource());
  }
  
 }

}
技術分享圖片

3配置文件 技術分享圖片
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 <bean name="helloWorld" class="study.HelloWorld">
  <property name="word" value="hello world"/>
 </bean>

 <bean id="tradeContextListener" class="study.TradeContextListener"/>
</beans>
技術分享圖片

4.測試代碼 技術分享圖片
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import study.HelloWorld;
public class TestHelloWorld {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("study-context.xml");
  HelloWorld bean = (HelloWorld)applicationContext.getBean("helloWorld");
  bean.say();
 }

}
技術分享圖片

Spring中ApplicationContext的事件機制--- 內定事件) 在Spring中已經定義了五個標準事件,分別介紹如下:

1) ContextRefreshedEvent:當ApplicationContext初始化或者刷新時觸發該事件。

2) ContextClosedEvent:當ApplicationContext被關閉時觸發該事件。容器被關閉時,其管理的所有單例Bean都被銷毀。

3) RequestHandleEvent:在Web應用中,當一個http請求(request)結束觸發該事件。

ContestStartedEvent:Spring2.5新增的事件,當容器調用ConfigurableApplicationContext的Start()方法開始/重新開始容器時觸發該事件。

5) ContestStopedEvent:Spring2.5新增的事件,當容器調用ConfigurableApplicationContext的Stop()方法停止容器時觸發該事件。

下面通過一個例子展示如何處理Spring內定的事件(例程3.8)。創建一個Java工程,添加Spring開發能力後,新建ioc.test包。在包中新建ApplicationEventListener類,實現ApplicationListener接口,在onApplicationEvent()方法中添加事件處理代碼,如下:

1 package ioc.test;
2
3 //Import省略
4 publicclass ApplicationEventListenerimplements ApplicationListener {
5
6 publicvoid onApplicationEvent(ApplicationEvent event) {
7
8 //如果是容器刷新事件
9 if(eventinstanceof ContextClosedEvent ){
10 System.out.println(event.getClass().getSimpleName()+" 事件已發生!");
11 }elseif(eventinstanceof ContextRefreshedEvent ){//如果是容器關閉事件
12 System.out.println(event.getClass().getSimpleName()+" 事件已發生!");
13 }elseif(eventinstanceof ContextStartedEvent ){
14 System.out.println(event.getClass().getSimpleName()+" 事件已發生!");
15 }elseif(eventinstanceof ContextStoppedEvent){
16 System.out.println(event.getClass().getSimpleName()+" 事件已發生!");
17 }else{
18 System.out.println("有其它事件發生:"+event.getClass().getName());
19 }
20
21 }
22
23 }
24

在Spring配置文件中定義一個Bean,類為ApplicationEventListener,代碼如下:

1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans…………
3
4 <bean id="ApplicationEventListener" class="ioc.test.ApplicationEventListener"/>
5
6 </beans>
7

添加含有主方法的TesMain類,在主方法中,調用容器的相應方法,觸發Spring內定事件,代碼如下:

1 package ioc.test;
2
3 //import省略
4 publicclass TesMain {
5
6 publicstaticvoid main(String[] args) {
7 AbstractApplicationContext ac=newClassPathXmlApplicationContext("applicationContext.xml");
8
9
10 // ac.refresh();//觸發ContextRefreshedEvent事件
11 ac.start();//觸發ContextStartedEvent事件
12 ac.stop(); //觸發ContextStoppedEvent事件
13 ac.close();//關閉容器,觸發ContextClosedEvent事件
14
15 }
16 }
17

運行主類,控制臺輸出如下:

技術分享圖片

從例子中可以知道,要註冊事件監聽器,我們只需要把它配置成一個Bean即可,ApplicationContext容器會自動將其註冊。

http://blog.csdn.net/it_man/article/details/8440737

設計模式:觀察者模式(有利於代碼解耦)