首页 > 其他分享 >spring-event-事件监听机制实现

spring-event-事件监听机制实现

时间:2024-03-08 17:56:59浏览次数:35  
标签:spring springframework 监听 public context org import event

1. 事件监听机制概述

1. 场景

模型版本更新了,新版本需要继承老版本的构件分享、自定义属性、着色数据,以后还可能有其他数据要继承,这些数据之间没有直接联系,就是当模型版本变更的时候,他们各自需要执行。

2. 涉及的三个对象

  • 事件源(提供事件处理时的元数据)

这里就是模型版本更新了

  • 监听器(事件处理者)

构件分享继承是一个监听者,监听到版本更新的事件,需要自己处理点东西
自定义属性继承也是一个监听者。。
着色数据继承也是一个监听者。。。

  • 事件发布者(调用者)

2. 示例

1. 事件源 ApplicationEvent

定义一个事件源,只需要继承 org.springframework.context.ApplicationEvent.ApplicationEvent 或其子类

package com.demo.event;

import org.springframework.context.ApplicationEvent;

/**
 * 事件源:模型变更了
 * 伪代码:模型变更
 * */
public class ModelExchangeEvent extends ApplicationEvent {

    private Integer modelId;
    private Integer modelVersion;

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public ModelExchangeEvent(Object source, Integer modelId, Integer modelVersion) {
        super(source);
        this.modelId = modelId;
        this.modelVersion = modelVersion;
    }

    public Integer getModelId() {
        return modelId;
    }
    public Integer getModelVersion() {
        return modelVersion;
    }
    public void setModelId(Integer modelId) {
        this.modelId = modelId;
    }
    public void setModelVersion(Integer modelVersion) {
        this.modelVersion = modelVersion;
    }
}

2. 监听者 ApplicationListener

监听器可以有多个,每个监听器代表一种操作。

  • 通过继承 ApplicationListener 实现监听器
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 伪代码:着色继承
 * 实现 `ApplicationListener` 方式
 * */
@Component
@Slf4j
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的变更通知!着色继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("着色继承结束!");
    }
}
  • 通过 @EventListener 注解实现监听器
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 伪代码:继承
 * 使用 @EventListener 方式
 * */
@Component
@Slf4j
public class ModelExchangePropertiesListener {

    /**
     * 属性继承
     * */
    @EventListener(ModelExchangeEvent.class)
    public void onEvent(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的变更通知!自定义属性继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("自定义属性继承结束!");
    }

    /**
     * 分享继承
     * */
    @EventListener(ModelExchangeEvent.class)
    public void sss(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的变更通知!分享继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("分享继承结束!");
    }
}

3. 事件发布者 ApplicationEventPublisher

实现了 ApplicationEventPublisher 接口,就是一个发布者,通过 publishEvent 方法发布事件。

  • 直接注入 ApplicationEventPublisher 对象
package com.demo.controller;

import com.demo.event.ModelExchangeEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/model")
@Slf4j
public class ModelController{

    /**
     * 使用 ApplicationEventPublisher 发布事件
     * */
    @Autowired
    private ApplicationEventPublisher ap;

    @RequestMapping("/exchange")
    public void exchange(){
        log.info("伪代码:模型变更的逻辑");
        ap.publishEvent(new ModelExchangeEvent(ap, 9001, 2));
    }
}

这里通过自动注入一个 ApplicationEventPublisher 来发布事件,这个注入的 publisher 其实就是启动的 Spring 容器对象 ConfigurableApplicationContext run = SpringApplication.run(EventMain.class, args),也就是这个 run

  • 通过 ApplicationEventPublisherAware 来手动设置 publisher
package com.demo.controller;

import com.demo.event.ModelExchangeEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/model")
@Slf4j
public class ModelController implements ApplicationEventPublisherAware{

    /**
     * 使用 ApplicationEventPublisher 发布事件
     * */
    private ApplicationEventPublisher ap;

    @RequestMapping("/exchange")
    public void exchange(){
        log.info("伪代码:模型变更的逻辑");
        this.ap.publishEvent(new ModelExchangeEvent(ap, 9001, 2));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.ap = applicationEventPublisher;
    }
}

跟上面差距不大

3. 重要组件说明

1. ApplicationEvent

Spring中所有事件的基类,继承自java.util.EventObject。开发人员可以通过继承ApplicationEvent类来创建自定义事件,在事件对象中封装相关信息。事件可以同步或异步触发,并支持条件事件和层次事件等特性。

2. ApplicationListener

是一个监听器接口,用于处理特定类型的事件。当被感兴趣的事件发生时,容器会调用相应的监听器方法,传入该事件作为参数。开发人员可以实现ApplicationListener接口来创建自己的监听器,然后使用@EventListener注解或者配置文件的方式进行注册。

3. ApplicationEventMulticaster

是一个事件广播器,将事件发送给所有已注册的ApplicationListener。默认情况下,Spring使用SimpleApplicationEventMulticaster实现事件广播,但也可以通过配置使用其他实现方式,例如AsyncApplicationEventMulticaster和SimpleThreadScopeEventMulticaster。
就是通过这玩意来调用每个 listener 的处理逻辑。

4. ApplicationEventPublisher

发布者,通过这个组件来发布事件。

4. 多个监听者执行顺序

默认情况下多个监听者同步执行,使用 @Order 注解标注执行顺序。

5. 异步调用

也有两种方式。

1. ApplicationEventMulticaster

package com.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.stereotype.Component;

import java.util.concurrent.Executor;

/**
 * 配置监听器异步执行
 * */
@Configuration
public class ListenerConfig {

    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() { //@1
        //创建一个事件广播器
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
        //给广播器提供一个线程池,通过这个线程池来调用事件监听器
        Executor executor = this.applicationEventMulticasterThreadPool().getObject();
        //设置异步执行器
        result.setTaskExecutor(executor);//@1
        return result;
    }

    //提供一个线程池
    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("modelThread-");
        result.setCorePoolSize(2);
        return result;
    }
}

  • 原理

  自定义 ApplicationEventMulticaster,并设置其线程池,监听器最终是通过ApplicationEventMulticaster内部的实现来调用的,所以我们关注的重点就是这个类,这个类默认有个实现类 SimpleApplicationEventMulticaster,这个类是支持监听器异步调用的,内部有个线程池字段:

private Executor taskExecutor;

SimpleApplicationEventMulticaster 执行逻辑:

@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) { 
            //线程池对象不为 null,就提交自定义监听事件到线程池中
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //线程池对象为 null,就顺序执行每个 listener 自定义监听事件
            invokeListener(listener, event);
        }
    }
}

所以我们需要定义一个 SimpleApplicationEventMulticaster 的 bean 并设置其线程池.

  • 弊端:这样会使所有的监听器都异步执行

2. @Async

@EnableAsync 标注在启动类上,来标识支持异步。@Async 标注在指定 listener 上。这种应该就是传说中 bean 的异步执行。

package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 伪代码:着色继承
 * 实现 ApplicationListener 方式
 * */
@Component
@Slf4j
@Async
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的变更通知!着色继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("着色继承结束!");
    }
}
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 伪代码:继承
 * 使用 @EventListener 方式
 * */
@Component
@Slf4j
public class ModelExchangePropertiesListener {

    /**
     * 属性继承
     * */
    @EventListener(ModelExchangeEvent.class)
    @Async
    public void onEvent(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的变更通知!自定义属性继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("自定义属性继承结束!");
    }

    /**
     * 分享继承
     * */
    @EventListener(ModelExchangeEvent.class)
    @Async
    public void sss(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的变更通知!分享继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("分享继承结束!");
    }
}

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class EventMain {
    public static void main(String[] args) {
        SpringApplication.run(EventMain.class, args);
    }
}

  • 默认的线程池 SimpleAsyncTaskExecutor 不太行,可以通过 @Async("applicationEventMulticasterThreadPool") 自己换一个。
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 伪代码:着色继承
 * 实现 ApplicationListener 方式
 * */
@Component
@Slf4j
@Async("applicationEventMulticasterThreadPool")
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的变更通知!着色继承开始!", e.getModelId(), e.getModelVersion());
        //模拟逻辑运行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("着色继承结束!");
    }
}

自定义的线程池

package com.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;

import java.util.concurrent.Executor;

/**
 * 配置监听器异步执行
 * */
@Configuration
public class ListenerConfig {
    //提供一个线程池
    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("modelThread-");
        result.setCorePoolSize(2);
        return result;
    }
}

参考文献

https://blog.csdn.net/lin1214000999/article/details/124707979
原理参考:https://cloud.tencent.com/developer/article/2364790

标签:spring,springframework,监听,public,context,org,import,event
From: https://www.cnblogs.com/cnff/p/18061283

相关文章

  • C#事件(event)的理解
    一、多播委托的应用--观察者模式遇到一个开发的问题?面试者:以面向对象的思想实现一下的场景:猫:Miao一声,紧接着引发了一系列的行为~Miao:引发了一系列的动作;从代码层面来说:代码这样写好吗?猫职责不单一(猫就是猫,他的行为只有Miao一声)依赖太重,依赖了很多的普通类;被依赖的类如......
  • Spring知识回顾
    首先对于我们来说Spring是什么,Spring可以让我们对刚开始写项目时,有一个明确的框架结构,用比较专业的话来讲,就是使用IOC和AOP,即Inversionofcontrol控制反转和AspectOrientedProgramming面向切面编程,总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。学习IOC的组......
  • springboot项目接入普罗米修斯
    为了更好查询项目的运行状态,这次引入普罗米修斯监控pom依赖<!--starter-actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency><!--下面是/actuato......
  • springboot 2.4.0 启动源码分析
    SpringBoot启动的基本配置SpringBoot启动的配置主要有以下两个部分添加依赖最基本的springboot依赖只需要添加一个springboot启动器的依赖即可,如果是web服务则添加web的启动器<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"......
  • springboot整合nacos注册中心和配置中心
    我的命名空间是centos效果图   可以是yml格式,名称不要变springboot版本2.1.6pom依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchem......
  • Spring boot常用命令总结
    1.打包jar包mvnpackageclean-Dmaven.test.skip=truemvnpackage-Dmaven.test.skip=true2.结束当前运行的程序ps-aux|grepjavakill-9$(ps-ef|grep/usr/bin/chromedriver|grep-vgrep|awk'{print$2}')kill-9$(ps-ef|grep/opt/google/chrome/chrome|grep......
  • SpringMVC实现文件上传&下载(2)
    文件上传步骤第一步:由于SpringMVC使用的是commons-fileupload实现,故将其组件引入项目中,这里用到的是commons-fileupload-1.2.1.jar和commons-io-1.3.2.jar。第二步:spring-mvx中配置MultipartResolver处理器。可在此加入对上传文件的属性限制。第三步:在Controller的方法中添加M......
  • Spring-Framework6.1.x源码阅读环境搭建
    Spring-FrameWork6.1.x源码阅读环境搭建1.代码获取https://github.com/spring-projects/spring-framework.git。2.进入目录,修改目录下gradle/wrapper/gradle-wrapper.properties,看一下这里指定的gradle版本,distributionUrl=https://services.gradle.org/distributions/gradle-......
  • 【Spring RESTful】RESTful开发风格的一些细节
    传统基于MVC模式的Web应用的问题JSP返回HTML不能被app/小程序解析REST&&RESTfulRESTful传输数据客户端发送的所有请求都是url,url是用户交互入口服务端只返回JSON/XML格式数据,不包含任何的渲染内容不同的客户端接受数据之后,以自己的形式对数据进行渲染与展现RESTfu......
  • spring - mvc - @Async
    @Async@EnableAsync1.启用异步支持@EnableAsync注释在我们的应用程序中启用异步处理。具有类似功能的XML等效项是使用executor属性的task:*命名空间。让我们首先通过Java配置启用异步处理。我们将通过将@EnableAsync添加到配置类来完成此操作:@Configuration@EnableAsync......