需求:我们在开发过程中会遇到下面这类需求,类似一张流程图,根据条件不断进行判断,命中,再进行下一轮判断。
实现:在项目开发过程中 ,完全可以根据每个节点进行if else 判断进行流程跳转,但是这样比较繁琐,且不符合设计原则
根据上面的需求知晓以下几点
流程跳转的三要素:条件、动作、下一步
整个判断的流程都是一次性,因此直接是内存判断,无需借助数据库等记录中间状态。
利用Spel表达式 递归进行条件判断:
结构图:
每个@Rule方法,代表了一个规则包含 条件、动作、下一步。满足condition条件,反射执行方法体,然后遍历next的后续节点,判断是否满足,不断递归执行执行最后一个节点。最后一个节点即叶子节点,next为空。
/**
* @author lixiaojian
* @version 5.9.
* @Description
* @date 2021/07/19
*/
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Rule {
String condition();
String rulename();
String[] next() default {};
}
/**
* @author lixiaojian
* @version 5.9.
* @Description
* @date 2021/07/19
*/
@Data
public class RuleV {
/**
* 条件
*/
private String condition;
/**
* 规则名称
*/
private String rulename;
/**
* 满足条件需要执行的方法
*/
private Method ruleMethod;
/**
* 后续的规则名称
*/
private List<String> next;
}
/**
* @author lixiaojian
* @version 5.9.
* @Description
* @date 2021/07/20
*/
public class RuleEngine {
public static final String CURRENT_RULE = "currentRule";
final Map<String, RuleV> ruleVMap = new HashMap<>();
public Object target;
public void init(Object target){
this.target = target;
ReflectionUtils.doWithMethods(target.getClass() , new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Rule annotation = method.getAnnotation(Rule.class);
if (annotation != null) {
RuleV ruleV = new RuleV();
ruleV.setRulename(annotation.rulename());
ruleV.setCondition(annotation.condition());
String[] next = annotation.next();
ruleV.setNext(Arrays.asList(next));
ruleV.setRuleMethod(method);
ruleVMap.put(annotation.rulename(), ruleV);
}
}
});
}
public void start(String startRuleName,HashMap variable) {
RuleV ruleV = ruleVMap.get(startRuleName);
variable.put(CURRENT_RULE,startRuleName);
try {
ruleV.getRuleMethod().invoke(target,variable);
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(e);
}
stepInto(variable);
}
/**
* 递归进入下一个阶段的判断
* @param variables
*/
private void stepInto(Map<String, Object> variables) {
RuleV ruleV = ruleVMap.get(variables.get(CURRENT_RULE));
if (ruleV == null) {
System.err.println("success.....stepInto...................");
}
for (String s : ruleV.getNext()) {
try {
RuleV nextRule = ruleVMap.get(s);
String condition = nextRule.getCondition();
Method ruleMethod = nextRule.getRuleMethod();
boolean check = check(variables, condition);
if (check) {
variables.put(CURRENT_RULE, ruleVMap.get(s).getRulename());
ruleMethod.invoke(target, variables);
stepInto(variables);
break;
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* spel表达式判断是否满足条件
* @param variables
* @param condition
* @return
*/
public static boolean check(Map<String, Object> variables,String condition){
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(variables);
Expression exp = parser.parseExpression(condition);
return exp.getValue(context, Boolean.class);
}
}
demo:
@Slf4j
@Service
public class SimpleRuleDemoService {
@Rule(condition = "true == true",rulename = "start",next = {"phonePrefix188","phonePrefix200"})
public void start(Map<String, Object> variables){
log.info("begin ****************************************** start {}",variables);
log.info("我成功进入到了 start");
variables.put("phone", "1881410");
log.info("end ******************** into ********************** start {}",variables);
}
/**
* 满足手机号是188开头,下一阶段的规则是 finAms,findOther
* @param variables
*/
@Rule(condition = "#phone.startsWith('188')",rulename = "phonePrefix188",next = {"finAms","findOther"})
public void phonePrefix188(Map<String, Object> variables){
log.info("begin ****************************************** phonePrefix188 {}",variables);
log.info("我成功进入到了 phonePrefix188");
variables.put("finAms", true);
log.info("end ******************** into ********************** phonePrefix188 {}",variables);
}
/**
* 满足手机号是188开头,下一阶段的规则是 空,表示最后命中结束
* @param variables
*/
@Rule(condition = "#phone.startsWith('200')",rulename = "phonePrefix200")
public void phonePrefix200(Map<String, Object> variables){
log.info("begin ****************************************** phonePrefix200 {}",variables);
log.info("我成功进入到了 phonePrefix200");
variables.put("isInAms", true);
log.info("end ******************** into ********************** phonePrefix200 {}",variables);
}
@Rule(condition = "#finAms == true",rulename = "finAms",next = {"jueseB","ok"})
public void finAms(Map<String, Object> variables){
log.info("begin ****************************************** finAms {}",variables);
log.info("我成功进入到了 finAms");
variables.put("jueseB", false);
log.info("end ******************** into ********************** finAms {}",variables);
}
@Rule(condition = "#findOther",rulename = "findOther")
public void findOther(Map<String, Object> variables){
log.info("begin ****************************************** findOther {}",variables);
log.info("我成功进入到了 findOther");
log.info("end ******************** into ********************** findOther {}",variables);
}
@Rule(condition = "#jueseB == false",rulename = "jueseB")
public void jueseB(Map<String, Object> variables){
log.info("begin ****************************************** jueseB {}",variables);
log.info("我成功进入到了 jueseB");
log.info("end ******************** into ********************** jueseB {}",variables);
}
@Rule(condition = "!#jueseB",rulename = "ok")
public void ok(Map<String, Object> variables){
log.info("begin ****************************************** ok {}",variables);
log.info("我成功进入到了 ok");
log.info("end ******************** into ********************** ok {}",variables);
}
public static void main(String[] args) {
RuleEngine ruleEngine = new RuleEngine();
ruleEngine.init(new SimpleRuleDemoService());
ruleEngine.start("start",new HashMap());
}
public void checkparse(){
ExpressionParser parser = new SpelExpressionParser();
Expression parseExpression = parser.parseExpression("name");
parseExpression.getValue();
}
}
demo的流程执行节点结果: