基于spring实现事件监听

目录

  • 事件监听
  • JDK 事件监听机制
    • 基于JDK实现事件监听
  • Spring 事件监听
    • 基于Spring 实现事件监听
    • 基于Spring 实现异步事件监听

事件监听

​ 常见事件监听机制的主要角色如下:
​ 事件及事件源:事件源发生某事件是特定事件监听器被触发的原因;
​ 事件监听器:监听器监听特定事件,并在内部定义了事件发生后的响应逻辑;
​ 事件发布器:事件监听器的容器,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。

JDK 事件监听机制

​ JDK为提供了一个代表所有可被监听事件的事件基类 java.util.EventObject,所有自定义事件类型都必须继承该类:

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    /**
     * 事件源对象
     */
    protected transient Object  source;

    /**
     * 构建一个原型事件,source不能为null 
     */
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    
    public Object getSource() {
        return source;
    }

    
    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

​ JDK 还提供了一个对所有事件监听器进行抽象的接口 java.util.EventListener,这是一个标记接口,所有自定义事件监听器都必须实现该标记接口:

/*
 * 所有事件侦听器接口都必须扩展的标记接口
*/
public interface EventListener {

}

基于JDK实现事件监听

​ 以监听任务执行完毕,发送邮件为例:

​ 1、定义事件源

@Data
@AllArgsConstructor
public class Task {

    /**
     *  任务名
     */
    private String name;

    /**
     * 任务执行结果
     */
    private Integer code;

}

​ 2、定义事件

@Getter
public class TaskEvent extends EventObject {

    /**
     * 消息内容
     */
    private String message;

    /**
     * 消息接收人
     */
    private String receiver;


    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public TaskEvent(Object source, String message, String receiver) {
        super(source);
        this.message = message;
        this.receiver = receiver;
    }
}

​ 3、定义事件监听器

/**
 * @Description 定义统一事件监听处理接口
 * @Author jidi
 */
public interface TaskEventListener extends EventListener {

    /**
     * 任务完成处理逻辑
     */
    void handleTask(TaskEvent event);
}


/**
 * @Description 定义邮件发送监听器
 * @Author jidi
 */
@Slf4j
@Data
public class MailTaskListener implements TaskEventListener {
    private static final Integer OK = 200;

    @Override
    public void handleTask(TaskEvent event) {
        Task source = (Task)event.getSource();

        if(Objects.equals(source.getCode(), OK)){
            /**
             * 模拟发送邮件
             */
            log.info("尊敬的{},您好!任务-{}已执行完毕!{}", event.getReceiver(), source.getName(), event.getMessage());
        }
    }
}

​ 4、定义事件发布器

public class TaskEventPublisher {

    /**
     * 保存监听器
     */
    private List<TaskEventListener> listeners = new ArrayList<>();


   /**
    * 注册监听器
    */
    public synchronized void register(TaskEventListener listener){
        if(!listeners.contains(listener)){
            listeners.add(listener);
        }
    }


    /**
     * 移除监听器
     */
    public synchronized boolean unregister(TaskEventListener listener){
        if(listeners.isEmpty()){
            return true;
        }
        return listeners.remove(listener);
    }


    /**
     * 发布事件
     */
    public void publishEvent(TaskEvent event){
        // 循环调用
        listeners.stream().forEach(listener -> listener.handleTask(event));
    }

}

​ 5、测试

 public static void main(String[] args) {

        /**
         * 初始化事件源
         */
        Task task = new Task("订单10001开始发货!", 200);

        /**
         * 初始化事件
         */
        TaskEvent event = new TaskEvent(task, "回复TD退订", "基地");

        /**
         * 初始化监听器
         */
        MailTaskListener listener = new MailTaskListener();

        /**
         * 初始化发布器
         */
        TaskEventPublisher publisher = new TaskEventPublisher();

        /**
         * 注册监听器
         */
        publisher.register(listener);

        /**
         * 发布事件
         */
        publisher.publishEvent(event);
    }

​ 最终测试结果如下:
image-20211201213201934

Spring 事件监听

​ Spring 框架对事件的发布与监听提供了相对完整的支持,它扩展了JDK中对自定义事件监听提供的基础框架(java.util.EventObjectjava.util.EventListener),并与Spring的IOC特性作了整合,使得用户可以根据自己的业务特点进行相关的自定义,并依托Spring容器方便的实现监听器的注册和事件的发布。

​ Spring 事件机制有几个重要的接口:
​ 1、ApplicationEvent,Spring 提供的事件抽象类,继承自 java.util.EventObject
​ 2、ApplicationListener,Spring 提供的事件监听器接口,继承自 java.util.EventListener,提供了一个 onApplicationEvent 方法,用于执行具体的事件处理逻辑;
​ 3、ApplicationEventPublisher,Spring 提供的事件发布接口,ApplicationContext 实现了该接口,该接口提供了一个publishEvent 方法,用来发布事件;
​ 4、ApplicationEventMulticaster,Spring 事件机制中的事件广播器,默认实现 SimpleApplicationEventMulticaster,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。

执行流程

​ Spring 事件执行流程为:当一个事件源产生事件时,它经过事件发布器 ApplicationEventPublisher 发布事件,而后事件广播器ApplicationEventMulticaster 会去事件注册表 ApplicationContext 中找到事件监听器 ApplicationListnener,而且逐个执行监听器的 onApplicationEvent 方法,从而完成事件监听器的逻辑。

​Spring 提供5种标准的事件监听:
​ 1、ContextRefreshedEvent:上下文更新事件,会在 ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的 refresh() 方法时被触发;
​ 2、ContextStartedEvent:上下文开始事件,当容器 ConfigurableApplicationContextstart() 方法开始/重新开始容器时触发该事件;
​ 3、ContextStoppedEvent:上下文停止事件,当容器 ConfigurableApplicationContextstop() 方法停止容器时触发该事件;
​ 4、ContextClosedEvent:上下文关闭事件,当 ApplicationContext 被关闭时触发该事件;
​ 5、RequestHandledEvent:请求处理事件,在web应用中,当一个http请求结束会触发该事件。

Spring Boot扩展了 Spring的 ApplicationContextEvent,提供了四种事件:
​ 1、ApplicationStartedEvent: spring boot启动开始时触发;
​ 2、ApplicationEnvironmentPreparedEvent:spring boot 对应 Enviroment已经准备完毕会触发,此时上下文还没创建;
​ 3、ApplicationPreparedEvent:spring boot上下文 context 创建完成触发,但此时 spring 中的 bean 是没有完全加载完成的;
​ 4、ApplicationFailedEvent:spring boot 启动异常时触发。

基于Spring 实现事件监听

自定义 Spring 事件监听的流程分4步:
​ 1、自定义事件,继承 ApplicationEvent 抽象类;
​ 2、定义事件监听器:
​       a、实现接口 ApplicationListener,重写 onApplicationEvent 方法来自定义相关事件的处理逻辑;
​       b、在某个方法上使用 @EventListener注解即可。
​ 3、注册监听器:
​       a、通过 SpringApplicationaddListeners 方法,手动注册;
​       b、通过注解 @Component 将监听器加入到容器中;
​       c、在配置文件中通过 context.listener.classes 指定监听器。
​ 4、发布事件,触发监听。使用实现了 ApplicationEventPublisher 接口的类(常用 ApplicationContext)的publishEvent(ApplicationEvent event)方法发布事件。

自定义事件

​ 通过继承 ApplicationEvent 来自定义事件:

@Getter
@Setter
public class MessageEvent<T> extends ApplicationEvent implements Serializable {
    private static final long serialVersionUID = 3529299915023264729L;

    /**
     *  消息类型
     */
    private Integer type;

    /**
     * 消息内容
     */
    private T content;

    public MessageEvent(Object source, Integer type, T content) {
        super(source);
        this.type = type;
        this.content = content;
    }
}

​ 注意:一定要显示创建默认的构造方法,构造器的参数为该事件的相关数据对象,监听器可以获取到该数据对象,进而进行相关逻辑处理。

定义事件监听器

​ 方式一,实现接口 ApplicationListener,重写 onApplicationEvent 方法:

@Slf4j
public class MessageEventListenter<T> implements ApplicationListener<MessageEvent<T>> {

    @Override
    public void onApplicationEvent(MessageEvent<T> event) {
        Integer type = event.getType();
        T content = event.getContent();

        // 发短信
        if(type == 1){
            log.info("模拟发送短信,短信内容为:{}", content);
        }
        // 发邮件
        else if(type == 2){
            log.info("模拟发送邮件,邮件内容为:{}", content);
        }
        // 站内信
        else {
            log.info("模拟发送站内信,站内信内容为:{}", content);
        }
    }
}

​ 方式二,使用注解@EventListener

@Slf4j
public class MessageEventHandler<T> {


    /**
     * 1、被@EventListener注解方法必须满足: 最多只能有一个参数
     * 2、若只是监听一种事件,参数类型应为该事件对象类(该事件的子类事件,也会被监听到)
     * 3、设置 classes 属性指定多个事件来监听多种事件且保证这个方法无参
     * 4、设置 condition 属性可以实现对事件进行选择性处理
     *
     */
    @EventListener(classes = MessageEvent.class)
    public void handlerMessageEvent(MessageEvent<T> event){
        Integer type = event.getType();
        T content = event.getContent();

        // 发短信
        if(type == 2){
            log.info("模拟发送短信,短信内容为:{}", content);
        }
        // 发邮件
        else if(type == 1){
            log.info("模拟发送邮件,邮件内容为:{}", content);
        }
        // 站内信
        else {
            log.info("模拟发送站内信,站内信内容为:{}", content);
        }
    }

}

注册监听器

​ 注册监听器,其实质就是将监听器进行IOC处理,让 spring 容器管理监听器的生命周期。 在SpringBoot中有三种方式可以将自定义的监听器纳入spring容器管理:

具体实现 备注
在SpringBoot启动类的main方法中,使用 SpringApplicationaddListeners 方法进行注册 进行单元测试时,(由于不会走SpringBoot启动的类main方法,所以)此方式不生效。
也不能搭配 @Async 注解,进行异步监听。
通过 @Component 或类似注解,将监听器(或监听器方法所在的)纳入容器管理 推荐使用方式,能够搭配@Async 注解,进行异步监听。
在SpringBoot配置文件中指定监听器(或监听器方法所在的)类 多个监听器,使用逗号分割即可。
不能搭配 @Async 注解,进行异步监听。

​ 方式一,使用 SpringApplicationaddListeners 方法进行注册:

 public static void main(String[] args) {
        SpringApplication application =	new SpringApplication(AnswerAdminApplication.class);
     	// 添加监听器
        application.addListeners(new MessageEventListenter<String>());
        application.run(args);
    }

​ 方式二,使用注解将监听器纳入容器管理:

/**
 * 使用@Component,纳入spring容器管理
 */
@Component
@Slf4j
public class MessageEventHandler<T> {


    /**
     * 1、被@EventListener注解方法必须满足: 最多只能有一个参数
     * 2、若只是监听一种事件,参数类型应为该事件对象类(该事件的子类事件,也会被监听到)
     * 3、设置 classes 属性指定多个事件来监听多种事件且保证这个方法无参
     * 4、设置 condition 属性可以实现对事件进行选择性处理
     *
     */
    @EventListener(classes = MessageEvent.class)
    public void handlerMessageEvent(MessageEvent<T> event){
        Integer type = event.getType();
        T content = event.getContent();

        // 发短信
        if(type == 2){
            log.info("模拟发送短信,短信内容为:{}", content);
        }
        // 发邮件
        else if(type == 1){
            log.info("模拟发送邮件,邮件内容为:{}", content);
        }
        // 站内信
        else {
            log.info("模拟发送站内信,站内信内容为:{}", content);
        }
    }
}


/**
 * 使用@Component,纳入spring容器管理
 */
@Component
@Slf4j
public class MessageEventListenter<T> implements ApplicationListener<MessageEvent<T>> {

    @Override
    public void onApplicationEvent(MessageEvent<T> event) {
        Integer type = event.getType();
        T content = event.getContent();

        // 发短信
        if(type == 1){
            log.info("模拟发送短信,短信内容为:{}", content);
        }
        // 发邮件
        else if(type == 2){
            log.info("模拟发送邮件,邮件内容为:{}", content);
        }
        // 站内信
        else {
            log.info("模拟发送站内信,站内信内容为:{}", content);
        }
    }
}

​ 方式三,使用配置文件设置监听器

# 配置监听器
context.listener.classes: com.vvupup.answer.admin.event.MessageEventListenter

事件发布

​ 调用 ApplicationEventPublisher 接口的 publishEvent 方法,发布事件:

/**
 * @Description Spring 事件发布测试
 * @Author jidi
 */
@RestController
@RequestMapping("/event")
public class TestController {

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/pub")
    public String test(){
        /**
         * 发布事件
         */
        applicationContext.publishEvent(new MessageEvent<>(this, 1, "短信"));
        return "ok";
    }
}

​ 最终执行结果:
image-20211201220353767

基于Spring 实现异步事件监听

​ 上面的例子实现事件监听使用的是同步方式,还可以结合注解 @Async 实现异步事件监听:

/**
 * 1.开启异步,自定义线程池(非必须,默认会使用 SimpleAsyncTaskExecutor ) 
 */
@Configuration
@EnableAsync   // @EnableAsync 注解开启异步
public class AsyncConfig extends AsyncConfigurerSupport {

    /**
     *  自定义线程池
     */
    @Bean("executor")
    public Executor executor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 线程池核心线程数量
        executor.setCorePoolSize(5);
        // 队列最大长度
        executor.setQueueCapacity(200);
        // 最大线程数量
        executor.setMaxPoolSize(10);
        // 线程池拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程名前缀
        executor.setThreadNamePrefix("async-");
        // 允许空闲时间
        executor.setKeepAliveSeconds(30);
        // 初始化
        executor.initialize();
        return executor;
    }


    /**
     *  如果有多个线程池,指定要使用的线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }
}



/**
 * 2、使用 @Async 注解,开启异步处理
 */

@Data
@Slf4j
public class MessageEventListener<T> implements ApplicationListener<MessageEvent<T>> {


    /**
     * 使用 @Async 开启方法异步执行,value属性指定要使用的线程池
     */
    @Async(value = "executor")
    @Override
    public void onApplicationEvent(MessageEvent<T> event) {

        Integer type = event.getType();
        T content = event.getContent();

        // 发短信
        if(type == 1){
            log.info("模拟发送短信,短信内容为:{}", content);
        }
        // 发邮件
        else if(type == 2){
            log.info("模拟发送邮件,邮件内容为:{}", content);
        }
        // 站内信
        else {
            log.info("模拟发送站内信,站内信内容为:{}", content);
        }
    }
}

你可能感兴趣的:(spring,boot,spring,boot,spring,java,后端,事件处理机制)