java表达式解析器(支持自定义函数以及操作符)
说明
下面的工具类适用于类似这种表达式 VAR(‘001d’)+VAR(‘test1’) 的解析,需要自定义函数并且函数还附带字符串等非数字类型 的参数
执行逻辑
接受一个需要计算的表达式以及计算表达式所需要的参数信息(参数可空),获取传入参数的方式FormalAnalysis.param.get()
将函数中的字符串参数替换为纯数字参数 VAR(‘001d’)+VAR(‘test1’) -> VAR(1)+VAR(2) ,并且记录改对应关系,在计算是自定替换为原始字符串参数
创建解析器Expression
使用SpringBoot的ApplicationContext获取对应的自定义函数和操作符 ,并添加到所创建的解析器中
优化
工具类中使用的都是线程变量ThreadLocal,确保每个线程的参数以及映射信息都是独立的,并且在每次公式计算完毕进行清空
公式中还定义了搜集异常信息的list变量warnContent,如果想保留异常信息调用FormalAnalysis.warnContent.get().add(异常信息) 进行存储,公式解析完毕会将该异常信息抛出
使用ApplicationContext自动获取自定义的函数和操作符,添加到表达式解析器中,无需手动添加
注意
一定要严格按照你自定义函数所配置的函数名称、参数个数以及自定义的操作符来编写表达式,也就是严格控制表达式的内容
如果表达式中出现了没有定义的其他函数或操作符,会直接报错
1.引入依赖
1 2 3 4 5 <dependency> <groupId>com.udojava</groupId> <artifactId>EvalEx</artifactId> <version>2.7 </version> </dependency>
2.关于自定义函数介绍
如果你解析的公式函数不包含参数或者你的参数为数字,那么你可以直接继承AbstractFunction
如果你的自定义函数中包含的参数是字符串或者其他类型的信息,那么我建议你看下我写的自定义类CustomAbstractFunction
重写自定义函数父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public abstract class CustomAbstractFunction extends AbstractFunction { @Override public Expression.LazyNumber lazyEval (List<Expression.LazyNumber> lazyParams) { return super .lazyEval(lazyParams); } public String restoreOriginalString (Integer s) { return FormalAnalysis.restoreOriginalString(s); } protected CustomAbstractFunction (String name, int numParams) { super (name, numParams); } @Override public BigDecimal eval (List<BigDecimal> parameters) { List<String> param = new ArrayList <>(); for (int i = 0 ; i < parameters.size(); i++) { if (StrUtil.isEmpty(restoreOriginalString(parameters.get(i).intValue()))) { param.add(i, parameters.get(i).toString()); } else { String s = restoreOriginalString(parameters.get(i).intValue()); param.add(i, s); } } return customEval(param); } public BigDecimal customEval (List<String> parameters) { return null ; } }
自定义函数示例
customEval方法会自动将你的函数替换为原始字符串
创建完类你需要声明你的函数名称以及你的函数参数个数,-1代表参数个数不确定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class VAR extends CustomAbstractFunction { public VAR () { super ("VAR" , 1 ); } @Override public BigDecimal customEval (List<String> parameters) { String p1 = parameters.get(0 ); return BigDecimal.ZERO; } }
3.关于自定义操作符的介绍
自定义操作符需要继承AbstractOperator
自定义操作符构造方法参数介绍
参数名
类型
含义
oper
String
操作符符号,例如 “+”、“-”、“*”、“/”。
precedence
int
操作符优先级,值越高优先级越高。例如 * 的优先级高于 +。
leftAssoc
boolean
操作符是否是左结合性 ,例如:+ 和 * 是左结合,^(幂运算)是右结合。
booleanOperator
boolean
是否是布尔操作符,布尔操作符处理 true/false 值,例如 && 和
unaryOperator
boolean
是否是一元操作符 (只有一个操作数),例如 -a 中的 - 是一元操作符。
默认的符号优先级
操作符
描述
优先级 (precedence)
结合性 (leftAssoc)
+
加法
20
true (左结合)
-
减法
20
true (左结合)
*
乘法
30
true (左结合)
/
除法
30
true (左结合)
^
幂运算
40
false(右结合)
自定义操作符示例
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component public class MaxOperator extends AbstractOperator { public MaxOperator () { super (">>" , 30 , true ); } @Override public BigDecimal eval (BigDecimal v1, BigDecimal v2) { return v1.max(v2); } }
4.工具类
方法介绍
analysisCal
preprocessExpression
用于将表达式函数中参数是字符串的参数替换为数字 ,并且将对应关系存储到mapping 变量中
restoreOriginalString
工具类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 @Component public class FormalAnalysis { public static final ThreadLocal<Map<Integer, String>> mapping = ThreadLocal.withInitial(HashMap::new ); private static final ThreadLocal<Integer> placeholderCounter = ThreadLocal.withInitial(() -> 0 ); public static final ThreadLocal<Map<String, Object>> param = ThreadLocal.withInitial(HashMap::new ); public static final ThreadLocal<List<String>> warnContent = ThreadLocal.withInitial(ArrayList::new ); @Resource private ApplicationContext applicationContext; public BigDecimal analysisCal (String formula, Map<String, Object> param) { BigDecimal eval = null ; try { FormalAnalysis.param.set(param); formula = preprocessExpression(formula); Expression expression = new Expression (formula); Map<String, AbstractFunction> fun = applicationContext.getBeansOfType(AbstractFunction.class); for (AbstractFunction abstractFunction : fun.values()) { expression.addFunction(abstractFunction); } Map<String, AbstractOperator> oper = applicationContext.getBeansOfType(AbstractOperator.class); for (AbstractOperator abstractFunction : oper.values()) { expression.addOperator(abstractFunction); } eval = expression.eval(); if (CollUtil.isNotEmpty(warnContent.get())) { throw new RuntimeException (StringUtils.join(warnContent.get().stream().distinct().collect(Collectors.toList()), System.lineSeparator())); } } catch (ArithmeticException e) { if (CollUtil.isNotEmpty(warnContent.get())) { throw new RuntimeException (StringUtils.join(warnContent.get().stream().distinct().collect(Collectors.toList()), System.lineSeparator())); } eval = BigDecimal.ZERO; } finally { FormalAnalysis.mapping.remove(); FormalAnalysis.placeholderCounter.remove(); FormalAnalysis.warnContent.remove(); FormalAnalysis.param.remove(); } return eval.setScale(4 , RoundingMode.HALF_UP); } private static String preprocessExpression (String formula) { formula = formula.replace(ValParamEnum.BEG.getName(), ValParamEnum.BEG.getId().toString()); formula = formula.replace(ValParamEnum.END.getName(), ValParamEnum.END.getId().toString()); Pattern pattern = Pattern.compile("'([^']*)'" ); Matcher matcher = pattern.matcher(formula); StringBuffer sb = new StringBuffer (); while (matcher.find()) { String originalString = matcher.group(1 ); placeholderCounter.set(placeholderCounter.get() + 1 ); mapping.get().put(placeholderCounter.get(), originalString); matcher.appendReplacement(sb, placeholderCounter.get() + "" ); } matcher.appendTail(sb); System.out.println(sb); return sb.toString(); } public static String restoreOriginalString (Integer placeholder) { return mapping.get().get(placeholder); } }
5.使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController @RequestMapping("formula") @Api(tags = "公式测试") public class FormalTestController { @Resource FormalAnalysis formalAnalysis; @GetMapping @ApiOperation("test") public ApiResponse test () { String formal = "VAR('001d')+VAR('test1')" ; BigDecimal bigDecimal = formalAnalysis.analysisCal(formal, new HashMap <>()); System.out.println(bigDecimal); return ApiResponse.succeed(bigDecimal); } }