从责任链模式聊到aware接口

news2025/4/2 6:22:13

从责任链模式聊到aware接口

责任链是什么?

责任链模式是一种行为型设计模式,将多个对象连接成一条链,并且沿着这条链传递请求,让多个对象都有机会处理这个请求,请求会顺着链传递,直到某个对象处理它为止。

它主要避免了请求发送者和接受者之间的耦合,增强了系统的灵活性和可扩展性。

责任链模式的特点:

  1. 解耦请求发送者与接收者:请求发送者无需知道请求是由哪个处理器处理的。

  2. 动态组合处理器:可以灵活地动态改变链的结构。

  3. 请求沿链传递:请求可以由链中的一个或多个处理器处理。

一般用在什么场景?

  1. 请求需要多个处理器:例如日志记录的不同级别处理。

  2. 动态指定处理流程:请求的处理方式不固定,依赖于运行时的链条结构。

  3. 消除条件分支:用责任链代替代码中的 if-elseswitch-case 语句。

我们先来看一个责任链模式的简易版代码

// 处理器接口
abstract class Handler {
    protected Handler nextHandler;

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleRequest(String request);
}

// 具体处理器A
class ConcreteHandlerA extends Handler {
    @Override
    public void handleRequest(String request) {
        if ("A".equals(request)) {
            System.out.println("ConcreteHandlerA handled request: " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.println("No handler for request: " + request);
        }
    }
}

// 具体处理器B
class ConcreteHandlerB extends Handler {
    @Override
    public void handleRequest(String request) {
        if ("B".equals(request)) {
            System.out.println("ConcreteHandlerB handled request: " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.println("No handler for request: " + request);
        }
    }
}

// 客户端
public class Main {
    public static void main(String[] args) {
        Handler handlerA = new ConcreteHandlerA();
        Handler handlerB = new ConcreteHandlerB();

        handlerA.setNextHandler(handlerB);

        handlerA.handleRequest("A"); // Output: ConcreteHandlerA handled request: A
        handlerA.handleRequest("B"); // Output: ConcreteHandlerB handled request: B
        handlerA.handleRequest("C"); // Output: No handler for request: C
    }
}

看完这个代码,可能很多人会觉得很简单,但这里我抛出两个问题

1.这不就是一个面向对象的一个思想吗,把责任链的判断逻辑封装成对象?那为何叫做责任链模式

2.这抛开“OOP”的解耦,真的做到了进一步解耦吗?

好我们来逐一解答:

问题一:

我觉得这就是责任链模式,责任链模式是一种行为型设计模式,将多个对象连接成一条链。把行为封装成对象既是责任链的定义也是oop的思想。

问题二:

我觉得解耦度还不够,因为假设这个时候需要加一个责任链判定点。有两种做法

1.直接在ConcreteHandlerA orB里面去填加代码

2.写一个ConcreteHandlerN,然后在对应的接口把ConcreteHandlerN加进去

不难发现,这里的两种方法都要去修改已经写好的代码,这是不太完美的

所以我们要对这个责任链进行改进,引入一个东西 ApplicationContextAware

ApplicationContextAware

可能对spring不太熟悉的人不知道这是什么,我们先简单介绍一下

ApplicationContextAware 是 Spring 框架中的一个核心接口,用于让 Bean 感知到 Spring 应用上下文(ApplicationContext),从而能够与容器进行交互。以下是它的核心概念

关键能力

  • 访问容器资源:通过 ApplicationContext 获取其他 Bean、环境变量、国际化消息等。

  • 监听容器事件:可以注册监听器(ApplicationListener)响应容器事件(如上下文刷新、关闭等)。

  • 动态控制容器行为:例如动态注册新 Bean 或修改 Bean 定义。

我们怎么使用ApplicationContextAware到责任链中

这里我们使用了他的第一个能力

访问容器资源:通过 ApplicationContext 获取其他 Bean、环境变量、国际化消息等

 
public final class MerchantAdminChainContext<T> implements ApplicationContextAware, CommandLineRunner {
 private ApplicationContext applicationContext;
​ private final Map<String, List<MerchantAdminAbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();
@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
   Map<String, MerchantAdminAbstractChainHandler> chainFilterMap = applicationContext.getBeansOfType(MerchantAdminAbstractChainHandler.class);
   //以下就是根据责任链上的一些属性,比如mark(声明是干嘛的),order(优先级)、用一个map按标记分组,按order排序
   //然后再写一个hander调用bean对责任链判断就ok了
}

applicationContext.getBeansOfType(MerchantAdminAbstractChainHandler.class);这个方法要记住,这个方法获取了责任链接口下的所有实现类,String 类型的键(Key)表示 Bean 的名称,而 T 类型的值(Value)是对应的 Bean 实例。

那我们怎么使用

 private final MerchantAdminChainContext merchantAdminChainContext;
  
    @Override
    public void createCouponTemplate(CouponTemplateSaveReqDTO requestParam) {
    
        merchantAdminChainContext.handler(ChainBizMarkEnum.MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name(), requestParam);

代码解疑

可能有朋友不清楚MerchantAdminChainContext<T>为什么会 implements CommandLineRunner,这是干嘛的

CommandLineRunner 在这里的用途是 在 Spring Boot 应用启动完成后立即执行初始化逻辑

核心作用

  • 延迟初始化:在 Spring 上下文完全加载后(所有 Bean 初始化完成),才执行责任链处理器的扫描、分组和排序操作。

  • 确保 Bean 就绪:保证 applicationContext.getBeansOfType(...) 能获取到完整的 Bean 集合,避免因 Bean 未初始化导致的空指针问题。

现在回头看第二个问题,这样还耦合吗? 我们在创建优惠券模块(这里假设场景是对优惠券创建合法性的判断)只用到了MerchantAdminChainContext,而如果想要假如新的判断逻辑只需要继续写一个类去实现MerchantAdminAbstractChainHandler就ok了,耦合度相当低。

但是我们可以看到implements ApplicationContextAware之后我们还是调用了ApplicationContext applicationContext;那我能不能直接用ApplicationContext。

这里我觉得答案是可以的

那我为什么要先说ApplicationContextAware的方案呢,因为我初学这个用法的时候是学的网上一个比较火的项目,他是这么写的

我们先看看

ApplicationContextAware与注入ApplicationContext的差异

​维度​**ApplicationContextAware**​**@Autowired**
​调用时机在Bean初始化后由Spring容器主动调用setApplicationContext在构造函数或@PostConstruct后注入
​适用场景需要动态获取Bean(如插件化、运行时决策)常规依赖注入,Bean已被Spring管理
​代码耦合度需实现接口,增加少量代码冗余更简洁,符合Spring最佳实践
​内存泄漏风险需注意避免静态持有ApplicationContext无此风险
为何有时必须用ApplicationContextAware
  • 非Spring类:无法使用@Autowired,必须通过接口或手动传递上下文。

  • 容器初始化前访问:如Filter的init方法,此时依赖注入尚未完成。

  • 静态上下文访问:若需静态方法访问容器(如工具类),必须通过ApplicationContextAware或单例持有上下文。

为什么ApplicationContextAware能获取

以一个实现了ApplicationContextAware的Bean为例,流程如下:

  1. Bean实例化:Spring通过构造函数创建Bean实例。

  2. 属性填充:填充Bean的依赖属性(如@Autowired注入的字段)。

  3. BeanPostProcessor处理:

    1. ApplicationContextAwareProcessor在属性填充后、初始化方法(如@PostConstruct)执行前被调用。

    2. 检测Bean是否实现了ApplicationContextAware,若是,则调用setApplicationContext注入上下文。

  4. 初始化完成:执行@PostConstruct方法或自定义初始化逻辑。

class ApplicationContextAwareProcessor implements BeanPostProcessor {
    private final ConfigurableApplicationContext applicationContext;

    public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(applicationContext);
        }
        return bean;
    }
}

Spring的Bean生命周期中,BeanPostProcessor接口允许在Bean初始化前后插入自定义逻辑。ApplicationContextAware的实现依赖于一个特殊的BeanPostProcessor:**ApplicationContextAwareProcessor**。

  • ApplicationContextAwareProcessor的作用: 在Bean实例化完成后、属性填充之前,检查该Bean是否实现了ApplicationContextAware接口。如果是,则在后续流程中调用其setApplicationContext方法。

  • 源码位置org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory类中的populateBean方法会调用invokeAwareMethods,进而触发ApplicationContextAwareProcessor的逻辑。

再说说实例化与初始化的区别吧

看看代码

@Component
public class OrderService {
    private String orderId;
    
    // 实例化:Spring调用构造函数
    public OrderService() {
        System.out.println("实例化:OrderService构造函数");
    }
    
    // 初始化:依赖注入(属性填充)
    @Autowired
    public void setOrderId(String orderId) {
        this.orderId = orderId;
        System.out.println("初始化:依赖注入orderId");
    }
    
    // 初始化:@PostConstruct方法
    @PostConstruct
    public void init() {
        System.out.println("初始化:@PostConstruct方法");
    }
}

可能有人不知道初始化到底是干什么的可以在看看【为什么要指定bean的初始化方法直接调用不香吗】https://www.bilibili.com/video/BV1aJ4m1b7cZ?vd_source=f12c29722f73a19d4d78325e01363bcb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos8上实现lvs集群负载均衡nat模式

1.背景&#xff1a; 个人&#xff08;菜鸟&#xff09;学习笔记&#xff0c;学点记下来&#xff0c;给未来的自己看。高手看了也请多指点。 按照课程讲&#xff0c;lvs是我国大神开发的负载均衡程序&#xff0c;被收录进内核&#xff0c;只要安装时内核里有它&#xff0c;它就…

影响HTTP网络请求的因素

影响 HTTP 网络请求的因素 1. 带宽 2. 延迟 浏览器阻塞&#xff1a;浏览器会因为一些原因阻塞请求&#xff0c;浏览器对于同一个域名&#xff0c;同时只能有4个连接&#xff08;这个根据浏览器内核不同可能会有所差异&#xff09;&#xff0c;超过浏览器最大连接数限制&…

(UI自动化测试web端)第二篇:元素定位的方法_css定位之css选择器

看代码里的【find_element_by_css_selector( )】( )里的表达式怎么写&#xff1f; 文章介绍了第三种写法css选择器&#xff0c;你要根据网页中的实际情况来判断自己到底要用哪一种方法来进行元素定位。每种方法都要多练习&#xff0c;全都熟了之后你在工作当中使用起来元素定位…

MPU6050模块详解:从原理到STM32驱动指南(上) | 零基础入门STM32第八十九步

主题内容教学目的/扩展视频加速度传感器电路连接。手册分析。驱动程序&#xff0c;读出数据。能读出3轴数据。 师从洋桃电子&#xff0c;杜洋老师 &#x1f4d1;文章目录 一、MPU6050模块介绍1.1 核心特性1.2 模块化优势 二、MPU6050模块连接方法2.1 硬件连接2.2 电源注意事项 …

STM32 MODBUS-RTU主从站库移植

代码地址 STM32MODBUSRTU: stm32上的modbus工程 从站 FreeModbus是一个开源的Modbus通信协议栈实现。它允许开发者在各种平台上轻松地实现Modbus通信功能&#xff0c;包括串口和以太网。FreeMODBUS提供了用于从设备和主站通信的功能&#xff0c;支持Modbus RTU和Modbus TCP协…

架构师面试(二十二):TCP 协议

问题 今天我们聊一个非常常见的面试题目&#xff0c;不管前端还是后端&#xff0c;也不管做的是上层业务还是底层框架&#xff0c;更不管技术方向是运维还是架构&#xff0c;都可以思考和参与一下哈&#xff01; TCP协议无处不在&#xff0c;我们知道 TCP 是基于连接的端到端…

程序自动化填写网页表单数据

1 背景介绍 如何让程序自动化填写网页表单数据&#xff0c;特别是涉及到批量数据情况时&#xff0c;可以减少人力。下面是涉及到的一些场景&#xff0c;都可以通过相关自动化程序实现。 场景1 场景1&#xff0c;领导安排&#xff0c;通过相关省、市、县、乡镇数据&#xff0…

Razer macOS v0.4.10快速安装

链接点这里下载最新的 .dmg 文件。将下载的 .dmg 映像文件拖入 应用程序 文件夹中。若首次打开时出现安全警告【什么扔到废纸篓】&#xff0c;这时候点击 Mac 的“系统偏好设置”-> “安全性与隐私”-> “通用”&#xff0c;然后点击底部的 “打开”。【或者仍然打开】 对…

如何自动化同义词并使用我们的 Synonyms API 进行上传

作者&#xff1a;来自 Elastic Andre Luiz 了解如何使用 LLM 来自动识别和生成同义词&#xff0c; 使术语可以通过程序方式加载到 Elasticsearch 同义词 API 中。 提高搜索结果的质量对于提供高效的用户体验至关重要。优化搜索的一种方法是通过同义词自动扩展查询词。这样可以更…

一. 相机模组摆放原理

1. 背景&#xff1a; 相机开发时经常出现因模组摆放问题&#xff0c;导致相机成像方向异常。轻则修改软件、模组返工&#xff0c; 重则重新修改堆叠&#xff0c;影响相机调试进度。因此&#xff0c;设计一个模型实现模组摆放纠错很有必要。 2. 原理&#xff1a; 2.1 口诀&am…

【C++游戏引擎开发】《线性代数》(1):环境配置与基础矩阵类设计

一、开发环境配置 1.1 启用C 20 在VS2022中新建项目后右键项目 1.2 启用增强指令集 1.3 安装Google Test vcpkg安装使用指南 vcpkg install gtest:x64-windows# 集成到系统目录&#xff0c;只需要执行一次&#xff0c;后续安装包之后不需要再次执行 vcpkg integrate inst…

sqli-labs靶场 less 8

文章目录 sqli-labs靶场less 8 布尔盲注 sqli-labs靶场 每道题都从以下模板讲解&#xff0c;并且每个步骤都有图片&#xff0c;清晰明了&#xff0c;便于复盘。 sql注入的基本步骤 注入点注入类型 字符型&#xff1a;判断闭合方式 &#xff08;‘、"、’、“”&#xf…

基于大模型的知识图谱搜索的五大核心优势

在传统知识图谱与生成式AI融合的浪潮中&#xff0c;基于大模型的知识图谱搜索正成为新一代智能检索的标杆技术&#xff0c;飞速灵燕智能体平台就使用了该技术&#xff0c;其核心优势体现在&#xff1a; 1. 语义穿透力升级 突破关键词匹配局限&#xff0c;通过大模型的深层语义…

【MySQL】从零开始:掌握MySQL数据库的核心概念(五)

由于我的无知&#xff0c;我对生存方式只有一个非常普通的信条&#xff1a;不许后悔。 前言 这是我自己学习mysql数据库的第五篇博客总结。后期我会继续把mysql数据库学习笔记开源至博客上。 上一期笔记是关于mysql数据库的增删查改&#xff0c;没看的同学可以过去看看&#xf…

Java版Manus实现来了,Spring AI Alibaba发布开源OpenManus实现

此次官方发布的 Spring AI Alibaba OpenManus 实现&#xff0c;包含完整的多智能体任务规划、思考与执行流程&#xff0c;可以让开发者体验 Java 版本的多智能体效果。它能够根据用户的问题进行分析&#xff0c;操作浏览器&#xff0c;执行代码等来完成复杂任务等。 项目源码及…

鸿蒙UI开发

鸿蒙UI开发 本文旨在分享一些鸿蒙UI布局开发上的一些建议&#xff0c;特别是对屏幕宽高比发生变化时的应对思路和好的实践。 折叠屏适配 一般情况&#xff08;自适应布局/响应式布局&#xff09; 1.自适应布局 1.1自适应拉伸 左右组件定宽 TypeScript //左右定宽 Row() { …

Elasticsearch-实战案例

一、没有使用Elasticsearch的查询速度698ms 1.数据库模糊查询不走索引&#xff0c;在数据量较大的时候&#xff0c;查询性能很差。需要注意的是&#xff0c;数据库模糊查询随着表数据量的增多&#xff0c;查询性能的下降会非常明显&#xff0c;而搜索引擎的性能则不会随着数据增…

IP数据报报文格式

一 概述 IP数据报由两部分组成&#xff1a;首部数据部分。首部的前一部分是固定长度&#xff0c;一共20字节大小&#xff0c;是所有IP数据报文必须具有的&#xff1b;固定部分后面是一些可选字段&#xff0c;其长度是可变的。 二 首部固定部分各字段意义 &#xff08;1&…

openEuler24.03 LTS下安装Kafka集群

目录 前提条件 Kafka集群规划 下载Kafka 解压 设置环境变量 配置Kafka 分发到其他机器 分发安装文件 分发环境变量 启动Kafka 测试Kafka 关闭Kafka 集群启停脚本 问题及解决 前提条件 安装好ZooKeeper集群&#xff0c;可参考&#xff1a;openEuler24.03 LTS下安…

qt QQuaternion详解

1. 概述 QQuaternion 是 Qt 中用于表示三维空间中旋转的四元数类。它包含一个标量部分和一个三维向量部分&#xff0c;可以用来表示旋转操作。四元数在计算机图形学中广泛用于平滑的旋转和插值。 2. 重要方法 默认构造函数 QQuaternion::QQuaternion(); // 构造单位四元数 (1…