项目背景:
由于做的物联网项目,需求是接入子设备(智能家居设备),由于各种子设备厂家,设备型号都不一样,举个例子,美的空调和格力空调,都属于空调类,但是属于两个厂家,物模型能力大致相同但存在差异;
移动端又有自己需求展示对应子设备属性数据。举个例子:假设这次接入了一个双键开关,接到一个需求说必须在首页展示一个快捷开关控制按钮。(首页不是指设备详情/控制页,控制页是所有开关都可以控制的)。默认是第一个开关 按键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);