Spring事件ApplicationEvent源码浅读

文章目录

ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现了 ApplicationListener 接口的 bean 部署到容器中,则每次将 ApplicationEvent 发布到ApplicationContext 时,都会通知到该 bean,这简直是典型的观察者模式。设计的初衷就是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

Spring 中提供了以下的事件

Event 描述
ContextRefreshedEvent ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生
ContextStartedEvent 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序
ContextStoppedEvent 当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作
ContextClosedEvent 使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启
RequestHandledEvent 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务
ServletRequestHandledEvent RequestHandledEvent的一个子类,用于添加特定于Servlet的上下文信息。

demo应用

具体的详情可以访问:https://cuizb.top/myblog/static/resource/Untitled-1697189238408.png

这里只是个demo例子

实现

1、 自定义事件类,基于ApplicationEvent实现扩展;

java 复制代码
public class DemoEvent extends ApplicationEvent {
    private static final long serialVersionUID = -2753705718295396328L;
    private String msg;

    public DemoEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

2、 定义Listener类,实现ApplicationListener接口,并且注入到IOC中等发布者发布事件时,都会通知到这个bean,从而达到监听的效果;

java 复制代码
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        String msg = demoEvent.getMsg();
        System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    }
}

3、 要发布上述自定义的event,需要调用ApplicationEventPublisher的publishEvent方法,我们可以定义一个实现ApplicationEventPublisherAware的类,并注入IOC来进行调用;

java 复制代码
@Component
public class DemoPublisher implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void sendMsg(String msg) {
        applicationEventPublisher.publishEvent(new DemoEvent(this, msg));
    }
}

4、 客户端调用publisher;

java 复制代码
@RestController
@RequestMapping("/event")
public class DemoClient implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @GetMapping("/publish")
    public void publish(){
        DemoPublisher bean = applicationContext.getBean(DemoPublisher.class);
        bean.sendMsg("发布者发送消息......");
    }
}

输出结果:

bean-listener 收到了 publisher 发布的消息: 发布者发送消息......

基于注解

我们可以不用实现 AppplicationListener 接口 ,在方法上使用 @EventListener 注册事件。如果你的方法应该侦听多个事件,并不使用任何参数来定义,可以在 @EventListener 注解上指定多个事件。

重写DemoListener 类如下:

java 复制代码
public class DemoListener {
    @EventListener(value = {DemoEvent.class, TestEvent.class})
    public void processApplicationEvent(DemoEvent event) {
        String msg = event.getMsg();
        System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    }
}

事件过滤

如果希望通过一定的条件对事件进行过滤,可以使用 @EventListener 的 condition 属性。以下实例中只有 event 的 msg 属性是 my-event 时才会进行调用。

java 复制代码
@EventListener(value = {DemoEvent.class, TestEvent.class}, condition = "#event.msg == 'my-event'")
public void processApplicationEvent(DemoEvent event) {
     String msg = event.getMsg();
     System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
 }

此时,发送符合条件的消息,listener 才会侦听到 publisher 发布的消息。

bean-listener 收到了 publisher 发布的消息: my-event

异步事件监听

前面提到的都是同步处理事件,那如果我们希望某个特定的侦听器异步去处理事件,如何做?

使用@Async 注解可以实现类内方法的异步调用,这样方法在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

java 复制代码
@EventListener
@Async
public void processApplicationEvent(DemoEvent event) {
    String msg = event.getMsg();
    System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
}

使用异步监听时,有两点需要注意:

  • 如果异步事件抛出异常,则不会将其传播到调用方。
  • 异步事件监听方法无法通过返回值来发布后续事件,如果需要作为处理结果发布另一个事件,请插入 ApplicationEventPublisher 以手动发布事件

源码解读

ApplicationEvent事件机制流程:

  1. ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的pulishEvent方法发布事件;
  2. ApplicationEventMulticaster就是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的它通过父类AbstractApplicationEventMulticastergetApplicationListeners方法从事件注册表(事件-监听器关系保存)中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑;
  3. ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口,本图中列出了典型的几个子类其中RestartApplicationListnenerSpringBoot的启动框架中就有使用;
  4. 在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法;

通过上图就能较清晰的知道当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。

来到ApplicationEventPublisher 的 publishEvent 方法内部

java 复制代码
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
 if (this.earlyApplicationEvents != null) {
	 this.earlyApplicationEvents.add(applicationEvent);
 }
 else {
	  getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
 }
}

多播事件multicastEvent方法

java 复制代码
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 // 注意
 Executor executor = getTaskExecutor();
 // 遍历所有的监听者
 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  if (executor != null) {
   // 异步调用监听器
   executor.execute(() -> invokeListener(listener, event));
  }
  else {
   // 同步调用监听器
   invokeListener(listener, event);
  }
 }
}

在准备执行监听者方法时,会先获取容器中是否有默认的异步线程池,如果在容器启动时,声明了一个异步线程池,getTaskExecutor方法一定不为null,然后异步调用执行listener的业务方法,否则会同步调用执行listener。

此时如果你使用注解@TransactionalEventListener监听,注解会失效。

具体请看:https://cuizb.top/myblog/article/detail/1684739163,

invokeListener方法

java 复制代码
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);
 }
}

doInvokeListener方法

java 复制代码
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
 try {
  // 这里是事件发生的地方
  listener.onApplicationEvent(event);
 }
 catch (ClassCastException ex) {
	 ......
 }
}

点击ApplicationListener 接口 onApplicationEvent 方法的实现,可以看到我们重写的方法。

总结

Spring 使用反射机制,获取了所有继承 ApplicationListener 接口的监听器,在 Spring 初始化时,会把监听器都自动注册到注册表中。

Spring 的事件发布非常简单,我们来总结一下:

  1. 定义一个继承ApplicationEvent的事件;
  2. 定义一个实现ApplicationListener的监听器或者使用@EventListener监听事件;
  3. 定义一个发送者,调用ApplicationContext直接发布或者使用ApplicationEventPublisher来发布自定义事件;

最后,发布-订阅模式可以很好的将业务逻辑进行解耦,大大提高了可维护性、可扩展性。


--------------------------------------------------------------欢迎叨扰此地址---------------------------------------------------------------

本文作者:Java技术债务

原文链接:https://cuizb.top/myblog/article/detail/1697189495

版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。

相关推荐
F-2H22 分钟前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
苹果酱056725 分钟前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
武昌库里写JAVA29 分钟前
【MySQL】7.0 入门学习(七)——MySQL基本指令:帮助、清除输入、查询等
spring boot·spring·毕业设计·layui·课程设计
_oP_i1 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx1 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康2 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘3 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意3 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
刘大辉在路上3 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
FF在路上4 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言