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

1. 基本介绍

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

1.1. 代码执行流程

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

基本步骤如下 :

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

2. 项目创建

2.1. 项目结构

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

2.2. maven


<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.0modelVersion>
  <parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.3.5.RELEASEversion>
    <relativePath/> 
  parent>
  <groupId>cn.knightzzgroupId>
  <artifactId>chain-responsibility-pattern-exampleartifactId>
  <version>0.0.1-SNAPSHOTversion>
  <name>chain-responsibility-pattern-examplename>
  <description>责任链模式demodescription>
  <properties>
    <java.version>1.8java.version>
  properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
    dependency>

    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <optional>trueoptional>
    dependency>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-testartifactId>
      <scope>testscope>
    dependency>
  dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-maven-pluginartifactId>
      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 
     * @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 
     * @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 
     * @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 
     * @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方法既可

你可能感兴趣的:(Java系列,责任链模式,SpringBoot,设计模式)