spring event事件通知机制

spring容器提供一种事件发布通知机制用来实现简单的消息通信,达到一定的业务解耦,开发人员可以使用Spring事件框架来定义和处理各种事件,是一种观察者模式的实现。

事件定义

要发布事件首先要定义事件,我们可以通过集成ApplicationEvent类来定义事件

java 复制代码
public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

需要调用ApplicationEvent类构造函数来设置消息内容。

事件发布

事件的发布我们可以通过实现ApplicationEventPublisherAware接口,重写其setApplicationEventPublisher方法获取事件发布器。然后调用发布器的publishEvent方法发布事件

如下发布上面自定义的MyEvent事件

java 复制代码
@Service
@Slf4j
public class EventService implements ApplicationEventPublisherAware {

    //@Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    //从写setApplicationEventPublisher 获取发布器
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publish(String msg){
        log.info("开始发送事件");
        MyEvent event = new MyEvent(msg);
        /**
         * 默认事件的发送是同步处理
         * 所以要在事件处理里控制好异常,否则会影响当前发布方法
         */
        applicationEventPublisher.publishEvent(event);
        log.info("结束事件处理");
    }
}

这里setApplicationEventPublisher方法传入的publisher实际上就是context上下文,因为context实现了ApplicationEventPublisher接口。实现了ApplicationEventPublisherAware接口的bean会在ApplicationContextAwareProcessor后置处理器的before方法处调用其接口方法,完成回调赋值。

除了通过实现接口的方式,也可以直接使用@Autowired注解来注入事件发布器。

发布器的publishEvent(event)方法默认是同步方式进行调用的,所以在事件处理方法里一定要处理好异常,以免影响我们主业务逻辑。

除了同步方式调用,还可以配置为异步方式

如下自定义@Bean(name = "applicationEventMulticaster"),设置其taskExecutor即可。

java 复制代码
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
    SimpleApplicationEventMulticaster eventMulticaster =
            new SimpleApplicationEventMulticaster();

    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    return eventMulticaster;
}

具体原理在后面源码分析解读。

事件处理

定义消息处理器可以通过实现ApplicationListener接口来完成,该接口支持泛型指定一个具体类型消息E extends ApplicationEvent。当有匹配消息是,其onApplicationEvent方法会被调用。

如下定义处理MyEvent类型消息:

java 复制代码
@Component
@Slf4j
@Order(1)
public class MyEventListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        log.info("receive event:"+event);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用@EventListener注解也可以定义事件监听器
     * @param event
     */
    @EventListener
    @Order(2)
    public void annotationListener(MyEvent event){
        log.info("注解接收事件:"+event);
    }
}

这里看到除了实现ApplicationListener接口外,还可以使用@EventListener注解的方式指定处理其。

如果定义了多个消息处理器,还可以使用 @Order注解来指定调用顺序,小的先执行。

源码分析

spring容器在初始化的过程中,有几步是初始化消息相关操作。来看容器重要初始化方法refresh里几个相关的操作

java 复制代码
//1、初始化事件处理器
initApplicationEventMulticaster();
//2、初始化事件监听器
registerListeners();
//3、发布相关事件
finishRefresh();

1、初始化事件处理器会往beanFacotry添加一个SimpleApplicationEventMulticaster

java 复制代码
protected void initApplicationEventMulticaster() {
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   //这里常量APPLICATION_EVENT_MULTICASTER_BEAN_NAME的值是applicationEventMulticaster
   if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
      this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
   }
   else {
      this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
      beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
   }
}

事件发布时候会用到该类进行发布事件。看到这里很容易理解上面自定义applicationEventMulticaster使用异步方式处理消息的配置为什么可行。

2、初始化listener会获取配置的listener

java 复制代码
protected void registerListeners() {
   // Register statically specified listeners first.
   for (ApplicationListener<?> listener : getApplicationListeners()) {
      getApplicationEventMulticaster().addApplicationListener(listener);
   }

   //获取定义的ApplicationListener类型的bean
   String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
   for (String listenerBeanName : listenerBeanNames) {
     //将listner存到EventMulticaster里
      getApplicationEventMulticaster里().addApplicationListenerBean(listenerBeanName);
   }
  //...
}

所以我们只要实现ApplicationListener接口,实现其onApplicationEvent方法,当一个事件发布时候,就可以接收到该事件。也可以通过@EventListener注解定义。这里会将所有的消息listeners存放到EventMulticaster里。

java 复制代码
AbstractApplicationEventMulticaster{
  DefaultListenerRetriever {
     Set<ApplicationListener<?>> applicationListeners;
  }
}

addListener最后会存放到applicationListeners变量里。

3、在第三步内部发布相关refresh完成的事件里会调到publishEvent(new ContextRefreshedEvent(this))方法发布一个事件。最后会调到getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);进行事件广播。这里getApplicationEventMulticaster就是上面第一步初始化的SimpleApplicationEventMulticaster。

multicastEvent广播事件方法

java 复制代码
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   Executor executor = getTaskExecutor();//获取执行器
   //获取合适的listeners 广播事件
   for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      if (executor != null) {//有执行器就异步执行
         executor.execute(() -> invokeListener(listener, event));
      }
      else {//同步执行
         invokeListener(listener, event);
      }
   }
}

这里的Executor属性可以通过BeanPostProcessor后置拦截器进行赋值。或者根据第1步multicaster的初始化,会先从factory里查找有没有APPLICATION_EVENT_MULTICASTER_BEAN_NAME类型的bean,没有才new一个。所以我们可以显示的声明一个ApplicationEventMulticaster然后设置exexutor属性。

getApplicationListeners会根据消息类型进行listener的过滤

invokeListener就是调用listener.onApplicationEvent(event)方法。

相关推荐
ProtonBase4 分钟前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v10 分钟前
leetCode43.字符串相乘
java·数据结构·算法
suweijie7683 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿4 小时前
List深拷贝后,数据还是被串改
java
xlsw_7 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹8 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭9 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫9 小时前
泛型(2)
java
超爱吃士力架9 小时前
邀请逻辑
java·linux·后端
南宫生9 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论