• Spring事件监听机制


    一、事件监听概念

    应用程序事件允许我们发送和接收特定事件,我们可以根据需要处理这些事件。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。

    二、事件监听三要素

    • 事件源:事件对象的产生者,任何一个事件都有一个来源
      ApplicationEvent
    • 事件监听器:事件框架或组件收到一个事件后,需要通知所有相关的事件监听器来进行处理。这些监听器统一存储在事件监听器注册表中。
      ApplicationListener(编码式事件监听器)
      @EventListener(注解式事件监听器)
    • 发布事件:ApplicationContext(spring容器)

    三、Java事件

    Java中,通过java.util. EventObject来描述事件,通过java.util. EventListener来描述事件监听器。
    实例:

    // Abstract事件
    public class AbstractEvent extends EventObject {
        public AbstractEvent(Object source) {
        	super(source);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    // Test事件
    public class TestEvent extends AbstractEvent {
        public TestEvent(Object source) {
        	super(source);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    // AbstractEvent监听器
    public interface AbstractListener extends EventListener {
        // 处理AbstractEvent事件
        void onRegister(AbstractEvent event);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // TestEvent监听器
    public class TestListener implements AbstractListener {
        @Override
        public void onRegister(TestEvent event) {
            if (event instanceof TestEvent){
                System.out.println("Message: " + event.getSource());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    // 服务类
    public class TestService {
        // 存放所有监听器
        private final List<AbstractListener> listeners = new ArrayList<>();
        
        // 添加监听器
        public void addListener(AbstractListener abstractListener){
            this.listeners.add(abstractListener);
        }
        // 并发布注册事件
        public void register(String message){
            System.out.println("注册新事件");
            publishEvent(new TestEvent(message));
        }
        // 广播注册事件
        private void publishEvent(AbstractEvent event){
            for (AbstractListener listener : listeners) {
                listener.onRegister(event);
            }
        }
        
        public static void main(String[] args) {
            TestService service = new TestService();
            service.addListener(new TestListener());
            service.register("Register event...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    四、Spring事件

    在Spring中,事件机制采用观察者模式进行具体实现。

    • 事件源:ApplicationEvent。ApplicationEvent是Spring事件的顶层抽象类,代表事件本身,继承自JDK的EventObject。Spring提供了一些内置事件,如ContextClosedEvent、ContextStartedEvent、ContextRefreshedEvent、ContextStopedEvent等
    • 事件监听器:ApplicationListener(编码式事件监听器)、@EventListener(注解式事件监听器)。ApplicationListener是Spring事件监听器顶层接口,所有的监听器都实现该接口,继承自JDK的EventListener。Spring提供了一些易扩展的接口,如SmartApplicationListener、GenericApplicationListener
    • 事件发布:ApplicationContext(spring容器继承:ApplicationEventPublisher)。ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的publishEvent方法发布事件,所有的应用上下文都具备事件发布能力,因为ApplicationContext继承了该接口
    • 事件广播器:ApplicationEventMulticaster
      ApplicationEventMulticaster是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的(初始化逻辑见AbstractApplicationContext的refresh方法)。它通过父类AbstractApplicationEventMulticaster的getApplicationListeners方法从事件注册表中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑。
      默认的广播器是同步调用监听器的执行逻辑,但是可以通过为广播器配置Executor实现监听器的异步执行。
      在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法。
      当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。

    ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。

    如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。
    spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑。

    五、SpringApplicationEvent

    SpringApplicationEvent是SpringBoot事件的顶层抽象类,内置了7个事件:

    • ApplicationStartingEvent:SpringBoot启动开始的时候发布的事件
    • ApplicationEnvironmentPreparedEvent:SpringBoot对应Environment准备完毕时发布的事件,此时上下文还没有创建,该事件中可以获取配置信息。通过监听该事件可以修改默认的配置信息,配置中心就是借助该事件完成将远端配置放入应用中
    • ApplicationContextInitializedEvent:当准备好上下文,并且在加载任何Bean之前发布的事件。该事件可以获取上下文信息
    • ApplicationPreparedEvent:上下文创建完成时发布的事件,此时的Bean还没有完全加载完成,只是加载了部分特定Bean。该事件可以获取上下文信息,但是无法获取自定义Bean,应为还没有被加载
    • ApplicationStartedEvent:上下文刷新(完成所有Bean的加载)之后,但是还没有调用任何>+ ApplicationRunner 和 CommandLineRunner运行程序之前发布的事件。该事件同样可以获取上下文信息,并且可以获取自定义的Bean
    • ApplicationReadyEvent:完成ApplicationRunner 和 CommandLineRunner运行程序调用之后发布的事件,此时的SpringBoot已全部启动完成。该事件同样可以获取上下文信息
    • ApplicationFailedEvent:SpringBoot启动异常时发布的事件。该事件可以获取上下文信息和异常信息,通过该事件可以友好地完成资源的回收

    上述7个事件在容器启动的合适阶段进行发布,发布顺序自上而下,可查看SpringApplication的run(String… args)方法,分别对应SpringApplicationRunListener的7个方法,源码如下:

    // 该接口规定了SpringBoot的生命周期,会在各个生命周期广播响应的事件,调用实际的监听器
    public interface SpringApplicationRunListener {
        // 发布ApplicationStartingEvent
        default void starting() {
        }
        // 发布ApplicationEnvironmentPreparedEvent
        default void environmentPrepared(ConfigurableEnvironment environment) {
        }
        // 发布ApplicationContextInitializedEvent
        default void contextPrepared(ConfigurableApplicationContext context) {
        }
        // 发布ApplicationPreparedEvent
        default void contextLoaded(ConfigurableApplicationContext context) {
        }
        // 发布ApplicationStartedEvent
        default void started(ConfigurableApplicationContext context) {
        }
        // 发布ApplicationReadyEvent
        default void running(ConfigurableApplicationContext context) {
        }
        // 发布ApplicationFailedEvent
        default void failed(ConfigurableApplicationContext context, Throwable exception) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上述接口提供了唯一的实现类EventPublishingRunListener,该类聚合了SpringApplication和SimpleApplicationEventMulticaster,前4个事件的发布由广播器完成,后3个委托ApplicationContext完成发布,其实最终都是由广播器负责发布。

    六、Spring监听器使用方式

    6.1 通过SPI机制配置

    在resources目录下新建META-INF目录,并创建名为spring.factories的文件,在文件中声明以ApplicationListener接口全路径为键的配置,配置内容为ApplicationListener实现类的全路径,若有多个以逗号间隔,示例如下:

    org.springframework.context.ApplicationListener=\
    com.lyentech.bdc.tuya.listener.HunterApplicationContextInitializedListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationEnvironmentPreparedListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationFailedListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationPreparedListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationReadyListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationStartedListener,\
    com.lyentech.bdc.tuya.listener.HunterApplicationStartingListener
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Spring在启动的时候会加载spring.factories中配置的监听器实现类,并在合适的阶段通过发布事件触发相应监听器的逻辑。此方式可以实现针对任何事件(SpringBoot内置7个事件都支持)监听器的成功加载。

    6.2 启动类main方法手动添加

    在引用启动类的main方法中先创建一个SpringApplication实例,然后调用addListeners方法添加自定义的监听器实现类,最后调用实例的run方法启动容器,示例如下:

    public class TuyaServiceApplication{
        public static void main(String[] args) {
            // 习惯写法
            //SpringApplication.run(TuyaServiceApplication.class, args);
            
            // 新写法:实例化对象、添加监听器、启动
            SpringApplication springApplication = new SpringApplication(TuyaServiceApplication.class);
            springApplication.addListeners(new HunterApplicationStartingListener());
            springApplication.addListeners(new HunterApplicationPreparedListener());
            springApplication.run(args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这种方式也可以实现针对任何事件(SpringBoot内置7个事件都支持)监听器的成功加载。

    6.3 使用@Component

    在监听器实现类定义的时候适用@Component注解注释当前监听器,确保让Spring可以扫描到并完成加载,示例如下:

    // 监听ApplicationReadyEvent事件
    @Component
    public class HunterApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            System.err.println("===execute 【ApplicationReadyEvent】 listener...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这种方式只能实现对SpringBoot内置事件的后3个进行监听,因为容器启动在借助@Component加载Bean的时候,前4个事件已经发布了,所以不会生效。

    6.4 使用@Component和@EventListener

    这种方式可以减少监听器类的个数,这种方式配置的对前四个事件的监听不会生效,原因同第三种方式。示例如下:

    @Component
    public class HunterListener {
        @EventListener
        public void listener1(ApplicationStartingEvent event){
            System.err.println("使用@EventListener注册监听器,监听ApplicationStartingEvent事件");
        }@EventListener
        public void listener2(ApplicationStartedEvent event){
            System.err.println("使用@EventListener注册监听器,监听ApplicationStartedEvent事件");
        }@EventListener
        public void listener3(ApplicationReadyEvent event){
            System.err.println("使用@EventListener注册监听器,监听ApplicationReadyEvent事件");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    前三种方式的监听器都需要通过实现ApplicationListener接口给出具体定义,如果监听器特别多,就会造成类的数量很大;第四种方式仅需一个类就可以完成多个监听器的定义,但是适用的事件有限。
    如果多种方式共存,监听器会被执行多次。
    如果需要指定同类事件监听器的执行顺序,可以通过@Order或者实现Ordered接口完成。
    监听器默认同步执行,如果需要异步执行,一种方式是为广播器提供Executor,另一种就是在后两种使用方式上结合@Async注解(此时需要通过@EnableAsync开启容器对异步的支持)。

    七、自定义事件

    7.1 自定义事件

    // 自定义事件
    public class TestEvent extends ApplicationEvent {
        public TestEvent(Object obj) {
            super(obj);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.2 自定义监听器

    // 自定义监听器
    @Component
    public class TestListener implements ApplicationListener<TestEvent> {
        @Override
        public void onApplicationEvent(TestEvent event) {
    	    System.out.println("监听到事件发生:"+event.getClass());	
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    7.3 发布事件

    @Component
    public class EventPublisher implements ApplicationEventPublisherAware {
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
            TestEvent testEvent = new TestEvent("hello word");
            publisher.publishEvent(testEvent);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

  • 相关阅读:
    作战仿真试验理论体系研究
    DSP篇--C6678功能调试系列之Nor_FLASH调试
    MATLAB环境下基于离散小波变换的体外血管图像处理
    扫除知识共享障碍,天翎知识文档管理系统+群晖NAS一体化解决方案
    RabbitMQ延迟消息:死信队列 | 延迟插件 | 二合一用法+踩坑手记+最佳使用心得
    火山引擎边缘云:数智化项目管理助力下的业务增长引擎
    请求本地的 JSON 文件作为 mock 数据
    JS中的【函数】与【方法】之“父慈子孝”
    一起来学Kotlin:概念:13. Kotlin List, Set, Map, Sequence
    JavaScript之while和do while循环的用法
  • 原文地址:https://blog.csdn.net/qq_27870421/article/details/125498486