ApplicationEvent demo

文章介绍了Spring框架中的事件处理机制,包括创建自定义事件、监听器的实现以及事件的发布。通过一个SayHelloEvent的例子展示了事件的触发和监听过程。同时,讨论了监听器如何在程序启动时自动注册并接收事件,以及未指定泛型的监听器如何接收所有类型的事件。文章还提到了SpringBoot中@EventListener注解的使用及其与ApplicationListener接口的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前ApplicationRunner的源码,发现实现此类会注册一个事件到spring容器中,所以随便把spring中事件摸索一下

DEMO
  1. 事件传输消息体Bean
public class User {

    private LocalDateTime time;

    private String name;

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 创建事件。继承ApplicationEvent类
public class SayHelloEvent extends ApplicationEvent {

    private final User user;

    public SayHelloEvent(Object source, User user) {
        super(source);
        this.user = user;
    }

    public User getUser() {
        return user;
    }
}
  1. 创建监听器
@Component
public class SayHelloEventListener implements ApplicationListener<SayHelloEvent> {

    @Override
    public void onApplicationEvent(SayHelloEvent event) {
        User user = event.getUser();
        String msg = "I am " + user.getName() + " ,It's  " + user.getTime() + " now";
        System.out.println(msg);
    }

}
  1. 触发事件
@RestController
public class PublishEventController {

    @Resource
    private ApplicationEventPublisher eventPublisher;

    @GetMapping("/event")
    private ResponseEntity<?> publishEvent() {
        User user = new User();
        user.setName("Chris!");
        user.setTime(LocalDateTime.now());

        SayHelloEvent sayHelloEvent = new SayHelloEvent(this, user);

        eventPublisher.publishEvent(sayHelloEvent);
        return ResponseEntity.ok("OK");
    }
}
  1. 测试事件
GET 127.0.0.1:8080/event

输出:
SayHelloEventListener receive SayHelloEvent
I am ForestASpring ,It's 2023-03-02T16:52:37.150 now

需要注意的是:org.springframework.context.ApplicationEventPublisher#publishEvent(org.springframework.context.ApplicationEvent)方法发布事件之后,将被所有已注册的监听器监听到。所有实现org.springframework.context.ApplicationListener接口的监听器都将收到该事件并进行处理。

新增监听器 StudyEventListener

@Component
public class StudyEventListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof SayHelloEvent) {
            System.out.println("StudyEventListener receive SayHelloEvent");
        } else {
            System.out.println("StudyEventListener receive other event");
        }
    }
}

观察控制台输出,在程序启动时,该监听器就已经监听到事件。说明启动时,程序也发布了一些事件,只被StudyEventListener监听到了,为什么?

StudyEventListener receive other event
StudyEventListener receive other event
2023-03-02 17:27:35.826  INFO 30360 --- [           main] c.f.springboot.DemoApplication           : Started DemoApplication in 2.743 seconds (JVM running for 4.04)
StudyEventListener receive other event
StudyEventListener receive other event
StudyEventListener receive other event
StudyEventListener receive other event

测试后得到输出

SayHelloEventListener receive SayHelloEvent
I am ForestASpring ,It's 2023-03-02T17:34:45.563 now
StudyEventListener receive SayHelloEvent
StudyEventListener receive other event

可以看到,明明我们触发了SayHelloEvent事件,但是为什么 StudyEventListener 类的 other event也被打印了,而SayHelloEventListene却只打印了一遍,为什么?

显而易见,上面两个监听类其中SayHelloEvent没有指定泛型,所以它可以监听到程序启动时、其他被发布的任何事件,而指定泛型的监听器却只能监听到改事件的发生。

我们从发布事件入手,查看是如何拿到监听器处理事件的。

跟进publishEvent,进入AbstractApplicationContext

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    ....
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    } else {
        // 广播事件
        this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
    }
	....
}

跟进multicastEvent,进入SimpleApplicationEventMulticaster

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();
    // 匹配事件对应的监听器
    Iterator var5 = this.getApplicationListeners(event, type).iterator();

    while(var5.hasNext()) {
        // 遍历执行监听器的方法
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
            this.invokeListener(listener, event);
        }
    }

}

跟进getApplicationListeners,进入AbstractApplicationEventMulticaster

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
      
	.....
    // 重点关注这两行
	// 把传入的事件对象构造成缓存的key,eventType = SayHelloEvent,sourceType=调用publishEvent的Class
    AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new 								                     							AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
    
    // 根据key去缓存中匹配对应的value,key是ListenerCacheKey对象,value是CachedListenerRetriever,
    // 对象中的applicationListeners属性就是监听器对象列表
    // 通过这一步就能匹配到对应的监听器
    AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever =                   													(AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);
    ......

        return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

查看retrieverCache这个MAP是如何存放进去对象的需要查看容器的启动方法,不在这里做说明。

其次是为什么StudyEventListener会执行两遍,因为在发布事件的时候,spring内部还触发了其他的事件,被StudyEventListener捕捉到了,所以会比SayHelloEventListene多打印信息。

另外:

当使用Spring Boot构建应用程序时,除了org.springframework.context.ApplicationListener接口之外,还可以使用@EventListener注释来注册事件监听器。@EventListener注释是ApplicationListener接口的一个便捷替代品,可以使用它来将方法标记为事件监听器。在这种情况下,如果您想要注销事件监听器,则需要使用removeEventListener()方法,而不是removeApplicationListener()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值