Java SpringBoot
事件及监听并不是 SpringBoot 的新功能,Spring 框架早已提供了完善的事件监听机制,在 Spring 框架中实现事件监听的流程如下:

  1. 自定义事件,继承 org.springframework.context.ApplicationEvent 抽象类
  2. 定义事件监听器,实现 org.springframework.context.ApplicationListener 接口
  3. 在 Spring 容器中发布事件

    实现自定义事件及监听

    定义事件

    1. //自定义事件
    2. public class ApplicationEventTest extends ApplicationEvent {
    3. public ApplicationEventTest(Object source) {
    4. super(source);
    5. }
    6. /**
    7. * 事件处理事项
    8. * @param msg
    9. */
    10. public void printMsg(String msg)
    11. {
    12. System.out.println("监听到事件:"+ApplicationEventTest.class);
    13. }
    14. }

    定义监听器

    1. //自定义事件监听器
    2. //@Component
    3. public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> {
    4. @Override
    5. public void onApplicationEvent(ApplicationEventTest event) {
    6. event.printMsg(null);
    7. }
    8. }

    在Spring容器中发布事件

    1. public static void main(String[] args) {
    2. SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
    3. //需要把监听器加入到spring容器中
    4. application.addListeners(new ApplicationListenerTest());
    5. Set<ApplicationListener<?>> listeners = application.getListeners();
    6. ConfigurableApplicationContext context = application.run(args);
    7. //发布事件
    8. context.publishEvent(new ApplicationEventTest(new Object()));
    9. context.close();
    10. }

    上面的示例是在 SpringBoot 应用中简单的测试一下。
    实际开发中实现监听还有其他的方式,在 Spring 框架中提供了两种事件监听的方式:

  4. 编程式:通过实现 ApplicationListener 接口来监听指定类型的事件

  5. 注解式:通过在方法上加 @EventListener 注解的方式监听指定参数类型的事件,写该类需要托管到 Spring 容器中

在 SpringBoot 应用中还可以通过配置的方式实现监听:
3. 通过 application.properties 中配置 context.listener.classes 属性指定监听器
下面分别分析一下这三种监听方式

编程式实现监听

实现 ApplicationListenser 接口:

  1. @Component
  2. public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> {
  3. @Override
  4. public void onApplicationEvent(ApplicationEventTest event) {
  5. event.printMsg(null);
  6. }
  7. }

控制台输出测试:

  1. public static void main(String[] args) {
  2. SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
  3. //需要把监听器加入到spring容器中
  4. //application.addListeners(new ApplicationListenerTest());
  5. //Set<ApplicationListener<?>> listeners = application.getListeners();
  6. ConfigurableApplicationContext context = application.run(args);
  7. //发布事件
  8. context.publishEvent(new ApplicationEventTest(new Object()));
  9. }

那么跟踪一下源码,看一下事件是如何发布出去的,又是如何被监听到的呢?
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. // Decorate event as an ApplicationEvent if necessary
  7. /将object转成ApplicationEvent
  8. ApplicationEvent applicationEvent;
  9. if (event instanceof ApplicationEvent) {
  10. applicationEvent = (ApplicationEvent) event;
  11. }
  12. else {
  13. applicationEvent = new PayloadApplicationEvent<>(this, event);
  14. if (eventType == null) {
  15. eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
  16. }
  17. }
  18. // Multicast right now if possible - or lazily once the multicaster is initialized
  19. if (this.earlyApplicationEvents != null) {
  20. this.earlyApplicationEvents.add(applicationEvent);
  21. }
  22. else {
  23. // SimpleApplicationEventMulticaster 获取事件发布器,发布事件
  24. getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
  25. }
  26. // Publish event via parent context as well...
  27. if (this.parent != null) {
  28. if (this.parent instanceof AbstractApplicationContext) {
  29. ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
  30. }
  31. else {
  32. this.parent.publishEvent(event);
  33. }
  34. }
  35. }

查看一下 ApplicationContext 类结构图可以发现:应用上下文 AbstractApplicationContext 实际还是通过继承 ApplicationEventPublisher 接口,实现了其中的事件发布的方法,使得 Spring 应用上下文有了发布事件的功能,在 AbstractApplicationContext 内部通过 SimpleApplicationEventMulticaster 事件发布类,将具体事件 ApplicationEvent 发布出去。
Spring Boot 自定义事件及监听 - 图1
那么事件发布出去后又是如何被监听到的呢?下面看一下具 Spring 中负责处理事件发布类 SimpleApplicationEventMulticastermulticastEvent 方法具体实现过程
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. for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  6. Executor executor = getTaskExecutor();
  7. if (executor != null) {
  8. executor.execute(() -> invokeListener(listener, event));
  9. }
  10. else {
  11. //尝试逐个向监听器广播
  12. invokeListener(listener, event);
  13. }
  14. }
  15. }

Spring Boot 自定义事件及监听 - 图2

@EventListener 注解方式实现

定义注解方法

  1. @Component
  2. public class MyEventHandleTest {
  3. /**
  4. * 参数为Object类型时,所有事件都会监听到
  5. * 参数为指定类型事件时,该参数类型事件或者其子事件(子类)都可以接收到
  6. */
  7. @EventListener
  8. public void event(ApplicationEventTest event){
  9. event.printMsg(null);
  10. }
  11. }

实现过程分析:
@EventListener 注解主要通过 EventListenerMethodProcessor 扫描出所有带有 @EventListener 注解的方法,然后动态构造事件监听器,并将监听器托管到 Spring 应用上文中。

  1. protected void processBean(
  2. final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
  3. if (!this.nonAnnotatedClasses.contains(targetType)) {
  4. Map<Method, EventListener> annotatedMethods = null;
  5. try {
  6. //查找含有@EventListener注解的所有方法
  7. annotatedMethods = MethodIntrospector.selectMethods(targetType,
  8. (MethodIntrospector.MetadataLookup<EventListener>) method ->
  9. AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
  10. }
  11. catch (Throwable ex) {
  12. // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
  13. if (logger.isDebugEnabled()) {
  14. logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
  15. }
  16. }
  17. if (CollectionUtils.isEmpty(annotatedMethods)) {
  18. this.nonAnnotatedClasses.add(targetType);
  19. if (logger.isTraceEnabled()) {
  20. logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
  21. }
  22. }
  23. else {
  24. // Non-empty set of methods
  25. ConfigurableApplicationContext context = getApplicationContext();
  26. //遍历含有@EventListener注解的方法
  27. for (Method method : annotatedMethods.keySet()) {
  28. for (EventListenerFactory factory : factories) {
  29. if (factory.supportsMethod(method)) {
  30. Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
  31.       //动态构造相对应的事件监听器
  32. ApplicationListener<?> applicationListener =
  33. factory.createApplicationListener(beanName, targetType, methodToUse);
  34. if (applicationListener instanceof ApplicationListenerMethodAdapter) {
  35. ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
  36. }
  37.       //将监听器添加的Spring应用上下文中托管
  38. context.addApplicationListener(applicationListener);
  39. break;
  40. }
  41. }
  42. }
  43. if (logger.isDebugEnabled()) {
  44. logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
  45. beanName + "': " + annotatedMethods);
  46. }
  47. }
  48. }
  49. }

在 application.properties 中配置 context.listener.classes

添加如下配置:

  1. context.listener.classes=com.sl.springbootdemo.Listeners.ApplicationListenerTest

查看一下 DelegatingApplicationListener 类中实现逻辑:

  1. public class DelegatingApplicationListener
  2. implements ApplicationListener<ApplicationEvent>, Ordered {
  3. private static final String PROPERTY_NAME = "context.listener.classes";
  4. private int order = 0;
  5. //Spring framework提供的负责处理发布事件的类,前面说的Spring应用上下文中也是通过这个类发布事件的
  6. private SimpleApplicationEventMulticaster multicaster;
  7. @Override
  8. public void onApplicationEvent(ApplicationEvent event) {
  9. if (event instanceof ApplicationEnvironmentPreparedEvent) {
  10. // getListeners内部实现读取context.listener.classes配置的监听器
  11. List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
  12. ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
  13. if (delegates.isEmpty()) {
  14. return;
  15. }
  16. this.multicaster = new SimpleApplicationEventMulticaster();
  17. for (ApplicationListener<ApplicationEvent> listener : delegates) {
  18. this.multicaster.addApplicationListener(listener);
  19. }
  20. }
  21. //发布事件
  22. if (this.multicaster != null) {
  23. this.multicaster.multicastEvent(event);
  24. }
  25. }
  26. }

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 中配置即可。