巧用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的解决方案,自然拿不上台面的,也解决不了问题,因为你今天增加了这个条件,明天产品一定会提出其他的条件;
- 策略模式听起来很高大上,但策略的数量一般都是在编码时已经确定的,是需要代码扩展的,扩展完,还需要发版;
- 动态校验是我目前(加个时间限制吧,也许将来有更好的)想到的最佳方案,具体怎么做呢?
其实品牌和保险产品,以及经销商编码、年龄和兑换码都是该订单的字段,这个维度可以扩展到该类所定义的所有字段,包括继承自父类的,可以将规则列表总结如下:
上述表中的规则已经不是简单的需要在开始时编写的代码,而是发版后在管理平台编辑的规则,该规则可以任意添加、删除和修改。
下面是解析的代码:
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,为啥不是格式合同?
活用、巧用才会有创新。