Spring5 事件驅動模型分析

簡介

事件驅動模型,也即是我們通常說的觀察者設計模式的一種實現方式。

概念

定義對象間的一種一對多的依賴關係,當一個對象的狀態發生變化時,所有依賴它的對象都得到通知並自動更新。

核心組成
  • 事件源:負責產生事件的對象。比如我們常見的按鈕,按鈕就是一個事件源,能夠產生“點擊”這個事件
  • 事件監聽器/事件處理器:負責處理事件的對象
  • 事件:或者稱爲事件對象,是事件源和事件監聽器之間的信息橋樑。是整個事件模型驅動的核心

在這裏插入圖片描述
圖片引自:https://blog.csdn.net/zrudong/article/details/78567473

Spring中事件驅動模型核心組成
  • 事件對象:ApplicationContextEvent
  • 事件源:AbstractApplicationContext
  • 事件監聽器/事件處理器:ApplicationListener的實現類
  • 事件發佈者:ApplicationEventPublisher
  • 事件廣播器:ApplicationEventMulticaster

案例說明

用戶註冊功能需要實現:

  • 註冊用戶
  • 加積分
  • 發確認郵件
  • 如果是遊戲帳戶,可能贈送遊戲大禮包
  • 索引用戶數據

以下案例通過使用Spring事件監聽機制實現註冊核心功能和輔助業務解耦

部分核心代碼如下:

// 事件本身
public class RegisterEvent extends ApplicationEvent {

	private static final long serialVersionUID = 1L;
	// User對象爲事件源
	public RegisterEvent(User user) {
        super(user);
    }

}
-----
// 監聽用戶註冊事件,異步發送郵件
@Component
public class EmailRegisterListener implements ApplicationListener<RegisterEvent> {
	// 異步執行
    @Async
    @Override
    public void onApplicationEvent(final RegisterEvent event) {
        System.out.println("註冊成功,發送確認郵件給:" + ((User)event.getSource()).getUsername());
    }
}
------
// 監聽用戶註冊事件,索引用戶信息
@Component
public class
IndexRegisterListener implements ApplicationListener<RegisterEvent> {
    @Async
    @Override
    public void onApplicationEvent(final RegisterEvent event) {
        System.out.println("註冊成功,索引用戶信息:" + ((User)event.getSource()).getUsername());
    }
}
---------
@Service
public class RegisterService {

    @Autowired
    private ApplicationContext applicationContext;

    public void register(String username, String password) {
        System.out.println(username + "註冊成功!");
        // 發佈事件
        publishRegisterEvent(new User(username, password));
    }

    private void publishRegisterEvent(User user) {
        applicationContext.publishEvent(new RegisterEvent(user));
    }


}
--------
    
    public static void main(String[] args) {
		// 通過配置文件方式初始化上下文
    	ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config-register.xml");
    
    	RegisterService registerService = context.getBean(RegisterService.class);
    	
    	registerService.register("long", "123");
    }
-----------------------
    <context:component-scan base-package="com.sishuok"/>
    <!-- 任務調度器 -->
    <task:scheduler id="scheduler" pool-size="10"/>
    <!-- 任務執行器 -->
    <task:executor id="executor" pool-size="10"/>
    <!--開啓註解調度支持 @Async @Scheduled-->
    <task:annotation-driven executor="executor" scheduler="scheduler" proxy-target-class="true"/>

測試輸出如下:

long註冊成功!
註冊成功,發送確認郵件給:long
註冊成功,索引用戶信息:long
註冊成功,贈送遊戲大禮包給:long
註冊成功,贈送積分給:long

案例核心

  • 事件源 :User 對象
  • 事件類型 :註冊事件RegisterEvent
  • 事件監聽器 : IndexRegisterListener 、EmailRegisterListener
  • 事件發佈: …ApplicationContext 繼承了ApplicationEventPublisher,具有事件發佈的能力
  • 異步執行註解: @Async
    在這裏插入圖片描述

原理分析

事件廣播器初始化

初始化方法和調用鏈如下:
在這裏插入圖片描述

// org.springframework.context.support.AbstractApplicationContext#refresh()
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                 // 事件廣播器初始化 
                this.initApplicationEventMulticaster();
                this.onRefresh();
                // 註冊事件監聽器
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

初始化核心代碼

	public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
	
	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		// 判斷是否已加載bean:applicationEventMulticaster
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			// 已加載,applicationEventMulticaster 賦值
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			// 未加載,創建SimpleApplicationEventMulticaster對象並賦值
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			// 註冊bean
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

在這裏插入圖片描述

  • AbstractApplicationContext類圖可知,AbstractApplicationContext本身具有事件發佈的功能
  • 事件廣播器初始化過程完成了事件創博對象的創建、註冊和注入到具有事件發佈功能的AbstractApplicationContext``中。
事件對象ApplicationContextEvent分析

先說下EventObject對象,java中所有事件對象均爲EventObject子類。類聲明和註釋如下:

/**
 * <p>
 * 從中派生所有事件狀態對象的根類。
 * The root class from which all event state objects shall be derived.
 * <p>
 * All Events are constructed with a reference to the object, the "source",
 * that is logically deemed to be the object upon which the Event in question
 * initially occurred upon.
 *
 * @since JDK1.1
 */

public class EventObject implements java.io.Serializable {

ApplicationContextEvent類圖如下:
在這裏插入圖片描述

  • ApplicationContextEvent也不例外,也爲EventObject的派生類。
  • 由ApplicationContextEvent派生出四個和應用上下文相關的事件
    • ContextStartedEvent:容器啓動後觸發的事件。
    • ContextRefreshedEvent:容器初始化或者刷新完成後觸發的事件。
    • ContextStopedEvent:容器停止後觸發的事件。
    • ContextClosedEvent:容器關閉後觸發的事件。
      源碼如下:
public abstract class ApplicationContextEvent extends ApplicationEvent {

	/**
	 * Create a new ContextStartedEvent.
	 * @param source the {@code ApplicationContext} that the event is raised for
	 * (must not be {@code null})
	 */
	public ApplicationContextEvent(ApplicationContext source) { //1
		super(source);
	}

	/**
	 * Get the {@code ApplicationContext} that the event was raised for.
	 */
	public final ApplicationContext getApplicationContext() { //2
		return (ApplicationContext) getSource();
	}

}

1.構造函數,聲明事件源對象ApplicationContext。查看源碼可知,由ApplicationContextEvent 派生出的四個應用上下文事件,事件源對象也爲ApplicationContext,eg:

public class ContextClosedEvent extends ApplicationContextEvent {

	/**
	 * Creates a new ContextClosedEvent.
	 * @param source the {@code ApplicationContext} that has been closed
	 * (must not be {@code null})
	 */
	public ContextClosedEvent(ApplicationContext source) {
		super(source);
	}

}

2.獲取事件源對象方法,即獲取應用上下文對象。

註冊監聽事件

核心代碼如下

	// org.springframework.context.support.AbstractApplicationContext#registerListeners()
	protected void registerListeners() {
		// Register statically specified listeners first.
		//首先註冊靜態指定的偵聽器。  --- Spring本身的事件監聽器
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			// 註冊到時間廣播器中
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
		// 獲取ApplicationListeners實現類
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			// 註冊到時間廣播器中
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// Publish early application events now that we finally have a multicaster...
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}
事件發佈和事件監聽器執行

事件發佈調用鏈和源碼如下:
在這裏插入圖片描述

// org.springframework.context.support.AbstractApplicationContext#publishEvent(Object event, @Nullable ResolvableType eventType)
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			// 轉成spring事件
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(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.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

發佈事件方法詳情如下

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 獲取註冊到事件廣播器中監聽指定事件類型的事件集合,遍歷執行對應事件監聽器
		//type:org.springframework.context.event.ContextRefreshedEvent
		//type:com.sishuok.register.RegisterEvent
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				// taskExecutor屬性存在,異步執行監聽事件
				// eg-1:    使用@Async
				// eg-2:   
				// <bean id="applicationEventMulticaster"class="org.springframework.context.event.SimpleApplicationEventMulticaster">
		        //		<!-- 注入任務執行器 這樣就實現了異步調用(缺點是全局的,要麼全部異步,要麼全部同步(刪除這個屬性即是同步))  -->
		        //		<property name="taskExecutor" ref="executor"/>
		   		 //	</bean>
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				// 同步執行監聽事件
				invokeListener(listener, event);
			}
		}
	}
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				// 執行對應監聽事件
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}
	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			// 執行對應事件
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isTraceEnabled()) {
					logger.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

在這裏插入圖片描述

相關

參考
  • https://blog.csdn.net/zrudong/article/details/78567473
  • https://www.iteye.com/blog/jinnianshilongnian-1902886
源碼

https://github.com/hdlxt/event

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章