Spring Boot 自定义事件及监听
事件及监听并不是 SpringBoot 的新功能,Spring 框架早已提供了完善的事件监听机制,在 Spring 框架中实现事件监听的流程如下:
-
自定义事件,继承 org.springframework.context.ApplicationEvent 抽象类
-
定义事件监听器,实现 org.springframework.context.ApplicationListener 接口
-
在 Spring 容器中发布事件
实现自定义事件及监听
- 定义事件
1 //自定义事件 2 public class ApplicationEventTest extends ApplicationEvent { 3 4 public ApplicationEventTest(Object source) { 5 super(source); 6 } 7 8 /** 9 * 事件处理事项 10 * @param msg 11 */ 12 public void printMsg(String msg) 13 { 14 System.out.println("监听到事件:"+ApplicationEventTest.class); 15 } 16 }
- 定义监听器
1 //自定义事件监听器 2 //@Component 3 public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> { 4 5 @Override 6 public void onApplicationEvent(ApplicationEventTest event) { 7 8 event.printMsg(null); 9 } 10 }
- 在 Spring 容器中发布事件
1 public static void main(String[] args) { 2 3 SpringApplication application = new SpringApplication(SpringbootdemoApplication.class); 4 //需要把监听器加入到 spring 容器中 5 application.addListeners(new ApplicationListenerTest()); 6 Set<ApplicationListener<?>> listeners = application.getListeners(); 7 ConfigurableApplicationContext context = application.run(args); 8 //发布事件 9 context.publishEvent(new ApplicationEventTest(new Object())); 10 11 context.close(); 12 }
上面的示例是在 SpringBoot 应用中简单的测试一下。
实际开发中实现监听还有其他的方式,在 Spring 框架中提供了两种事件监听的方式:
-
编程式:通过实现 ApplicationListener 接口来监听指定类型的事件
-
注解式:通过在方法上加 @EventListener 注解的方式监听指定参数类型的事件,写该类需要托管到 Spring 容器中
在 SpringBoot 应用中还可以通过配置的方式实现监听:
3. 通过 application.properties 中配置 context.listener.classes 属性指定监听器
下面分别分析一下这三种监听方式
编程式实现监听
实现 ApplicationListenser 接口:
1 @Component 2 public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> { 3 4 @Override 5 public void onApplicationEvent(ApplicationEventTest event) { 6 7 event.printMsg(null); 8 } 9 }
控制台输出测试:
1 public static void main(String[] args) { 2 3 SpringApplication application = new SpringApplication(SpringbootdemoApplication.class); 4 //需要把监听器加入到 spring 容器中 5 //application.addListeners(new ApplicationListenerTest()); 6 //Set<ApplicationListener<?>> listeners = application.getListeners(); 7 8 ConfigurableApplicationContext context = application.run(args); 9 //发布事件 10 context.publishEvent(new ApplicationEventTest(new Object())); 11 }
那么我们跟踪一下源码,看一下事件是如何发布出去的,又是如何被监听到的呢?
AbstractApplicationContext.java 中截取部分代码
1 protected void publishEvent(Object event, @Nullable ResolvableType eventType) { 2 Assert.notNull(event, "Event must not be null"); 3 if (logger.isTraceEnabled()) { 4 logger.trace("Publishing event in" + getDisplayName() + ":" + event); 5 } 6 7 // Decorate event as an ApplicationEvent if necessary 8 /将 object 转成 ApplicationEvent 9 ApplicationEvent applicationEvent; 10 if (event instanceof ApplicationEvent) { 11 applicationEvent = (ApplicationEvent) event; 12 } 13 else { 14 applicationEvent = new PayloadApplicationEvent<>(this, event); 15 if (eventType == null) { 16 eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType(); 17 } 18 } 19 20 // Multicast right now if possible - or lazily once the multicaster is initialized 22 if (this.earlyApplicationEvents != null) { 23 this.earlyApplicationEvents.add(applicationEvent); 24 } 25 else { 26 // SimpleApplicationEventMulticaster 获取事件发布器,发布事件 27 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 28 } 29 30 // Publish event via parent context as well... 31 if (this.parent != null) { 32 if (this.parent instanceof AbstractApplicationContext) { 33 ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); 34 } 35 else { 36 this.parent.publishEvent(event); 37 } 38 } 39 }
查看一下 ApplicationContext 类结构图可以发现:应用上下文 AbstractApplicationContext 实际还是通过继承 ApplicationEventPublisher 接口,实现了其中的事件发布的方法,使得 Spring 应用上下文有了发布事件的功能,在 AbstractApplicationContext 内部通过 SimpleApplicationEventMulticaster 事件发布类,将具体事件 ApplicationEvent 发布出去。
那么事件发布出去后又是如何被监听到的呢?下面看一下具 Spring 中负责处理事件发布类 SimpleApplicationEventMulticaster 中 multicastEvent 方法具体实现过程
SimpleApplicationEventMulticaster.java 部分代码,实际尝试将当前事件逐个广播到指定类型的监听器中(listeners 已经根据当前事件类型过滤了)
1 @Override 2 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { 3 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); 4 // getApplicationListeners(event, type) 筛选监听器,在 context.publish(ApplicationEvent event) 中已经将事件传入,getApplicationListeners 中将可以根据这个 event 类型从 Spring 容器中检索出符合条件的监听器 5 6 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { 7 Executor executor = getTaskExecutor(); 8 if (executor != null) { 9 executor.execute(() -> invokeListener(listener, event)); 10 } 11 else { 12 //尝试逐个向监听器广播 13 invokeListener(listener, event); 14 } 15 } 16 }
@EventListener 注解方式实现
定义注解方法
@Component public class MyEventHandleTest {</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 参数为Object类型时,所有事件都会监听到 * 参数为指定类型事件时,该参数类型事件或者其子事件(子类)都可以接收到 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @EventListener </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> event(ApplicationEventTest event){ event.printMsg(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); }
}
实现过程分析:
@EventListener 注解主要通过 EventListenerMethodProcessor 扫描出所有带有 @EventListener 注解的方法,然后动态构造事件监听器,并将监听器托管到 Spring 应用上文中。
1 protected void processBean( 2 final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) { 3 4 if (!this.nonAnnotatedClasses.contains(targetType)) { 5 Map<Method, EventListener> annotatedMethods = null; 6 try { 7 //查找含有 @EventListener 注解的所有方法 8 annotatedMethods = MethodIntrospector.selectMethods(targetType, 9 (MethodIntrospector.MetadataLookup<EventListener>) method -> 10 AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class)); 11 } 12 catch (Throwable ex) { 13 // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it. 14 if (logger.isDebugEnabled()) { 15 logger.debug("Could not resolve methods for bean with name'"+ beanName +"'", ex); 16 } 17 } 18 if (CollectionUtils.isEmpty(annotatedMethods)) { 19 this.nonAnnotatedClasses.add(targetType); 20 if (logger.isTraceEnabled()) { 21 logger.trace("No @EventListener annotations found on bean class:" + targetType.getName()); 22 } 23 } 24 else { 25 // Non-empty set of methods 26 ConfigurableApplicationContext context = getApplicationContext(); 27 //遍历含有 @EventListener 注解的方法 28 for (Method method : annotatedMethods.keySet()) { 29 for (EventListenerFactory factory : factories) { 30 if (factory.supportsMethod(method)) { 31 Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName)); 32 //动态构造相对应的事件监听器 33 ApplicationListener<?> applicationListener = 34 factory.createApplicationListener(beanName, targetType, methodToUse); 35 if (applicationListener instanceof ApplicationListenerMethodAdapter) { 36 ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator); 37 } 38 //将监听器添加的 Spring 应用上下文中托管 39 context.addApplicationListener(applicationListener); 40 break; 41 } 42 } 43 } 44 if (logger.isDebugEnabled()) { 45 logger.debug(annotatedMethods.size() + "@EventListener methods processed on bean'" + 46 beanName + "':" + annotatedMethods); 47 } 48 } 49 } 50 }
在 application.properties 中配置 context.listener.classes
添加如下配置:
context.listener.classes=com.sl.springbootdemo.Listeners.ApplicationListenerTest
查看一下 DelegatingApplicationListener 类中实现逻辑:
1 public class DelegatingApplicationListener 2 implements ApplicationListener<ApplicationEvent>, Ordered { 3 4 private static final String PROPERTY_NAME = "context.listener.classes"; 5 6 private int order = 0; 7 //Spring framework 提供的负责处理发布事件的类,前面说的 Spring 应用上下文中也是通过这个类发布事件的 8 private SimpleApplicationEventMulticaster multicaster; 9 10 @Override 11 public void onApplicationEvent(ApplicationEvent event) { 12 if (event instanceof ApplicationEnvironmentPreparedEvent) { 13 // getListeners 内部实现读取 context.listener.classes 配置的监听器 14 List<ApplicationListener<ApplicationEvent>> delegates = getListeners( 15 ((ApplicationEnvironmentPreparedEvent) event).getEnvironment()); 16 if (delegates.isEmpty()) { 17 return; 18 } 19 this.multicaster = new SimpleApplicationEventMulticaster(); 20 for (ApplicationListener<ApplicationEvent> listener : delegates) { 21 this.multicaster.addApplicationListener(listener); 22 } 23 } 24 //发布事件 25 if (this.multicaster != null) { 26 this.multicaster.multicastEvent(event); 27 } 28 }
Spring-boot-{version}.jar 包中提供一个类 DelegatingApplicationListener,该类的作用是从 application.properties 中读取配置 context.listener.classes,并将事件广播给这些配置的监听器。通过前面一章对 SpringBoot 启动流程分析,我们已经了解到 SpringBoot 启动时会从 META-INF/spring.factories 中读取 key 为 org.springframework.context.ApplicationListener 的所有监听器。DelegatingApplicationListener 的功能可以让我们不需要创建 META-INF/spring.factories,直接在 application.properties 中配置即可。