深入挖掘Spring系列 -- Spring内部的事件机制(上)

简介: 深入挖掘Spring系列 -- Spring内部的事件机制(上)

相信在使用Spring框架的过程中,很多小伙伴都发现内部提供了一种叫做事件的机制,今天的文章主要重点给各位读者系统地介绍关于事件的部分知识点。


其实事件并非是Spring官方专门创造出来的,在早期的JDK中就已经有事件设计的影子了。


JDK内部提供的事件机理


package org.idea.spring.framework.event;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;
/**
 * @Author linhao
 * @Date created in 3:52 下午 2021/5/23
 */
public class EventDemo {
public static void main(String[] arg) {
        EventObservable observable = new EventObservable();
        observable.addObserver(new EventObserver());
        observable.notifyObservers("send message");
    }
static class EventObservable extends  Observable {
@Override
protected synchronized void setChanged() {
super.setChanged();
        }
@Override
public void notifyObservers(Object data){
this.setChanged();
super.notifyObservers(new EventObject(data));
            clearChanged();
        }
    }
static class EventObserver implements Observer, EventListener {
@Override
public void update(Observable o, Object msg) {
            EventObject eventObject = (EventObject) msg;
            System.out.println("接收数据:" + eventObject);
        }
    }
}
复制代码


JDK这类的事件机制的设计思路是,将订阅事件Event的角色定义为Observer角色,然后统一将这些Observer存放到一个List集合中,每个Observer都会有一个专属的获取通知的函数(也就是代码中的update函数),Event如果需要通知到各个Observer只需要出发订阅者的update函数即可。


整体的设计思路大致如下图所示:


网络异常,图片无法展示
|


JDK提供的事件机制在实际使用的时候还是存在较多的问题。这里我结合上述的代码案例来进行解析:


网络异常,图片无法展示
|


触发通知之前需要手动开启一个开关检验逻辑,因为JDK内部源代码规定,如果没有开启开关,将不会通知到各个订阅方。


网络异常,图片无法展示
|


网络异常,图片无法展示
|


关于JDK的事件机制个人感觉使用起来不是那么友善,所以不是特别推荐使用,稍微了解即可。


ps:其实Spring内部的事件标准也是基于JDK的这套API进行改善的。


Spring内部事件的订阅


关于事件部分我打算先从实战讲起,毕竟感觉没有实战案例的原理分析都是在炫技能。通过相关的实战案例既能够让大家有更深入的感受,也能在工作中如果对这块技术生疏之后又快速恢复印象起来。


多事件接收案例:


package org.idea.spring.framework.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
/**
 * @Author linhao
 * @Date created in 4:30 下午 2021/5/23
 */
@EnableAsync
public class AnnotationEventDemo {
public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(AnnotationEventDemo.class);
        annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
//这一块会优先执行
@Override
public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("=== application listener===");
            }
        });
        annotationConfigApplicationContext.refresh();
        annotationConfigApplicationContext.start();
        annotationConfigApplicationContext.close();
    }
//多事件处理的时候需要对参数做区别 监听数据处理的时候,需要注意先后顺序,因为反射中的getMethod是无顺序的
//@order的生效规则需要事件参数都是一致的时候才能生效
@EventListener
@Async
//    @Order(1)
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) throws InterruptedException {
        System.out.println("EventListener -- refresh 接收事件参数:" + contextRefreshedEvent);
    }
@EventListener
@Async
//    @Order(2)
public void onApplicationEvent2(ContextClosedEvent contextClosedEvent) throws InterruptedException {
        System.out.println("EventListener -- close 接收事件参数:" + contextClosedEvent);
    }
@EventListener
@Async
//    @Order(3)
public void onApplicationEvent(ContextStartedEvent contextStartedEvent) throws InterruptedException {
        System.out.println("EventListener -- started 接收事件参数:" + contextStartedEvent);
    }
}
复制代码


在Spring容器启动的过程中会发送多种事件,实际上我们可以对事件的类型进行分开监听,根据参数来识别。


例如监听Spring容器启动的事件


网络异常,图片无法展示
|


@EventListener事件使用的注意点


1.同步处理可能存在堵塞问题


这类事件监听机制实际上是同步的过程,按照上述的代码案例来说,如果在接收到Spring的refresh事件处理过程中,如果出现了堵塞情况,就会影响下边的started和close事件接收处理流程。


为了验证这一点可以在事件处理过程中让线程睡眠1秒钟观测下打印的效果


网络异常,图片无法展示
|


同步处理带来的不足点


网络异常,图片无法展示
|


如果希望解决这一问题,可以尝试启用异步处理机制。加上@Async注解即可


网络异常,图片无法展示
|


打印结果也有所不同


网络异常,图片无法展示
|


2.相同事件接收的先后问题


例如两个相同的事件 (一般是指在同一个bean内,接收事件时候参数一致的情况下) 接收之间有逻辑先后顺序的要求,可以对其加入@Order的注解进行区分


举个案例,当Spring容器准备进行销毁的时候,需要进行优雅关闭策略,那么这个时候需要指定几个关闭的环节,关闭的环节先后有一定的依赖顺序,代码案例如下图所示:


网络异常,图片无法展示
|


按照函数在代码的顺序从上到下依次执行即可保证优雅关闭的先后顺序,但是执行顺序却时而有序时而无序。


网络异常,图片无法展示
|


导致这一点的原因是:


Spring对于多事件监听的处理过程中会通过反射机制获取带有@EventListener注解的方法整理到一个数组集合中,然后便利去回调。但是在反射获取方法的时候,是需要通过JDK底层的getMethod获取的,但是getMehod获取到的Method集合本身是无顺序的,所以才会产生上边所说的那种现象。


网络异常,图片无法展示
|


为了避免这种现象的发生,可以加入一个@Order的注解来避免。如下所示:


网络异常,图片无法展示
|


自定义Spring事件订阅


这类事件我们在实际使用过程中可能会运用地更多。下边我们列举一个案例进行介绍:

首先定义一个Spring的事件


package org.idea.spring.framework.event;
import org.springframework.context.ApplicationEvent;
/**
 * @Author linhao
 * @Date created in 6:21 下午 2021/5/23
 */
public class MyEvent extends ApplicationEvent {
    /**
     * 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 MyEvent(Object source) {
        super(source);
    }
}
复制代码


然后再定义一个事件的发送和接收逻辑:


package org.idea.spring.framework.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * 事件发布器
 *
 * @Author linhao
 * @Date created in 4:47 下午 2021/5/23
 */
public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(ApplicationEventPublisherDemo.class);
        annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<MyEvent>() {
            @Override
            public void onApplicationEvent(MyEvent event) {
                System.out.println("application listener 接收到消息:" + event);
            }
        });
        annotationConfigApplicationContext.refresh();
        annotationConfigApplicationContext.close();
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("====");
        applicationEventPublisher.publishEvent(new MyEvent("hello world") {
        });
        applicationEventPublisher.publishEvent("pay load event");
    }
}
复制代码


这就是一个简单的自定义Spring事件案例。


这些自定义的事件发送是如何实现通知的?


关于如何实现部分需要查阅一下源代码中的实现机制。


这里我将其中的逻辑分成了两个步骤:


1.对订阅者管理


在Spring的内部的这个个后置处理组件:


org.springframework.context.support.ApplicationListenerDetector


中,再对每个用户自定义事件的bean进行初始化之前会将其加入到一个Map当中,然后再初始化之后对这些个Map中记录过的bean加入到一个专门存放ApplicationListener对象的Set集合中。


2.对订阅者通知


以上边的案例代码作为讲解对象,当Spring容器进行了启动的最后一个环节,代码逻辑位于:


org.springframework.context.support.AbstractApplicationContext#finishRefresh

这里面有个事件发布的通知机制


网络异常,图片无法展示
|


根据源代码跟踪,你会看到这么一处地方:


网络异常,图片无法展示
|


这里涉及到一个叫做多播器的概念,Spring对于广播事件实际上封装了一个

org.springframework.context.event.ApplicationEventMulticaster 接口来进行抽象管理,这个接口有时候也被称之为 “多播器”,默认实现是

org.springframework.context.event.SimpleApplicationEventMulticaster

而对应的回调部分其实也就是位于

org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener中。


分析了Spring事件的源码实现思路,你会发现其实逻辑比较清晰明了。


ApplicationEvent内部定义的四种基本事件


  • ContextRefreshedEvent事件


org.springframework.context.support.AbstractApplicationContext#finishRefresh 中进行触发
复制代码


  • ContextClosedEvent事件


org.springframework.context.support.AbstractApplicationContext#doClose 中进行触发
复制代码


  • ContextStartedEvent事件


org.springframework.context.support.AbstractApplicationContext#start中进行触发
复制代码


  • ContextStoppedEvent事件


org.springframework.context.support.AbstractApplicationContext#stop中进行触发
复制代码


可以参考上述我所介绍的debug方式逐一查看相关源代码

目录
相关文章
|
XML Java 数据格式
深度挖掘Spring IoC核心模块源码的宝藏
深度挖掘Spring IoC核心模块源码的宝藏
70 1
|
6月前
|
Java Maven 数据安全/隐私保护
代码优雅升级,提升开发效率:挖掘Spring AOP配置的学习宝藏!
代码优雅升级,提升开发效率:挖掘Spring AOP配置的学习宝藏!
|
设计模式 Java 数据库连接
Spring高手之路7——事件机制与监听器的全面探索
本篇文章为你详细解析了Spring的事件机制,包括了Spring事件模型的四个核心概念:事件源、事件、广播器、监听器。我们通过深入浅出的实例解析了如何自定义事件和监听器,以及如何在实际项目中应用。最后,我们还详细探讨了监听器和Bean的生命周期的关系。无论你是Spring初学者,还是有一定经验的开发者,阅读本文都将帮助你更深入地理解Spring的事件机制和监听器,掌握Spring框架的核心技术。
1082 0
Spring高手之路7——事件机制与监听器的全面探索
|
消息中间件 存储 Java
「Spring和Kafka」Kafka整合Spring 深入挖掘第2部分:Kafka和Spring Cloud Stream
「Spring和Kafka」Kafka整合Spring 深入挖掘第2部分:Kafka和Spring Cloud Stream
|
消息中间件 Java Kafka
「Spring和Kafka」Kafka整合Spring 深入挖掘 -第1部分
「Spring和Kafka」Kafka整合Spring 深入挖掘 -第1部分
|
消息中间件 架构师 Java
「首席架构师看Event Hub」Kafka的Spring 深入挖掘 -第1部分
「首席架构师看Event Hub」Kafka的Spring 深入挖掘 -第1部分
|
XML 存储 Java
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
189 0
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
|
XML 缓存 Java
Spring异步事件机制剖析
Spring异步事件机制剖析
218 0
|
Java Spring
《云栖社区特邀专家徐雷Java Spring Boot开发实战系列课程(第20讲):经典面试题与阿里等名企内部招聘求职面试技巧》电子版地址
云栖社区特邀专家徐雷Java Spring Boot开发实战系列课程(第20讲):经典面试题与阿里等名企内部招聘求职面试技巧
119 0
《云栖社区特邀专家徐雷Java Spring Boot开发实战系列课程(第20讲):经典面试题与阿里等名企内部招聘求职面试技巧》电子版地址
|
开发框架 Java Spring
GitHub榜一大哥!竟是Alibaba内部被疯狂转载的Spring全能指南?
spring相信大家都不会陌生! Spring 是目前主流的 Java Web 开发框架,是 Java 世界上最为成功的框架。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力。 Spring 由 Rod Johnson 创立,2004 年发布了 Spring 框架的第一版,其目的是用于简化企业级应用程序开发的难度和周期。本教程使用版本为 Spring 5.2。 Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。