(一)理論通識:
我們知道spring事件是基于事件/監聽器編程模型,在這個模型中,有幾個重要的角色,事件(ApplicationEvent),應用事件監聽器(ApplicationListener),以及事件發布者(ApplicationContext)。
(二)需求描述
用戶注冊成功之后,發送短信告知用戶。
(三)代碼實現
我們先看下目錄結構先看MyRegisApplicationEvent類,他是繼承ApplicationEvent類:
package com.wangming.event;
import org.springframework.context.ApplicationEvent;
public class MyRegisApplicationEvent extends ApplicationEvent {
public MyRegisApplicationEvent(Object source) {
super(source);
}
}
MyRegisApplicationListener類是實現ApplicationListener接口,注意泛型為MyRegisApplicationEvent類。其中@Order注解表示這個監聽器在容器中的順序,比如我們可以注冊很多監聽,有時候業務需要先執行某個后執行某個,這樣就需要用到排序了:
package com.wangming.listener;
import com.wangming.event.MyRegisApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
@Order(Ordered.LOWEST_PRECEDENCE+1)
public class MyRegisApplicationListener implements ApplicationListener<MyRegisApplicationEvent> {
@Override
public void onApplicationEvent(MyRegisApplicationEvent event) {
System.out.println("MyRegisApplicationListener:name:"+event.getClass().getName());
System.out.println("MyRegisApplicationListener:getSource:"+event.getSource());
System.out.println("發送短信告知用戶注冊成功!");
}
}
ApplicationListenerBootstrap引導啟動類:
package com.wangming.bootstrap;
import com.wangming.listener.MyRegisApplicationListener;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.wangming")
@EnableScheduling
public class ApplicationListenerBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext run =
new SpringApplicationBuilder(ApplicationListenerBootstrap.class)
// .web(WebApplicationType.NONE)
.run(args);
//此處我們可以使用之前的spring工廠加載機制,在resource目錄下建立resources/META-INF/spring.factories文件,并在文件中如此配置
//# ApplicationListener 實現配置
//#org.springframework.context.ApplicationListener=\
//#com.wangming.listener.MyRegisApplicationListenertener
run.addApplicationListener(new MyRegisApplicationListener());
}
}
最后寫一個注冊接口RegisController,通過裝配的ApplicationContext 進行事件發布:
package com.wangming.controller;
import com.wangming.event.MyRegisApplicationEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RegisController {
@Autowired
ApplicationContext context;
@GetMapping("/hello")
public String regis() {
//....
context.publishEvent(new MyRegisApplicationEvent("注冊成功"));
return "ok";
}
}
讓我們調用一下注冊接口,看看控制臺:發現確實如我們預期那樣。
(四)源碼解讀
我們以context.publishEvent(new MyRegisApplicationEvent("注冊成功"));這句代碼為契機點,往下走:
AbstractApplicationContext類中關鍵代碼:
...
/**
* Publish the given event to all listeners.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @since 4.2
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// 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 {
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);
}
}
}
...
其中這行代碼 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);非常值得我們關注,ApplicationEventMulticaster這個類是我們所說的廣播器,在spring事件中起到了非常關鍵的作用,發布者發布的事件會發送給EventMultiCaster, 而后由EventMultiCaster注冊著所有的Listener,然后根據事件類型決定轉發給那個Listener。
Spring 應用事廣播器的默認實現類為SimpleApplicationEventMulticaster,我們繼續往下走就會進入SimpleApplicationEventMulticaster類中,然我們瞅瞅關鍵代碼:
...
@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);
}
}
}
...
/**
* Invoke the given listener with the given event.
* @param listener the ApplicationListener to invoke
* @param event the current event to propagate
* @since 4.1
*/
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);
}
}
...
@SuppressWarnings({"unchecked", "rawtypes"})
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;
}
}
}
...
最終我們看到了真正回調的地方 listener.onApplicationEvent(event);。
這里說一個題外話,我們在SimpleApplicationEventMulticaster廣播器類中發現這樣一段代碼:
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
說明我們監聽器中的業務代碼是可以異步執行的。我們怎么實現呢?很簡單,做三處修改,1在引導啟動類上加上@EnableAsync,2 注釋掉這行代碼run.addApplicationListener(new MyRegisApplicationListener());
package com.wangming.bootstrap;
import com.wangming.listener.MyRegisApplicationListener;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.wangming")
@EnableScheduling
@EnableAsync
public class ApplicationListenerBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext run =
new SpringApplicationBuilder(ApplicationListenerBootstrap.class)
// .web(WebApplicationType.NONE)
.run(args);
// run.addApplicationListener(new MyRegisApplicationListener());
}
}
第三就是在MyRegisApplicationListener監聽類上打上
@Component
@Async這兩個注解
剛剛之所以屏蔽掉run.addApplicationListener(new MyRegisApplicationListener());因為@Component注解已經幫我們注入了,如果不屏蔽會有兩次回調。