当前位置: 首页 > article >正文

Spring监听的使用、原理、源码分析

一、原理分析

        Spring监听的核心原理就是观察者模式。本文将对应观察者模式分析Spring监听的使用和原理。文章较长,跳转记得使用文章右边的目录。

        1.1,观察者模式的组成

1,Subject:目标,即被观察者

        维护了观察者列表;

        提供观察者添加、删除方法;

        通知观察者方法;

        目标发生变化的发放,具体变化业务由子类实现(相当于又使用了抽象模版设计模式)

2,ConcreteSubject:具体目标,即被观察者子类

        实现目标具体变化业务

3,Observer:观察者

        根据目标变化,做出相应的反应。实际业务由子类实现

4,ConcreteObserver:具体观察者

        实现观察者的实际变化业务

        1.2,观察者模式原理

        当被观察者发生变化时,根据维护的观察者列表,在通知观察者方法中,遍历观察者列表依次通知,调用观察者的反应方法。

        2.1,Spring监听组成

1,Event:事件,相当于被观察者。非Spring Bean

        Spring提供了事件基类:ApplicationEvent extends EventObject,用户自定义事件只需继承基类实现自己的数据变化业务即可;

        监听列表(观察者列表)由Spring容器管理;

2,Listener:监听器,相当于观察者。Spring Bean

        Spring提供了监听器基类:

        ApplicationListenner<E extends ApplicationEvent> extends EventListener ,用户自定义监听器需指明监听事件,并实现自己的事件反应业务;

3,Publisher:事件发布器。Spring独有,Spring Bean

        观察者模式中,当目标发生变化后,需要由修改目标变化的线程主动调用目标的通知观察者方法,以此触发观察者的执行;

        Spring监听在事件发生后,由修改事件变化的线程调用Spring容器的事件发布方法,以此触发监听的执行。

        2.2,Spring监听原理

        Spring容器维护了事件-监听的容器:Map<Event, Set<Listener>>。当事件发布后,通过维护的事件-监听容器,找到对应的监听,通过反射,调用监听方法。

        由于事件非Spring Bean,故事件发布前,容器中并不存在事件-监听的关系。由于监听器在声明时已经指明了监听的事件,故在事件发布时,通过Listener Bean与事件作匹配对比,维护在Map容器中。

二、使用方式

        构造了一个最基础的springboot项目用于底座,实现了一个事件多个监听器的Demo,项目结构如下:

        1,定义事件

package com.example.simplespringlistener.events;

import org.springframework.context.ApplicationEvent;

public class MyEvent extends ApplicationEvent {

    private String priceChangedStr;

    public MyEvent(Object source, String priceChangedStr) {
        super(source);
        this.priceChangedStr = priceChangedStr;
    }

    public String getPriceChangedStr() {
        return priceChangedStr;
    }

    public void setPriceChangedStr(String priceChangedStr) {
        this.priceChangedStr = priceChangedStr;
    }
}

        2,定义监听器

 

        定义监听器共有三种方式: 

        2.1,实现基类:ApplicationListner<E extends ApplicationEvent>

package com.example.simplespringlistener.listeners;

import com.example.simplespringlistener.events.MyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;

@Service
public class MyListener1 implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
    }

}

        实现接口时,指明监听的事件,用于发布时找到事件对应的监听器。 

        2.2,使用注解:@EventListener

package com.example.simplespringlistener.listeners;

import com.example.simplespringlistener.events.MyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class MyListener2 {

    @EventListener(MyEvent.class)
    public void handlerEvent(MyEvent event) {
        System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
    }

}

        使用注解时,指明监听的事件,用于发布时找到事件对应的监听器。如果不指定监听的事件,该监听器将会监听所有事件。Spring在启动时,发布了框架所需要的一批事件,故自定义事件需要与框架定义的事件区分开来。

        2.3,使用注解:@TransactionalEventListener

package com.example.simplespringlistener.listeners;

import com.example.simplespringlistener.events.MyEvent;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Service
public class MyListener3 {

    // 使用默认执行机制时,phase属性可省略
    @TransactionalEventListener(value = MyEvent.class, phase = TransactionPhase.AFTER_COMPLETION)
    public void handlerEvent(MyEvent event) {
        System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
    }

}

         使用注解时,指明监听的事件,用于发布时找到事件对应的监听器。该注解需要依赖

<artifactId>spring-tx</artifactId>。@EventListener的区别在于:@EventListner无法指明监听器的执行时机。当主线程发布事件后,不管主线程发布后的事务是否提交,@EventListener监听器就开始执行。而@TransactionalEventListener默认在主线程提交事务后执行,还可以通过注解的phase属性调整监听器的执行时机,phase的可选值分别有:

public enum TransactionPhase {

	/**
	 * 事务提交前
	 */
	BEFORE_COMMIT,

	/**
	 * 事务提交后
	 */
	AFTER_COMMIT,

	/**
	 * 事务回滚后
	 */
	AFTER_ROLLBACK,

	/**
	 * 事务完成后
	 */
	AFTER_COMPLETION

        3,定义发布器

package com.example.simplespringlistener.publishers;

import com.example.simplespringlistener.events.MyEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

@Service
public class MyPublisher {

    @Autowired
    private ApplicationContext applicationContext;

    public void doPublish(String eventChangeStr) {
        applicationContext.publishEvent(new MyEvent(this, eventChangeStr));
    }

}

        4,测试Spring监听

package com.example.simplespringlistener.controller;

import com.example.simplespringlistener.publishers.MyPublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestListenerController {

    @Autowired
    private MyPublisher myPublisher;

    @PostMapping(value = "/updatePrice")
    public void updatePrice(@RequestParam(value = "newPrice") String newPrice) {
        myPublisher.doPublish(newPrice);
    }
}

        在业务需要的地方发布事件即可,执行结果如下:

三、源码分析

        根据上面的例子可知,Spring监听的入口在于事件发布,我们就从发布处查看源码

        从后面明显能知道:第一个是实际调用类,第二个是接口,第三个是测试用类。我们进第一类:

        进入该方法

        前面确定广播器类型的方法非核心方法,我们先沿着主流程查看,进入核心事件方法:multicastEvent()

        进入该方法后发现,该方法主要逻辑就是找到事件对应的监听器,然后调用监听。这两个分支都很重要,我们先查看“调用监听器” 的分支源码。

        1,调用监听器源码

        根据上文,监听器的实现有三种方式,每种方式的调用源码路径不同,下面将根据各种方式一一详解。

        1.1,实现基类ApplicationListner源码

1.1.1,调用监听器
1.1.2,调用的中间过程
1.1.3,调用onApplicationEvent()

 

        此时调用listener.onApplicationEvent()方法,该方法已被MyListener1继承并重写,则调用会直接进入MyListener1,执行监听器的方法,结束。

1.1.4,执行监听器方法

        1.2,使用注解@EventListener源码

1.2.1,调用监听器
1.2.2,调用的中间过程
1.2.3,调用onApplicationEvent()

        根据图1.2.1,此处的listener不是MyListener2,而是ApplicationListenerMethodAdapter,使用了适配器设计模式, 在该适配器中,赋值了几个重要属性:监听器Bean(MyListener2)、监听器方法、事件。通过适配器属性,将事件与监听器和具体方法产生关联,再通过反射调用方法。

        上图断点接着往里进入方法,如下图,进到了适配器类中:

1.2.4,进入适配器类

        查看该适配器的属性,如下图: 

1.2.5,查看适配器属性

        查看完属性后,接着图1.2.4往方法里进入,如下图:

1.2.6,处理参数和调用方法

        接着看doInvoke(args)方法,如下图:

1.2.7,调用方法

        获取MyListener2 Bean后,通过反射调用方法,进入到MyListener2的 handlerEvent()

        1.3,使用注解@TransactionalEventListener源码

        源码调用过程与@EventListener一样

        2,获取监听器源码

        看完了调用监听器的源码,知道了源码是怎么执行到自定义监听器的方法里了。接下来看源码的另一个分支:如何根据事件,找到对应的监听器?分支起点如下:

        这次源码要查看的方法是for循环里的getApplicationListeners(event, type),如下图:

        由上图可见,该方法的返回值是监听器集合,整体思路也很简单:

        步骤一:从 retrieverCache 中获取结果。retrieverCache 是个Map集合,map-key是类 ListenerCacheKey(该类包含了具体事件属性、发布器属性),map-value是类 CachedListenerRetriever(该类包含了监听器集合、监听器Bean名称集合),如下图:

        步骤二:如果map-value存在,就返回CachedListenerRetriever的监听器集合;

        步骤三:如果map-value不存在,创建CachedListenerRetriever 作为map-value放入retrieverCache map中,并在下面通过 retrieveApplicationListeners(eventType, sourceType, newRetriever) 方法 为新建的map-value赋予上图两个集合属性。最后返回该map-value即可。

        第一次发布事件时,retrieverCache map中没有事件对应的监听器集合,需要执行步骤三,接着查看步骤三方法源码,如下图:

        前面第一步原理分析中已经说明,每个监听器都是SpringBean,即for循环中的listeners包含了Spring启动时的所有监听器,如下图:

        接着查看源码是如何匹配事件-监听的,进入方法supports(listener, eventType, sourceType),如下图:

        又是适配器模式,通过适配器,最终通过this.declaredEventType.isAssignableFrom(eventType)) 判断是否匹配。即,根据监听器声明时指定的事件类型,与当前传入的事件class 做对比,监听器指定的事件如果是当前传入的事件的本类或子类,则表明当前监听器监听了当前事件。

        至此,Spring监听的源码已分析完毕,请再回头看看第一节的Spring监听原理分析,整个流程就非常清晰且简单了。


http://www.kler.cn/a/405654.html

相关文章:

  • HarmonyOS笔记5:ArkUI框架的Navigation导航组件
  • JVM(五、垃圾回收器)
  • react中Fragment的使用场景
  • GitLab|GitLab报错:PG::ConnectionBad: could not connect to server...
  • 北京申请中级职称流程(2024年)
  • ZYNQ-7020嵌入式系统学习笔记(1)——使用ARM核配置UART发送Helloworld
  • 【Linux】常用命令练习
  • 筑起数字堡垒:解析AWS高防盾(Shield)的全面防护能力
  • 【Fargo】基于mediasoup发rtp包及内存清理
  • Redis设计与实现第10章 -- RDB持久化 总结 (创建、载入、自动保存、文件结构)
  • 知识图谱介绍
  • AIVA 技术浅析(三):如何通过CNN捕捉音乐作品中的细节和模式
  • 【Linux】重定向,dup
  • docker和containerd的区别
  • C++之新的类功能与STL的变化
  • 进度条程序
  • 【日志】盛趣面试
  • 飞桨大模型PaddleOCR
  • 【UE5】在材质中计算模型在屏幕上的比例
  • 【Web前端】实现基于 Promise 的 API:alarm API
  • Qt模块学习 —— 数据库连接
  • 残酷的现实
  • docker 配置同宿主机共同网段的IP 同时通过通网段的另一个电脑实现远程连接docker
  • JVM基本结构(详细)
  • 社团管理新策略:SpringBoot技术解析
  • .net的winfrom程序 窗体透明打开窗体时出现在屏幕右上角