使用SpEL来实现一个简单流程处理引擎

本文介绍了如何使用Spring Expression Language(SpEL)来构建一个简单的流程处理引擎,以应对审批流程的需求。通过定义表达式和输出参数,实现动态配置加载,并避免过度依赖第三方组件。示例展示了如何根据用户岗位信息生成审批查询条件,提供了一种灵活且可扩展的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景是有一些简单的审批流程处理,不想用到工作流那么重的组件。但是,全凭if else 修改很费体力,也容易出错。需求是可以预见的在下一期或者下下期会调整审批流程。利用表达式语言来实现一个简单的流程选择。这个思路感觉比较通用,可以很方便扩展其他的类似的场景应用。主要点如下:

  • 挑选一个表达式引擎
  • 定义表达式与输出参数配置
  • 现实配置的定义加载
  • 使用表达式进行输入参数到输出参数的转化

表达式语言的组件有很多,比如ognl,velocity等等。SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。选用SpEL主要是Spring框架自带了,基本够用,尽可能少的减少项目对三方组件的依赖。比如常用到的配置参数注入注解@Value就可以使用SpEL:

// #{}内就是表达式的内容
@Value("#{表达式}")
public String arg;

简化一下需求场景,申请人任务提交后,根据不同的任务级别,不同的提报部门,选择不同的部门岗位的人员来进行审批(审批的结果流程省略了)。审批的多级支持省略,可自行扩展。尽可能完整给出示例。

其实就是需要审批权限的用户看到自己可以操作的任务,拼接查询条件去查询任务表。常规的思路就是写一堆ifelse进行判断和条件拼接。
流程示意

第一先把判断条件的表达式和最后输出的条件参数定义下,为了方便操作和修改,使用了json文件,这样反序列化加载非常容易就可以得到Java的对象;定义一个flow.json,放到resources下,会编译到类加载根路径之下。

 { 
   "approvePolicies": [
    {
      "expr": "dept == 'D1' and pos == 'P3'",
      "data": {"level": "L1"}
    },
    {
      "expr": "dept == 'D1' and pos == 'P1'",
      "data": {"level": "L2", "src": "S1"}
    },
    {
      "expr": "dept == 'D1' and pos == 'P1'",
      "data": {"level": "L2", "src": "S1"}
    }
   ]
 }

对应的流程定义对象为:

@Data
public class ApproveCondition {
	// 任务级别
  String level;
  // 任务来源
  String src;
}

@Data
public class ApprovePolicy {
  String expr;
  ApproveCondition data;
}

@Data
public class FlowCfgData {
    List<ApprovePolicy> approvePolicies;
}

流程加载可以放到应用容器bean初始化时,当然可以扩展一下实现动态加载(比如放到数据库),就可以随时修改和刷新流程定义了。

@Configuration
@Slf4j
public class FLowConfig {

    @Bean
    FlowCfgData flowCfgData() throws IOException {
        return getConfigDataFromResource();
    }
  
  	/**
  	 * 从类路径下加载流程配置定义
  	 */
	  private static FlowCfgData loadCfgDataFromResource() throws IOException {
        InputStream in = FLowConfig.class.getResourceAsStream("/flow.json");
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        in.close();
        String info = sb.toString();
        log.info("load flow config from resource: {}", info);
        FlowCfgData cfgData = JSON.parseObject(info, FlowCfgData.class);
        return cfgData;
    }
}

入参就是当前登录用户的岗位信息:

@Data
public class UserPosition {
    // 部门
    private String dept;
  	// 岗位
    private String pos;
    // ...
}

那边在判断用户岗位入参,生成审批查询条件就很简单了

@Service
public class XxxServiceImpl implements XxxService{
	  @Autowired
    FlowCfgData flowCfgData;
  
		// ...
  
  	/**
		 * 把条件参数拼装为查询条件(这里看数据库和ORM选择的组件)
		 * 这里示例拼接SQL条件,拼接完类似: (level='L2' and src='S1') or (leve='L1')
		 */
  	private String getQueryCondtion(List<ApproveCondition> conditionList) {
        String where = "";
      	for(ApproveCondition condition : conditionList) {
			String p = "";
          	if(condition.getLevel() != null){
               p += "level='" + condition.getLevel() + "'";
            }
            if(condtion.getSrc() != null) {
               p += (p.isEmpty() ? "" : " and ") + "src='" + condition.getSrc() + "'";
            }
            if(!p.isEmpty()) {
              	where += (where.isEmpty() ? "" : " or ") + "(" + p + ")"; 
            }
        }
    }
  	
		/**
		 * 把权限转化为条件参数
		 */
		private List<ApproveCondition> getApproveCondition(UserPosition userPos) {
        List<ApproveCondition> conditionList = new ArrayList<>();
        for (ApprovePolicy policy : flowCfgData.getApprovePolicies()) {
          	// 满足某个表达式时,就得到改表达式对应的条件参数
            if (execExpr(policy.getExpr(), userPos)) {
              conditionList.add(policy.getData());
            }
        }
        return conditionList;
    }

		/**
		 * 执行SpEL表达式
		 */
    private boolean execExpr(String expr, Object object) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext ctx = new StandardEvaluationContext(object);
        Expression exp = parser.parseExpression(expr);
        Object ret = exp.getValue(ctx);
        return ret instanceof Boolean && (boolean) ret;
    }
}

这样,一个完整的流程定义和加载处理的引擎就完成了。当然,选用其他表达式引擎或者选择编译型实现DSL可以提升执行效率。比如规则引擎Drools及MVEL或是Antrl,可以让代码随时修改随时运行,对于流程或规则决策型应用应用广泛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值