springboot 使用责任链模式优化项目代码案例

项目背景:

        由于做的物联网项目,需求是接入子设备(智能家居设备),由于各种子设备厂家,设备型号都不一样,举个例子,美的空调和格力空调,都属于空调类,但是属于两个厂家,物模型能力大致相同但存在差异;

        移动端又有自己需求展示对应子设备属性数据。举个例子:假设这次接入了一个双键开关,接到一个需求说必须在首页展示一个快捷开关控制按钮。(首页不是指设备详情/控制页,控制页是所有开关都可以控制的)。默认是第一个开关 按键1 作为首页快捷控制,后期是可以更新的。

        

所以此时就需要对开关类产品属性做加工,需要指定 按键1 为默认的首页快捷控制;如果后续有很多产品都需要这种加工的方式,或者存在一个产品即需要用到开关类产品加工逻辑,又要走电机类产品加工逻辑,此时用策略模式就不太适合了。所以采用责任链模式,每个产品一个加工逻辑,执行完毕后自动往下执行。

定义一个产品责任链基类:

package com.xhwl.smarthome.service.handle;

import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Component;

/**
 * @author wangxinyu
 * @since 2022/2/25
 * 产品责任链基类,所有需要加工的产品必须实现该类
 */
@Setter
@Getter
@Component
public abstract class ProductChainHandle{

    static final int LOWEST_PRECEDENCE = -2147483648;
    static final int HIGHEST_PRECEDENCE = 2147483647;

    private ProductChainHandle next ;

    /**
     * 获取责任链执行顺序 , 顺序越大越后执行 ,不能重复 ,重复会被替代 ,因为底层用的TreeMap , KEY 必须唯一
     * 在 HandleConstant 常亮类定义 ,方便维护 ;
     * @return 顺序编号
     */
    public abstract int getOrder();

    public abstract JSONObject handleSubDeviceProperties(JSONObject propertiesObj , String productCode);

}

定义开关类:

package com.xhwl.smarthome.service.handle;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xhwl.smarthome.config.PropertiesConfig;
import com.xhwl.smarthome.constant.IotConstants;
import com.xhwl.smarthome.constant.MyConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author wangxinyu
 * @since 2022/2/25
 * 开关类产品处理器
 */
@Component
@Slf4j
public class SwitchProductChainHandle extends ProductChainHandle{

    @Override
    public int getOrder() {
        return HandleConstant.SWITCH_HANDLE;
    }

    /**
     * 处理开关产品逻辑
     * @param propertiesObj 开关产品的 properties属性
     * @param productCode 产品code
     * @return
     */
    @Override
    public JSONObject handleSubDeviceProperties(JSONObject propertiesObj, String productCode) {
        log.info("执行SwitchProductChainHandle-----------");
        if(Arrays.asList(IotConstants.SWITCHES).contains(productCode)){
            doSomeThing(); //
        }else{
            if(getNext() != null){
                propertiesObj = getNext().handleSubDeviceProperties(propertiesObj,productCode);
            }
        }
        /* 开关类产品处理逻辑 end */

        return propertiesObj;
    }
}

定义电机类:

package com.xhwl.smarthome.service.handle;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xhwl.smarthome.config.PropertiesConfig;
import com.xhwl.smarthome.constant.IotConstants;
import com.xhwl.smarthome.constant.MyConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Iterator;

/**
 * @author wangxinyu
 * @since 2022/2/25
 * 电机类产品处理器
 */
@Component
@Slf4j
public class ElectricMachineryProductChainHandle extends ProductChainHandle {

    @Override
    public int getOrder() {
        return HandleConstant.ELECTRIC_MACHINERY_HANDLE;
    }

    @Override
    public JSONObject handleSubDeviceProperties(JSONObject propertiesObj, String productCode) {
        log.info("执行ElectricMachineryProductChainHandle-----------");
        if(Arrays.asList(IotConstants.ELECTRIC_MACHINERY).contains(productCode)){
            doSomeThing(); 
        }else{
            if(getNext() != null){
                propertiesObj = getNext().handleSubDeviceProperties(propertiesObj,productCode);
            }
        }
        return propertiesObj ;
    }
}

        由于我这里对责任链做了一个增强,思路参考与SpringCloud 网关组件 gateway ,新增了一个Order 顺序 ,否则一般责任链模式需要手动set Next 元素, 非常不适用与项目中;所以为了保证Order 顺序的唯一性 ,定义了一个常量类来统一管理,便于观察 ;

 

package com.xhwl.smarthome.service.handle;

/**
 * @author wangxinyu
 * @since 2022/2/28
 * 产品责任链执行顺序常亮定义,方便管理,避免重复;
 */
public class HandleConstant {

    /* 开关类产品 */
    public static final int SWITCH_HANDLE = 0;

    /* 电机类产品 */
    public static final int ELECTRIC_MACHINERY_HANDLE = 1;

}

现在我们需要将各个继承了 ProductChainHandle 抽象类的 子类全部找出,并按照order顺序进行set Next ;

这里有两种方式找到父类所有子类,一个是用到了Class的 isAssignableFrom(Class clz) , 方法 , 该方法接收一个类 ,用来比较 clz 是否是 调用 类的 子类 或者 本身 ;

所以定义了一个ClassUtil工具类

package com.xhwl.smarthome.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;


/**
 * @author wangxinyu
 * @since 2022/2/25
 * 反射基础方法类
 */
public class ClassUtil {

    private ClassUtil(){}

    /**
     * 获取父类中的所有子类
     * @param fatherClass 父类
     * @return 子类集合
     */
    @SuppressWarnings({"unchecked","rawtypes"})
    public static List<Class> getSonClass(Class fatherClass){
        List<Class> sonClassList = new ArrayList<>();
        String packageName = fatherClass.getPackage().getName();
        List<Class> packageClassList = getPackageClass(packageName);
        for (Class clazz : packageClassList) {
            if (fatherClass.isAssignableFrom(clazz) && !fatherClass.equals(clazz)) {
                sonClassList.add(clazz);
            }
        }
        return sonClassList;
    }

    /**
     * 获取包名下的所有类的全限定明集合
     * @param packageName 包名
     * @return 类文件集合 ,即类的全限定名
     */
    @SuppressWarnings({"rawtypes"})
    public static List<Class> getPackageClass(String packageName) {
        ClassLoader loader = ClassUtil.class.getClassLoader();
        String path = packageName.replace(".", "/");
        Enumeration<URL> resources = null;
        try {
            resources = loader.getResources(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<File> fileList = new ArrayList<>();
        assert resources != null;
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            fileList.add(new File(resource.getFile()));
        }
        ArrayList<Class> classList = new ArrayList<>();
        for (File file : fileList) {
            classList.addAll(findClass(file, packageName));
        }
        return classList;
    }

    /**
     * 通过资源目录获取指定包名下的所有类文件
     * @param file File 对象
     * @param packageName 包名
     * @return 类文件集合 ,即类的全限定名
     */
    @SuppressWarnings({"rawtypes"})
    public static List<Class> findClass(File file, String packageName) {
        List<Class> classList = new ArrayList<>();
        if (!file.exists()) {
            return classList;
        }
        File[] fileArray = file.listFiles();
        assert fileArray != null;
        for (File subFile : fileArray) {
            if (subFile.isDirectory()) {
                assert !file.getName().contains(".");
                classList.addAll(findClass(subFile, packageName + "." + subFile.getName()));
            } else if (subFile.getName().endsWith(".class")) {
                try {
                    classList.add(Class.forName(packageName + "." + subFile.getName().split(".class")[0]));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return classList;
    }
}

但是该方法有弊端,若在linux服务器中编译的目录不受控制,则会存在本地可以获取到所有子类,服务器上获取不到的现象 ;(我这里出现了)

推荐第二种方式,将上述所有责任链的类加上 @Compoent 注解 让Spring 管理 ,然后采用Spring 容器获取 父类的子类 方法 获取所有子类

ApplicationContext applicationContext = BeanContext.getApplicationContext();
        Map<String, ProductChainHandle> sonClass = applicationContext.getBeansOfType(ProductChainHandle.class);

获取所有子类的方法介绍完毕,现在定义一个初始化类,获取所有子类

package com.xhwl.smarthome.service.handle;

import com.xhwl.smarthome.config.BeanContext;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author wangxinyu
 * @since 2022/2/25
 * 这里有三种方式:
 * 1、采用包名扫描 ProductChainHandle 所有子类 + @PostConstruct,该方法本地可以,linux不行,猜测是目录结构不同导致的
 * 2、采用BeanContext.getApplicationContext() + @PostConstruct 扫描 ProductChainHandle 所有子类 ,导致的问题是ApplicationContext 获取不到
 * 空指针异常 。 该方法也是本地可以,linux不行,包括多个人的本地都是可以的,比如彭枭本地运行2方法也是可以的 ;
 * 导致原因未知 ,或许是 jdk 版本不同 ,本地的1.8 小版本 大于 服务器上的 ;
 * 3、采用BeanContext.getApplicationContext() + ApplicationListener< ContextRefreshedEvent> ,该方法本地测试可以的,但是会监听上下文改变而变化
 * 导致 initialize 会执行两次 ,比如 spring 的上下文执行一次 ,spingmvc 执行一次 (spring 父容器概念) , 则与业务不匹配;
 * 4、采用BeanContext.getApplicationContext() + ApplicationRunner ,该方法本地可以 ,测试环境也是可以的 ,而且也只会执行一次方法。
 *
 * 开发代码就好比老中医治病 , 开发者说的最多的就是 "wo 本地可以啊"
 */
@Component
@SuppressWarnings({"all"})
public class HandleInit implements ApplicationRunner {

    static Map<Integer,Class> objects = new TreeMap<>();
    static List<Class> list = new ArrayList<>();

//    @PostConstruct
//    public void initialize(){
//        List<Class> sonClass = ClassUtil.getSonClass(ProductChainHandle.class);
//        ApplicationContext applicationContext = BeanContext.getApplicationContext();
//        Map<String, ProductChainHandle> sonClass = applicationContext.getBeansOfType(ProductChainHandle.class);
//        for (Class aClass : sonClass) {
//            compareOrderRange(aClass);
//        }
//        sonClass.forEach((k,v) -> {
//            compareOrderRange(v);
//        });
//        list = putAllItemsInList();
//    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        /* 获取所有子类 */
        ApplicationContext applicationContext = BeanContext.getApplicationContext();
        Map<String, ProductChainHandle> sonClass = applicationContext.getBeansOfType(ProductChainHandle.class);
        sonClass.forEach((k,v) -> {
            compareOrderRange(v);
        });
        list = putAllItemsInList();
    }

    private void compareOrderRange(ProductChainHandle productChainHandle){
        int order = productChainHandle.getOrder();
        if(order > ProductChainHandle.HIGHEST_PRECEDENCE || order < ProductChainHandle.LOWEST_PRECEDENCE){
            throw new RuntimeException("产品执行器Order最大范围是" + ProductChainHandle.HIGHEST_PRECEDENCE + " -> " +ProductChainHandle.LOWEST_PRECEDENCE);
        }
        objects.put(order , productChainHandle.getClass());
    }

    private void compareOrderRange(Class clazz){
        try {
            Method getOrder = clazz.getMethod("getOrder");
            int order = (int) getOrder.invoke(BeanContext.getBean(clazz));
            if(order > ProductChainHandle.HIGHEST_PRECEDENCE || order < ProductChainHandle.LOWEST_PRECEDENCE){
                throw new RuntimeException("产品执行器Order最大范围是" + ProductChainHandle.HIGHEST_PRECEDENCE + " -> " +ProductChainHandle.LOWEST_PRECEDENCE);
            }
            objects.put(order , clazz);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private List<Class> putAllItemsInList(){
        List<Class> list = new ArrayList<>();
        objects.forEach((k,v) -> list.add(v));
        return list;
    }

}

然后定义一个执行器,去执行责任链的加工逻辑

package com.xhwl.smarthome.service.handle;

import com.alibaba.fastjson.JSONObject;
import com.xhwl.smarthome.config.BeanContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author wangxinyu
 * @since 2022/2/28
 * 产品责任链工具类
 */
@SuppressWarnings({"all"})
@Component
@Slf4j
public class HandleUtil extends HandleInit{

    /**
     * 执行所有产品加工逻辑
     * @param propertiesObj properties 属性
     * @param productCode 产品code
     * @return 加工后的properties 属性
     */
    public JSONObject executeAllChainHandle(JSONObject propertiesObj , String productCode){
        log.info("----------executeAllChainHandle----------进入") ;
        ProductChainHandle productChainHandle = setHandleNext();
        return productChainHandle.handleSubDeviceProperties(propertiesObj,productCode);
    }

    /**
     * 将 ProductChainHandle 的子类按照 order 顺序 排列 ;
     * @return
     */
    private ProductChainHandle setHandleNext(){
        log.info("------setHandleNext--------进入");
        int size = list.size();
        if(size == 1){
            return (ProductChainHandle) BeanContext.getBean(list.get(0));
        }
        log.info("------size--------{}",size);
        ProductChainHandle currHandle = (ProductChainHandle) BeanContext.getBean(list.get(size-1));
        for (int i = size-2; i >= 0; i--) {
            ProductChainHandle nextHandle = (ProductChainHandle) BeanContext.getBean(list.get(i));
            nextHandle.setNext(currHandle);
            currHandle = nextHandle;
        }
        log.info("------setHandleNext--------执行完毕");

        return currHandle;
    }


}

最后使用处位置代码:

/* 产品加工 */
HandleUtil handleUtil = BeanContext.getBean(HandleUtil.class);
handleObj = handleUtil.executeAllChainHandle(propertiesObj, productCode);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值