spring — Spring中的事件驅動機制解析

Haqiu.Hwang發表於2020-11-20

1、JAVA中的事件驅動機制

JDK不僅提供了Observable類、Observer介面支援觀察者模式,而且也提供了EventObject、EventListener介面來支援事件監聽模式。這些類都屬於java.util包下。

1.1 觀察者模式(JDK1.0 Observable和Observer)
  • 被觀察者Observable,相當於事件源和事件,執行邏輯時通知observer即可觸發oberver的update,同時可傳被觀察者和引數:addObserver/deleteObserver/notifyObservers()等,具體請參考javadoc。

  • 觀察者Observer,提供了觀察者需要的主要抽象:update(Observable o, Object arg),此處還提供了一種推模型(目標主動把資料通過arg推到觀察者)/拉模型(目標需要根據自己去拉資料,arg為null)。

原始碼分析:

// 觀察者,實現此介面即可
public interface Observer {
	// 當被觀察的物件發生變化時候,這個方法會被呼叫
	//Observable o:被觀察的物件
	// Object arg:傳入的引數
    void update(Observable o, Object arg);
}

// 它是一個Class
public class Observable {

	// 是否變化,決定了後面是否呼叫update方法
    private boolean changed = false;
    // 用來存放所有`觀察自己的物件`的引用,以便逐個呼叫update方法
    // 需要注意的是:1.8的jdk原始碼為Vector(執行緒安全的),有版本的原始碼是ArrayList的集合實現; 
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

	public synchronized void addObserver(Observer o); //新增一個觀察者 注意呼叫的是addElement方法,新增到末尾   所以執行時是倒序執行的
	public synchronized void deleteObserver(Observer o);
	public synchronized void deleteObservers(); //刪除所有的觀察者

	// 迴圈呼叫所有的觀察者的update方法
	public void notifyObservers();
	public void notifyObservers(Object arg);
    public synchronized int countObservers() {
        return obs.size();
    }

	// 修改changed的值
    protected synchronized void setChanged() {
        changed = true;
    }
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
}
1.2 釋出訂閱模式(JDK1.1 EventListener和EventObject)

事件機制一般包括三個部分:EventObject,EventListener和Source。

  • EventObject:事件狀態物件的基類,它封裝了事件源物件以及和事件相關的資訊。所有java的事件類都需要繼承該類
  • EventListener:是一個標記介面,就是說該介面內是沒有任何方法的。所有事件監聽器都需要實現該介面。事件監聽器註冊在事件源上,當事件源的屬性或狀態改變的時候,呼叫相應監聽器內的回撥方法(自己寫)
  • Source:事件源,即觸發事件的物件,他裡面必須含有監聽它的監聽器們;

缺點:對比上面的觀察者模式,監聽模式使用起來確實非常的繁瑣,且還執行緒安全問題還得自己考慮解決。

2、Spring中的事件機制

首先看一下Spring提供的事件驅動模型體系圖:
在這裡插入圖片描述
事件流程:

  • Spring提供了ApplicationEventPublisher介面作為事件釋出者,ApplicationContext介面繼承了該介面,擔當著事件釋出者的角色。
  • Spring提供了ApplicationEventMulticaster介面,負責管理ApplicationListener 和 真正釋出ApplicationEvent(ApplicationContext是委託給它完成的)
  • ApplicationListener實現了JDK的EventListener,但它抽象出一個onApplicationEvent方法,使用更方便。
  • ApplicationEvent繼承自EventObject。 它就是媒介,充當介質的作用。
  • ApplicationEventPublisher最終都是委託給ApplicationEventMulticaster去完成的。當然你也可以自己去實現一個ApplicationEventMulticaster

在spring中,容器管理所有的 bean。是ApplicationEvent 驅動的,一個ApplicationEvent publish了,觀察這個事件的監聽者就會收到通知。

在IOC容器原始碼中,與EventListener有關聯的步驟

  • initApplicationEventMulticaster():初始化事件多播器
  • registerListeners():註冊Listener到多播器
  • finishBeanFactoryInitialization(beanFactory):涉及將@EventListener轉為普通Listener
  • finishRefresh():釋出容器重新整理完成事件ContextRefreshedEvent

ApplicationListener類解析:

public abstract class ApplicationEvent extends EventObject {
	private static final long serialVersionUID = 7099057708183571937L;	
	private final long timestamp;

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

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	// 此子介面提供了泛型,和提供了統一的處理方法
	void onApplicationEvent(E event);
}

@FunctionalInterface
public interface ApplicationEventPublisher {
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}
	
	// 這個介面是Spring4.2後提供的,可以釋出任意的事件物件(即使不是ApplicationEvent的子類了)
	// 當這個物件不是一個ApplicationEvent,我們會使用PayloadApplicationEvent來包裝一下再傳送
	// 比如後面會建講到的@EventListener註解標註的放 就是使用的它
	void publishEvent(Object event);
}

通過Spring原始碼我們瞭解到,Spring容器重新整理的時候會發布ContextRefreshedEvent事件,因此若我們需要監聽此事件,直接寫個監聽類即可:

@Slf4j
@Component
public class ApplicationRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Object source = event.getSource();
        // 此處的source就是ApplicationContext這個物件
        System.out.println(source); 
        //容器此時已經準備好了,可以做你該做的事了~......(請注意:若存在父子容器或者多個容器情況,此方法會被執行多次,請務必注意是否冪等)
    }
}

釋出事件監聽demo:

public class MyAppEvent extends ApplicationEvent {

    public MyAppEvent(Object source) {
        super(source);
    }
}

// 寫個監聽器,然後交給容器管理即可
@Slf4j
@Component
public class MyEventListener implements ApplicationListener<MyAppEvent> {

    @Override
    public void onApplicationEvent(MyAppEvent event) {
        Object source = event.getSource();
        long timestamp = event.getTimestamp();

        System.out.println(source);
        System.out.println(timestamp);
        //doSomething

    }
}

public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
    // 釋出自己的事件
    applicationContext.publishEvent(new MyAppEvent("this is test event"));
}
// 輸出:
this is test event
1553581974928

總結:
使用spring事件機制能很好地幫助我們消除不同業務間的耦合關係,也可以提高執行效率,應該根據業務場景靈活選擇.

相關文章