相信大家對事件驅動、發佈訂閱模式早有耳聞。
其主要用途可以用在
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);
}
}
這樣,就實現了只是這一個事件是異步執行了。