springboot publish event 事件机制demo分享

 更新时间:2022年10月27日 09:40:08   作者:阿拉的梦想  
这篇文章主要介绍了springboot publish event 事件机制demo,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

1. 使用ApplicationEventPublisher 发布事件

复制下面全部代码,右键包名,粘贴即可生成java类,执行即可看到效果。

事件机制

  • 需要自定义一个事件类继承ApplicationEvent;
  • 需要自定义一个监听器类实现ApplicationListener接口,或普通类的方法中使用@EventListener注解;
  • 使用默认发布器ApplicationEventPublisher发布即可;
  • 事件类不需要注入到IOC;监听器需要注入到IOC;ApplicationEventPublisher用Autowired注入进来即可;
  • 默认情况下,事件发布与执行是同步的,事件执行完毕,发布者才会执行下面的逻辑;
package com.example.controller;

import com.alibaba.fastjson.JSON;
import com.example.SpringbootRedisApplication;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Objects;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringbootRedisApplication.class)
@Slf4j
@EnableAsync
public class PublishEventDemo {
    /**
     * 事件机制:
     * 需要自定义一个事件类继承ApplicationEvent;
     * 需要自定义一个监听器类实现ApplicationListener接口,或普通类的方法中使用@EventListener注解;
     * 使用默认发布器ApplicationEventPublisher发布即可;
     * 事件类不需要注入到IOC;监听器需要注入到IOC;ApplicationEventPublisher用Autowired注入进来即可;
     */


    /**
     * 事件发布器
     */
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    /**
     * 测试类
     */
    @Test
    public void publishTest() throws InterruptedException {
        Task task = new Task();
        task.setId(1L);
        task.setTaskName("测试任务");
        task.setTaskContext("任务内容");
        task.setFinish(false);
        MyEvent event = new MyEvent(task);
        log.info("开始发布任务");
        eventPublisher.publishEvent(event);
        //applicationContext.publishEvent(event);
        log.info("结束发布任务");
        Thread.sleep(10000);
    }
}

/**
 * 任务类
 * 任务就是一个普通的类,用来保存你要发布事件的内容。这个没有特殊限制,可以根据自己业务随意设置。
 */
@Data
class Task {

    private Long id;

    private String taskName;

    private String taskContext;

    private boolean finish;
}

/**
 * 事件类
 * 事件类需要继承org.springframework.context.ApplicationEvent,这样发布的事件才能被Spring所识别
 */
@Slf4j
class MyEvent<T> extends ApplicationEvent {

    private Task task;

    public MyEvent(Task task) {
        super(task);
        this.task = task;
    }

    public Task getTask() {
        return task;
    }
}

/**
 * 事件监听类
 * 事件的监听器需要实现org.springframework.context.ApplicationListener,并且需要注入到容器之中。
 */
@Component
@Slf4j
class MyEventListenerA implements ApplicationListener<MyEvent> {
    /**
     * 监听方式1:实现ApplicationListener接口,重写onApplicationEvent方法
     * 需要使用Component注入IOC
     */
    @SneakyThrows
    @Async
    @Override
    public void onApplicationEvent(MyEvent MyEvent) {
        Thread.sleep(5000);
        if (Objects.isNull(MyEvent)) {
            return;
        }
        Task task = MyEvent.getTask();
        log.info("监听器A接收任务:{}", JSON.toJSONString(task));
        task.setFinish(true);
        log.info("监听器A此时完成任务");

    }
}

@Component
@Slf4j
class MyEventListenerB implements ApplicationListener<MyEvent> {
    /**
     * 监听方式2:接口和注解混合使用
     * 但此时 @EventListener不能与注解@Async在同一个类中使用,会报错,至于为什么,不知道;
     * 需要使用Component注入IOC
     */
    //@Async//加上这个,@EventListener的方法就会报java.lang.IllegalStateException: Failed to load ApplicationContext
    @SneakyThrows
    @Override
    public void onApplicationEvent(MyEvent MyEvent) {
        Thread.sleep(1000);
        if (Objects.isNull(MyEvent)) {
            return;
        }
        Task task = MyEvent.getTask();
        log.info("监听器B接收任务:{}", JSON.toJSONString(task));
        task.setFinish(true);
        log.info("监听器B此时完成任务");
    }

    @EventListener
    public void someMethod(MyEvent event) throws InterruptedException {
        Thread.sleep(1000);
        log.info("监听器@EventListenerB收到={}", event.getTask());
    }
}


@Component
@Slf4j
class MyEventListennerC {
    /**
     * 监听方式3:注解@EventListener的监听器不需要实现任何接口
     * 需要使用Component注入IOC
     */
    @EventListener
    public void someMethod(MyEvent event) throws InterruptedException {
        Thread.sleep(1000);
        log.info("监听器@EventListenerC收到={}", event.getTask());
    }
}

运行日志:

2020-11-30 18:13:56.238  INFO 19776 --- [           main] com.example.controller.PublishEventDemo  : Started PublishEventDemo in 11.098 seconds (JVM running for 13.737)
2020-11-30 18:13:56.902  INFO 19776 --- [           main] com.example.controller.PublishEventDemo  : 开始发布任务
2020-11-30 18:13:57.904  INFO 19776 --- [           main] com.example.controller.MyEventListenerB  : 监听器@EventListenerB收到=Task(id=1, taskName=测试任务, taskContext=任务内容, finish=false)
2020-11-30 18:13:58.905  INFO 19776 --- [           main] c.example.controller.MyEventListennerC   : 监听器@EventListenerC收到=Task(id=1, taskName=测试任务, taskContext=任务内容, finish=false)
2020-11-30 18:13:59.920  INFO 19776 --- [           main] com.example.controller.MyEventListenerB  : 监听器B接收任务:{"finish":false,"id":1,"taskContext":"任务内容","taskName":"测试任务"}
2020-11-30 18:13:59.921  INFO 19776 --- [           main] com.example.controller.MyEventListenerB  : 监听器B此时完成任务
2020-11-30 18:13:59.921  INFO 19776 --- [           main] com.example.controller.PublishEventDemo  : 结束发布任务
2020-11-30 18:14:03.913  INFO 19776 --- [         task-1] com.example.controller.MyEventListenerA  : 监听器A接收任务:{"finish":true,"id":1,"taskContext":"任务内容","taskName":"测试任务"}
2020-11-30 18:14:03.913  INFO 19776 --- [         task-1] com.example.controller.MyEventListenerA  : 监听器A此时完成任务
2020-11-30 18:14:09.958  INFO 19776 --- [       Thread-3] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

事件发送后,会等待事件执行完毕,因此他们是同步的。若想异步执行事件,可以把@Async加到监听方法上;

2. 使用ApplicationContext发布事件

与上例不同之处

  • 使用ApplicationContext发布事件,ApplicationContext实现了ApplicationEventPublisher接口;
  • 使用ApplicationContextEvent 定义事件,ApplicationContextEvent 继承了ApplicationEvent类;

demo代码:

package com.example.controller;

import ch.qos.logback.classic.Logger;
import com.example.SpringbootRedisApplication;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RestController;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringbootRedisApplication.class)
@RestController
@Slf4j
public class PublishEventDemo2 {
    /**
     * 使用ApplicationContext发布事件
     */
    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void send() {
        MyEvent2 myEvent2 = new MyEvent2(applicationContext);
        myEvent2.setData("数据");
        log.info("开始发送事件");
        applicationContext.publishEvent(myEvent2);
        log.info("结束发送事件");
    }
}

/**
 * 监听器类
 */
@Slf4j
@Component
class MyEventListener2 implements ApplicationListener<MyEvent2> {
    @Override
    public void onApplicationEvent(MyEvent2 event) {
        log.info("监听器MyEventListener2收到={}", event);
    }

    @EventListener
    public void someMethod(MyEvent2 event) {
        log.info("监听器MyEventListener2@EventListener收到={}", event);
    }
}

/**
 * 监听器类
 */
@Slf4j
@Component
class MyEventListenner1 {
    @EventListener
    public void someMethod(MyEvent2 event) {
        log.info("监听器MyEventListenner1收到={}", event);
    }
}

/**
 * 事件类
 *
 * @param <T>
 */
@ToString
class MyEvent2<T> extends ApplicationContextEvent {
    private T data;

    public MyEvent2(ApplicationContext source) {
        super(source);
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

运行日志:

2020-11-30 18:03:38.638  INFO 15792 --- [           main] c.example.controller.PublishEventDemo2   : Started PublishEventDemo2 in 9.571 seconds (JVM running for 12.677)
2020-11-30 18:03:39.355  INFO 15792 --- [           main] c.example.controller.PublishEventDemo2   : 开始发送事件
2020-11-30 18:03:39.358  INFO 15792 --- [           main] com.example.controller.MyEventListener2  : 监听器MyEventListener2@EventListener收到=MyEvent2(data=数据)
2020-11-30 18:03:39.359  INFO 15792 --- [           main] c.example.controller.MyEventListenner1   : 监听器MyEventListenner1收到=MyEvent2(data=数据)
2020-11-30 18:03:39.359  INFO 15792 --- [           main] com.example.controller.MyEventListener2  : 监听器MyEventListener2收到=MyEvent2(data=数据)
2020-11-30 18:03:39.359  INFO 15792 --- [           main] c.example.controller.PublishEventDemo2   : 结束发送事件
2020-11-30 18:03:39.435  INFO 15792 --- [       Thread-3] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

事件发送后,会等待事件执行完毕,因此他们是同步的。若想异步执行事件,可以把@Async加到监听方法上;

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 快速搭建Spring Boot+MyBatis的项目IDEA(附源码下载)

    快速搭建Spring Boot+MyBatis的项目IDEA(附源码下载)

    这篇文章主要介绍了快速搭建Spring Boot+MyBatis的项目IDEA(附源码下载),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Java算法之计数排序、桶排序、基数排序实现代码

    Java算法之计数排序、桶排序、基数排序实现代码

    这篇文章主要介绍了Java算法之计数排序、桶排序、基数排序实现的相关资料,分别通过计数、分桶和位数处理实现高效稳定排序,文中通过代码及图文介绍的非常详细,需要的朋友可以参考下
    2025-05-05
  • Spring Cloud之注册中心Nacos的使用详解

    Spring Cloud之注册中心Nacos的使用详解

    本文介绍SpringCloud Alibaba中的Nacos组件,对比了Nacos与Eureka的区别,展示了如何在项目中引入SpringCloud Alibaba和Nacos,并配置负载均衡,通过实际操作,验证了服务注册和负载均衡的功能,感兴趣的朋友一起看看吧
    2025-03-03
  • 阿里通用OCR文字识别/图像识别/图片识别对接代码示例(Java篇)

    阿里通用OCR文字识别/图像识别/图片识别对接代码示例(Java篇)

    这篇文章主要介绍了阿里通用OCR文字识别/图像识别/图片识别对接(Java篇)的相关资料,文中详细介绍了包括开通服务、测试图片、编写识别代码、处理识别结果等步骤,需要的朋友可以参考下
    2024-12-12
  • mybatisplus驼峰命名映射的问题解决

    mybatisplus驼峰命名映射的问题解决

    本文主要介绍了mybatisplus驼峰命名映射的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • TKMybatis的介绍和使用详解

    TKMybatis的介绍和使用详解

    TKMybatis 是基于 Mybatis 框架开发的一个工具,本文主要介绍了TKMybatis的介绍和使用详解,内部实现了对单表的基本数据操作,只需要简单继承 TKMybatis 提供的接口,就能够实现无需编写任何 sql 即能完成单表操作,感兴趣的可以了解一下
    2021-12-12
  • Spring事务处理Transactional,锁同步和并发线程

    Spring事务处理Transactional,锁同步和并发线程

    本文详细讲解了Spring事务处理Transactional,锁同步和并发线程。对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • Lombok不生效,提示java: 找不到符号的解决方案

    Lombok不生效,提示java: 找不到符号的解决方案

    这篇文章主要介绍了Lombok不生效,提示java: 找不到符号的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • jpa使用uuid策略后无法手动设置id的问题及解决

    jpa使用uuid策略后无法手动设置id的问题及解决

    这篇文章主要介绍了jpa使用uuid策略后无法手动设置id的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • java 实现定时的方法及实例代码

    java 实现定时的方法及实例代码

    这篇文章主要介绍了java 定时任务详细介绍及实例代码的相关资料,需要的朋友可以参考下
    2017-03-03

最新评论