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加到监听方法上;

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

相关文章

  • 老生常谈Scanner的基本用法

    老生常谈Scanner的基本用法

    下面小编就为大家带来一篇老生常谈Scanner的基本用法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    这篇文章主要介绍了IDEA配置静态资源热加载操作(Springboot修改静态资源不重启),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Protostuff序列化和反序列化的使用说明

    Protostuff序列化和反序列化的使用说明

    今天小编就为大家分享一篇关于Protostuff序列化和反序列化的使用说明,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • spring启动错误Singleton bean creation not allowed while the singletons of this factory are indestruction

    spring启动错误Singleton bean creation not al

    本文主要介绍了spring启动错误Singleton bean creation not allowed while the singletons of this factory are indestruction,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • springboot业务功能实战之告别轮询websocket的集成使用

    springboot业务功能实战之告别轮询websocket的集成使用

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,下面这篇文章主要给大家介绍了关于springboot业务功能实战之告别轮询websocket的集成使用,需要的朋友可以参考下
    2022-10-10
  • Java中Console对象实例代码

    Java中Console对象实例代码

    这篇文章主要介绍了Java中Console对象实例代码,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    这篇文章主要介绍了Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁,本文讲解了ReentrantLock概况、Lock接口、Lock使用、轮询锁的和定时锁、公平性、可中断获锁获取操作等内容,需要的朋友可以参考下
    2015-04-04
  • Java mysql特殊形式的查询语句详解

    Java mysql特殊形式的查询语句详解

    这篇文章主要介绍了Java mysql特殊形式的查询,包括子查询和联合查询、自身连接查询问题,本文通过sql语句给大家介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • SpringBoot中的JPA(Java Persistence API)详解

    SpringBoot中的JPA(Java Persistence API)详解

    这篇文章主要介绍了SpringBoot中的JPA(Java Persistence API)详解,JPA用于将 Java 对象映射到关系型数据库中,它提供了一种面向对象的方式来操作数据库,使得开发者可以更加方便地进行数据持久化操作,需要的朋友可以参考下
    2023-07-07
  • SpringBoot多数据源配置并通过注解实现动态切换数据源

    SpringBoot多数据源配置并通过注解实现动态切换数据源

    本文主要介绍了SpringBoot多数据源配置并通过注解实现动态切换数据源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08

最新评论