好玩Spring之事件機制

相信大家對事件驅動、發佈訂閱模式早有耳聞。

其主要用途可以用在

1. 用戶註冊後,給用戶發郵件或新增積分

2. 用戶添加評論後,給用戶添加積分等操作時。

雖然以上2個場景,也可以在代碼中流式的實現,但是代碼耦合性太高,不夠單一,事件通知機制便可以很好的分離以上功能的操作。

事件通知機制

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

EventObject:事件對象,它定義了事件的源對象(source),所有的java事件類都要繼承該類。

public class CourseEvent extends ApplicationEvent {
	public CourseEvent(String name) { // name即source
		super(name);
	}
}

EventListener:只是一個標記接口,所有事件監聽器都需要繼承或實現它。

public class CourseListener implements EventListener {
	public void handleEvent(CourseEvent event) {
		System.out.println("開始學習可課程:"+event.getSource());
	}
}

Source:即EventObject中的源對象,事件發生的地方(即被觀察者)。

public class CourseManager {
	List<CourseListener> subs = new ArrayList<CourseListener>();

	public void publishCourse(String name) {
		notifyListener(new CourseEvent(name));
	}

	public void addListener(CourseListener listener) {
		subs.add(listener);
	}

	public void notifyListener(CourseEvent courseEvent) {
		for (CourseListener sub : subs) {
			sub.handleEvent(courseEvent);
		}
	}

	public static void main(String[] args) {
		CourseManager manager = new CourseManager();
		// 張三訂閱課程
		manager.addListener(new CourseListener() {
			@Override
			public void handleEvent(CourseEvent event) {
				System.out.println("zhangsan study course: "+event.getSource());
			}
		});

		// 李四訂閱課程
		manager.addListener(new CourseListener() {
			@Override
			public void handleEvent(CourseEvent event) {
				System.out.println("lisi study course: "+event.getSource());
			}
		});

		CourseEvent courseEvent = new CourseEvent("English");
		manager.notifyListener(courseEvent);
	}
}

結果輸出:

zhangsan study course: English
lisi study course: English

以上是JDK版的事件機制。

Spring的事件機制

spring的事件機制也包括三部分:

ApplicationListener:事件監聽器,繼承自EventListener

@Component
public class CourseListener implements ApplicationListener<eventlistener.chapter5.CourseEvent> {

	@Override
	public void onApplicationEvent(eventlistener.chapter5.CourseEvent event) {
		System.out.println("開始學習可課程:"+event.getSource());
	}
}

ApplicationEvent:spring事件對象,繼承自EventObject

public class CourseEvent extends ApplicationEvent {
	public CourseEvent(String name) { // name即source
		super(name);
	}
}

ApplicationEventPublisher:事件發佈。

@Component
public class CourseManager implements ApplicationEventPublisherAware {

	public void publishCourse(String name) {
		applicationEventPublisher.publishEvent(new CourseEvent(name));
	}

	private ApplicationEventPublisher applicationEventPublisher;

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

測試類:

public class MainTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		context.getBean(CourseManager.class).publishCourse("English");
	}
}

結果輸出:

開始學習課程:English

與JDK的事件機制還是一脈相承的哈~~

事件通知的執行過程

通過代碼跟蹤,我們知道,當執行AbstractApplicationContext的refresh()方法時,會執行registerListeners()方法,

protected void registerListeners() {
		// Register statically specified listeners first.
		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!
		// 獲取類型是ApplicationListener的beanName集合,此處不會去實例化bean
		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);
			}
		}
	}

這裏找出所有的ApplicationListener,註冊到applicationEventMulticaster中。

收集到了listener,按照事件通知的3個部分,那什麼時候publish呢?我們繼續往下看,在refresh()方法的最後一步,到了finishRefresh()方法,這個方法裏有publishEvent(new ContextRefreshedEvent(this));這麼一句,繼續跟蹤

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) {
			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 {
            // 從caster中取出對應的listener,並publish
			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);
			}
		}
	}

進入multicastEvent(applicationEvent, eventType)方法,

@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

可以看到這裏分爲同步invokeListener(listener, event)和異步2種方式,我們先看同步方式,經過跟蹤,可以找到

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.isDebugEnabled()) {
					logger.debug("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

終於看到了我們想要的事件發佈,至此,Spring的通知機制算是完成了。

異步事件通知

前面提到invokeListener(listener, event)的執行有同步和異步2種方式,默認情況下是同步的,那如何實現異步執行呢,畢竟很多場景下采用異步是更好的(比如發郵件等,我們只需要知道執行的結果便可)。

異步事件通知,可以有2種方式:

1. 自定義SimpleApplicationEventMulticaster

通過上面的代碼分析,可以知道,只要Executor executor = getTaskExecutor();的executor不爲null,便可以異步執行,那我們要重寫下SimpleApplicationEventMulticaster,進行setTaskExecutor()設置executor即可。

修改下AppConfig類,這樣便是在新起的線程中執行event:

@Configuration
@ComponentScan
public class AppConfig {

	@Bean
	public SimpleApplicationEventMulticaster applicationEventMulticaster() {
		SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
		simpleApplicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
		return simpleApplicationEventMulticaster;
	}
}

但這種方法有個缺點,重寫的SimpleApplicationEventMulticaster會作用於所有的listener,如果我只想讓部分listener異步執行,這種方式就行不通了

2. @Async

修改AppConfig類(加上@EnableAsync)

@Configuration
@ComponentScan
@EnableAsync
public class AppConfig {

	@Bean
	public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor() {
		return new SimpleAsyncTaskExecutor();
	}

}

並在listener的onApplicationEvent方法上加上@Async註釋。

@Component
public class MyListener implements ApplicationListener {
	@Async
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		System.out.println("我訂閱了以下事件:");
		System.out.println("Current Thread name: " + Thread.currentThread().getName());
		System.out.println(event);
	}
}

這樣,就實現了只是這一個事件是異步執行了。

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