提升代码可读性与可维护性:利用责任链模式优化你的Spring Boot代码

news/2024/5/19 12:28:30 标签: 责任链模式, SpringBoot, 设计模式

1. 基本介绍

责任链是一种非常常见的设计模式, 具体我就不介绍了, 本文是讲解如何在SpringBoot中优雅的使用责任链模式

1.1. 代码执行流程

image-20230829201630365

基本步骤如下 :

  1. SpringBoot启动时, 需要获取 handler 对应Bean, 不同业务对应着不同的多个处理器, 比如 购票业务, 可能需要检查参数是否为空, 检测参数是否合法, 检测是否重复购票等等, 所以需要一个 mark 用于标记当前业务, 这样才能把相同的handler放到一起
  2. 然后就是通过 mark 将不同的handler 放到一起, 具体查看 3.7 核心加载类
  3. 然后实现一个方法, 通过传入 mark 和 参数去批量执行 对应部分的代码

2. 项目创建

2.1. 项目结构

img

2.2. maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>cn.knightzz</groupId>
  <artifactId>chain-responsibility-pattern-example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>chain-responsibility-pattern-example</name>
  <description>责任链模式demo</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

3. 代码编写

3.1. 实体类

这个类是用于存储实体类的

package cn.knightzz.pattern.dto.req;

/**
 * @author 王天赐
 * @title: PurchaseTicketReqDTO
 * @description:
 * @create: 2023-08-29 18:09
 */
public class PurchaseTicketReqDTO {

}

3.2. 枚举类

package cn.knightzz.pattern.common.enums;

/**
 * @author 王天赐
 * @title: TicketChainMarkEnum
 * @description: 存储标记责任链的注解
 * @create: 2023-08-29 18:10
 */
public enum TicketChainMarkEnum {

    /**
     * 用于标记购票的责任链过滤器
     */
    TRAIN_PURCHASE_TICKET_FILTER("train_purchase_ticket_filter");

    private String name;

    TicketChainMarkEnum(String name) {
        this.name = name;
    }
}

枚举类主要是用于标记某一类业务的责任链

3.3. 通用类

package cn.knightzz.pattern.context;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * @author 王天赐
 * @title: ApplicationContextHolder
 * @description:
 * @create: 2023-08-29 18:31
 */
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext CONTEXT;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.CONTEXT = applicationContext;
    }

    /**
     * Get ioc container bean by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return CONTEXT.getBean(clazz);
    }

    /**
     * Get ioc container bean by name and type.
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return CONTEXT.getBean(name, clazz);
    }

    /**
     * Get a set of ioc container beans by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return CONTEXT.getBeansOfType(clazz);
    }

    /**
     * Find whether the bean has annotations.
     *
     * @param beanName
     * @param annotationType
     * @param <A>
     * @return
     */
    public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
        return CONTEXT.findAnnotationOnBean(beanName, annotationType);
    }

    /**
     * Get ApplicationContext.
     *
     * @return
     */
    public static ApplicationContext getInstance() {
        return CONTEXT;
    }
}

ApplicationContextHolder 的作用是, 当Bean被创建时, 将Spring中存储Bean的容器注入到CONTEXT中, 这样我们就可以在其他类中使用 Bean

3.4. 通用责任链接口

package cn.knightzz.pattern.chain;

import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import org.springframework.core.Ordered;

/**
 * @author 王天赐
 * @title: AbstractChainHandler
 * @description:
 * @create: 2023-08-29 18:15
 */
public interface AbstractChainHandler<T> extends Ordered {

    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);

    /**
     * @return 责任链组件标识
     */
    String mark();
}

3.5. 购票责任链接口

package cn.knightzz.pattern.filter;

import cn.knightzz.pattern.chain.AbstractChainHandler;
import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketChainFilter
 * @description:
 * @create: 2023-08-29 18:10
 */
public interface TrainPurchaseTicketChainFilter<T extends PurchaseTicketReqDTO> extends AbstractChainHandler<PurchaseTicketReqDTO> {

    @Override
    default String mark() {
        return TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name();
    }
}

通过实现通过责任链接口, 编写默认 mark 方法, 用于标记当前责任链处理器集合

3.6. 购票责任链处理器

package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketParamNotNullChainHandler
 * @description:
 * @create: 2023-08-29 18:18
 */
@Component
public class TrainPurchaseTicketParamNotNullChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("参数不能为空 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketParamVerifyChainHandler
 * @description: 购票流程过滤器之验证参数是否有效
 * @create: 2023-08-29 18:23
 */
@Component
public class TrainPurchaseTicketParamVerifyChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("参数合法 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 20;
    }
}
package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketRepeatChainHandler
 * @description: 购票流程过滤器之验证乘客是否重复购买
 * @create: 2023-08-29 18:24
 */
@Component
public class TrainPurchaseTicketRepeatChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("未重复购票 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 30;
    }
}

3.7. 核心加载类

package cn.knightzz.pattern.context;

import cn.knightzz.pattern.chain.AbstractChainHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 王天赐
 * @title: AbstractChainContext
 * @description: CommandLineRunner:SpringBoot 启动完成后执行的回调函数
 * @create: 2023-08-29 18:27
 */
@Component
@Slf4j
public final class AbstractChainContext<T> implements CommandLineRunner {

    // CommandLineRunner:SpringBoot 启动完成后执行的回调函数


    // 存储责任链组件实现和责任链业务标识的容器
    // 比如:Key:购票验证过滤器 Val:HanlderA、HanlderB、HanlderC、......
    private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();


    public void handler(String mark, T requestParam) {
        List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        abstractChainHandlers.forEach(each -> each.handler(requestParam));
    }

    @Override
    public void run(String... args) throws Exception {
        // 通过ApplicationContextHolder获取所有的Bean
        Map<String, AbstractChainHandler> chainFilterMap =
                ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);

        chainFilterMap.forEach((beanName, bean) -> {

            // 获取指定类型的责任链集合, 如果没有就创建
            // 需要将同一个mark的放到一起
            List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
            if (CollectionUtils.isEmpty(abstractChainHandlers)) {
                abstractChainHandlers = new ArrayList<>();
            }
            // 添加到处理器集合中
            abstractChainHandlers.add(bean);
            // 对处理器集合顺序进行排序
            List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers
                    .stream()
                    .sorted(Comparator.comparing(Ordered::getOrder))
                    .collect(Collectors.toList());

            log.info("mark {} , bean : {} add container", bean.mark(), bean);

            //将排好序的Bean存入到容器等待运行时被调用
            abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);
        });

    }
}

这个类主要是需要实现 CommandLineRunner 接口, 这个接口提供一个run方法, 会在SpringBoot启动后执行

handler 方法

3.8. 基本使用

package cn.knightzz.pattern.service;

import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.context.AbstractChainContext;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * @author 王天赐
 * @title: TicketService
 * @description:
 * @create: 2023-08-29 19:04
 */
@Service
@RequiredArgsConstructor
public class TicketService {

    private final AbstractChainContext<PurchaseTicketReqDTO> purchaseTicketAbstractChainContext;

    public void purchase(PurchaseTicketReqDTO requestParam) {
        purchaseTicketAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name(), requestParam);
    }
}

如上面代码所示 , 使用时 直接调用 AbstractChainContext 提供的handler方法既可


http://www.niftyadmin.cn/n/4981607.html

相关文章

【教程】部署apprtc服务中安装google-cloud-cli组件的问题及解决

#0# 前置条件 已经安装完成node&#xff0c;grunt&#xff0c;node 组件和python pip包等。需要安装google-cloud-cli组件。 Ubuntu安装google-cloud-cli组件 apprtc项目运行需要google-cloud-cli前置组件&#xff0c;且运行其中的dev_appserver.py。 根据google官方的关于安…

正中优配:“核污染防治”炒作按下暂停键, 中电环保、建龙微纳大跌

连日大涨的核污染防治炒作“步伐”放缓。 8月29日上午&#xff0c;核污染防治概念股多数低开&#xff0c;截止到午间休市&#xff0c;此前4个交易日累计涨超80%的中电环保&#xff08;300172.SZ&#xff09;大跌6.76%。 8月28日晚间&#xff0c;中电环保发布异动公告&#xff…

一文总结Redis知识点

目录 为什么基于MySQL又出现Redis&#xff1f;Redis的优点&#xff1f;Redis支持的基本命令Redis支持的数据结构1 String2 List3 Set4 Sorted Set5 Hash6 Stream 消息队列7 Geospatial 地理空间8 Bitmap 位图9 Bitfield 位域10 HyperLogLog Redis是单线程还是多线程&#xff1f…

LINUX系统下ORACLE19C客户端安装步骤

服务器系统版本&#xff1a;CentOS 7.4 Oracle客户端安装包&#xff08;19C版本&#xff09;下载地址&#xff1a; https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html 现阶段19c版本已下载完毕&#xff0c;上传至服务器&#xff1b;…

LeetCode第11~15题解

CONTENTS LeetCode 11. 盛最多水的容器&#xff08;中等&#xff09;LeetCode 12. 整数转罗马数字&#xff08;中等&#xff09;LeetCode 13. 罗马数字转整数&#xff08;简单&#xff09;LeetCode 14. 最长公共前缀&#xff08;简单&#xff09;LeetCode 15. 三数之和&#xf…

Verilog基础:巴科斯范式(BNF)

相关阅读 Verilog基础专栏https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 由于Verilog HDL标准中对语法的描述使用了Backus Naur Form&#xff08;BNF&#xff09;。本文将对其中的约定进行描述。 小写单词&#xff0c;其中一些包含…

Android DataBinding 基础入门(学习记录)

目录 一、DataBinding简介二、findViewById 和 DataBinding 原理及优缺点1. findViewById的优缺点2. DataBinding的优缺点 三、Android mvvm 之 databinding 原理1. 简介和三个主要的实体DataViewViewDataBinding 2.三个功能2.1. rebind 行为2.2 observe data 行为2.3 observe …

如何在视频中插入Avatar SDK生成的动画形象

想要在视频中加入一些酷炫的动画形象吗?今天我会教你如何使用Avatar SDK这个工具轻松做到! 步骤1:在视频网站中下载素材 首先,打开你的视频网站&#xff0c;点击截图按钮。保存在下载文件夹下。 步骤2:去Avatar SDK网站生成动画 打开https://webdemo.avatarsdk.com/ 这个网…