一. 背景:
最近小熙被问到一个场景拓展,是关于匹配规则的设计如何高拓展,由此将实现分享给大家。
二. 代码:
1. 介绍:
在经历技术调研和场景业务分析以及后续的考虑之后,最终没有选择规则引擎这些,而是采用更为切合业务的自研(主要也是由于规则简单,但又多变),分两部分 注解释意 和 Predicate过滤。
2. 注解代码:
其中注解是本业务中使用的(方便举例),可以直接看注解工具类
(1)注解
目前业务只需获取name属性,如有其他需求可以自行拓展
/**
* @author chengxi
* @date 2023/3/27 17:21
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface JudgeExpressExcludeField {
public String name() default "";
}
/**
* @author chengxi
* @date 2023/3/27 17:08
*/
@Documented
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JudgeExpressMatchField {
public String name() default "";
}
(2)注解工具类:
这里目前主要是获取name的value,如有拓展需要自行编写
/**
* @author chengxi
* @date 2023/3/28 11:10
* @blog https://blog.csdn.net/weixin_41133233?type=blog
*/
public class AnnotationUtil {
/**
* 获取注解key-value-list
*
* @param object
* @param annotationClass
* @return
* @param <T>
*/
public static <T extends Annotation> List<KeyValueDto> queryAnnotationKeyValueList(Object object, Class<? extends Annotation> annotationClass) {
//KeyValueDto是实体类方便返回也可以用map
List<KeyValueDto> keyValueDtoList = new ArrayList();
try {
//java反射机制获取所有字段名称
Field[] fields = object.getClass().getDeclaredFields();
//遍历循环方法并获取对应的字段名称
for (Field field : fields) {
field.setAccessible(true);
// 判断是否方法上存在注解 JudgeExpressMatchField
boolean annotationPresent = field.isAnnotationPresent(annotationClass);
if (annotationPresent) {
// 获取自定义注解对象
String keyName = "";
T annotation = (T) field.getAnnotation(annotationClass);
//获取被代理的对象
InvocationHandler invo = Proxy.getInvocationHandler(annotation);
Map map = (Map) getFieldValue(invo, "memberValues");
if (map != null) {
keyName = (String) map.get("name");
}
if (StringUtils.isNotEmpty(keyName)) {
// 根据对象获取注解值
Object o = field.get(object);
keyValueDtoList.add(new KeyValueDto(keyName, o == null ?"":o.toString()));
}
}
}
// 排序(按照方法名称排序)
Collections.sort(keyValueDtoList);
} catch (Exception e) {
PrintUtil.printCatchException(e, "获取注解key-value-list异常:");
}
return keyValueDtoList;
}
/**
* 获取字段值
*
* @param object
* @param property
* @return
* @param <T>
*/
public static <T> Object getFieldValue(T object, String property) {
if (object != null && property != null) {
Class<T> currClass = (Class<T>) object.getClass();
try {
Field field = currClass.getDeclaredField(property);
field.setAccessible(true);
return field.get(object);
} catch (NoSuchFieldException e) {
PrintUtil.printCatchException(e, currClass + " has no property: " + property);
throw new IllegalArgumentException(currClass + " has no property: " + property);
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
3. KeyValueDto实体类代码:
/**
* @author chengxi
* @date 2023/3/27 17:21
*/
public class KeyValueDto implements Comparable<KeyValueDto>{
private String key;
private String value;
public KeyValueDto(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int compareTo(KeyValueDto o) {
return o.getKey().compareTo(this.key);
}
@Override
public String toString() {
return "{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
}
4. 实体里注解使用代码:
此处截取一部分展示,注意匹配和校验的name是需要一一对应的
(1)JudgeExpressMatchField注解:
/**
* 省
*/
@JudgeExpressMatchField(name = "province")
private String province;
/**
* 市
*/
@JudgeExpressMatchField(name = "city")
private String city;
/**
* 区
*/
@JudgeExpressMatchField(name = "district")
private String district;
(2)JudgeExpressExcludeField注解:
/**
* 省
*/
@JudgeExpressExcludeField(name = "province")
private String province;
/**
* 市
*/
@JudgeExpressExcludeField(name = "city")
private String city;
/**
* 区
*/
@JudgeExpressExcludeField(name = "district")
private String district;
5. 示例业务实现代码:
(1)在filter中调用此Predicate
(2)此处示例,是将符合排除规则的,仅能匹配规则的,做对应的过滤
(3)在不变大体业务逻辑下,这里是不需要修改的,只需在对应实体类中,将比较的字段加上对应注解,即可自动拓展
@Override
public Predicate<RulesDto> judgeExpressMatch(DocOrderHeaderEntity docOrderHeaderEntity) {
return rulesDto -> {
Map<String, String> entryParamMap = MapUtils.listObjectToMap(AnnotationUtil.queryAnnotationKeyValueList(docOrderHeaderEntity, JudgeExpressMatchField.class));
List<KeyValueDto> ruleList = AnnotationUtil.queryAnnotationKeyValueList(RulesDto.getClass(), rulesDto, JudgeExpressMatchField.class);
for (ExcludeRuleDto excludeRuleDto : rulesDto.getExcludeRuleDtoList()) {
List<KeyValueDto> excludeRuleList = AnnotationUtil.queryAnnotationKeyValueList(excludeRuleDto, JudgeExpressExcludeField.class);
for (KeyValueDto keyValueDto : excludeRuleList) {
String value = entryParamMap.get(keyValueDto.getKey());
if (StringUtils.isNotBlank(keyValueDto.getValue()) && keyValueDto.getValue().equalsIgnoreCase(value)) {
return Boolean.FALSE;
}
}
}
// todo 此处和上述过虑类似,对ruleList规则并且操作,返回对应boolean
return Boolean.TRUE;
};
}
6. 业务调用示例代码:
// 循环获取到的生效规则集合,筛选出最优
Optional<DeliveryRulesDto> firstDeliveryRule = deliveryRulesDtoList.stream()
// 调用自定义实现的Predicate,并输入业务entity进行过滤适配
.filter(carrierReplaceCommonService.judgeExpressMatch(docOrderHeaderEntity))
// 通过事先预定好的顺序,排序
.sorted((s1, s2) -> s1.getPriority().compareTo(s2.getPriority()))
.findFirst();
if (firstDeliveryRule.isPresent()) {
// 获取优先级最高的规则
String ruleDetail = firstDeliveryRule.get().getRuleDetail();
// todo 收集,根据规则修改后的订单,并执行接下来的业务
}
三. 后语:
(1)上述就是小熙对于此处业务场景的,简单示例编写了。
(2)当然此处示例是当前的版本一,还有一些拓展和优化待后续更新。
(3)当然也有其他更好的办法,根据自己的业务和喜好抉择就好。
如果本次分享帮助到了你,还请点个赞哦~