巧用Spring Expression(SpEL)做动态校验

Spring Expression是Spring Framework的一个组件,有时候也管它叫Spring Expression Language,所以简称就成了SpEL,你也可以管它叫Spring表达式。

它能做什么呢?

简单地说, 就是能将一个带有变量的字符串解析为完整的String字符串,当然也可以是其他类型,比如int,long,或者最常用的boolean类型,说它最常用,是因为boolean类型是可以直接改变业务逻辑的,下面会有实例。

不多说,直接说说如何实现动态校验。

假定前端发送一个保险订单Order到后端,由于保险订单的特殊性,该订单中一般还有用户的信息和保险产品的信息,如果保的是车,还有车辆的信息,那么问题来了,产品说,当订单中车辆是品牌BrandA时,车辆的经销商编码不能为空,当保险产品ProductA时,年龄必须是18-60岁,等等,这些可能是产品在上线之前确定的需求,上线之后可能会突然说,当品牌为BrandB时,兑换码不能为空,作为开发,你会怎么办?

  • if-esle的解决方案,自然拿不上台面的,也解决不了问题,因为你今天增加了这个条件,明天产品一定会提出其他的条件;
  • 策略模式听起来很高大上,但策略的数量一般都是在编码时已经确定的,是需要代码扩展的,扩展完,还需要发版;
  • 动态校验是我目前(加个时间限制吧,也许将来有更好的)想到的最佳方案,具体怎么做呢?

其实品牌和保险产品,以及经销商编码、年龄和兑换码都是该订单的字段,这个维度可以扩展到该类所定义的所有字段,包括继承自父类的,可以将规则列表总结如下:

前提条件prerequisite 后置条件requirement 错误消息error
#o.vehicle.brand = ‘BrandA’ #o.vehicle.dealerNo != null 经销商编码不能为空
#o.product = ‘ProductA’ #o.insured.age >=18 && #o.insured.age < 65 经销商编码不能为空
#o.vehicle.brand = ‘BrandB’ #o.vehicle.giftCode != null 产品B要求兑换码不能为空

上述表中的规则已经不是简单的需要在开始时编写的代码,而是发版后在管理平台编辑的规则,该规则可以任意添加、删除和修改。
下面是解析的代码:

StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable(“o”, order);
List result = new ArrayList<>();
ExpressionParser parser = new SpelExpressionParser();
try {
  rules(rule -> {
    Expression preExpression = parser.parseExpression(rule.getPrerequisite());
    if (preExpression.getValue(context, Boolean.class)) {
      Expression postExpression = parser.parseExpression(rule.getRequirement());
      if (!postExpression.getValue(context, Boolean.class)) {
        result.add(rule.getValidationMsg().getError());
      }
    }
  });
} catch (Exception e) {
  e.printStackTrace();
}
  • rules是针对该订单所设定的规则,如果你觉得rules每次从数据库中获取性能不好,可以实现你自己的缓存策略,有优化的空间;
  • result存的就是错误消息列表,空表示没有触发任何错误;
  • ExpressionParser和StandardEvaluationContext便是Spring Expression的大杀器,其中context字面意思理解下就差不多了,上下文,同样的规则在不一样的上下文中解析出来的结果自然是不一样的,context中我们放置了一个变量o,即订单,如果你的上下文中有多个变量,尽管往里面扔,解析器parser是可以定义为static,每次实例化是没必要的,这里是为了大家好理解直接定义成了局部变量。

上述例子中,是将一个待解析的字符串解析为boolean类型直接用于判断,事实上,也可以解析为String类型,比如要访问一个形如

https://stackoverflow.com/questions/:id

的网页,可以将该字符串写成如下形式:

‘https://stackoverflow.com/questions/’ + #article.getId()

同样的代码只需要改变setVariable,将article的变量放进去,同时在parseExpression方法中指定返回类型为String.class,返回值就是一个真正的可被访问的URL。

其实笔者也多次处理过使用freemarker处理模板文件,技术非常类似,但很多人总以为是生成html的,其实都可以活用,为啥不是xml,为啥不是email,为啥不是格式合同?
活用、巧用才会有创新。

你可能感兴趣的:(Java)