因为系统有许多需要加工数据的需求,为了避免重复开发同样的功能,决定引入jeval工具类,实现特殊处理可配置。可能这样描述读者还是一知半解,且听我一一道来。
1. jeval简述
大部分能看见这篇博客的读者,肯定或多或少是提前了解过这个工具的,但是为了整篇博客不那么突兀,还是做个简单的介绍。
2.自定义函数实现
package cn.newhope.de.expr.function.array;
import cn.newhope.de.expr.EvaluatorUtil;
import cn.newhope.de.expr.util.JSONArrayUtil;
import cn.newhope.de.expr.util.PatternUtil;
import cn.newhope.de.util.Base64Util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import net.sourceforge.jeval.Evaluator;
import net.sourceforge.jeval.function.*;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 根据条件 判断获取数组最小值
* arrayMin(数组,列名,条件)
* eg:
* arrayMin(#{datas},age,'equals(test,'@{name}')')
* arrayMin(#{datas},age,'test==@{name}')
*
* arrayMin("#{data.BwjkGaofa.bizResponse.info}","filing_time","equals(""天津"",""@{province}"")")
*/
public class ArrayMinFunction implements Function {
@Override
public String getName() {
return "arrayMin";
}
@Override
public FunctionResult execute(Evaluator evaluator, String arguments) throws FunctionException {
String result=null;
try {
List<String> list= PatternUtil.splitCSV(arguments);
if (list.size()!=3){
throw new FunctionException("参数异常");
}
String data = list.get(0);
String prop = list.get(1);
String condition = list.get(2);
String filter=PatternUtil.replaceFilter(condition);
//先根据条件查到符合条件的数组,然后求最小值
JSONArray jsonArray=JSONArray.parseArray(data);
String key=PatternUtil.getKeyByCondition(filter);
List<JSONObject> filterList= Lists.newArrayList();
for (Object o:jsonArray) {
JSONObject jsonObject=JSONObject.parseObject(o.toString());
EvaluatorUtil.putVariable(key,jsonObject.getString(key));
if(!"0.0".equals(EvaluatorUtil.exEvaluate(filter))){
filterList.add(jsonObject);
}
}
//这个是自己实现的工具类,用于求对象数组的某一列最小值,读者可实现自己的,ps: 如果你的自定义函数能通过过滤条件走到这里,基本这个函数的使用你就会用了
result = JSONArrayUtil.arrayMin(jsonArray, prop);
result.replace("\"","\"\"");
} catch (Exception e) { throw new FunctionException(this.getName() + ": evalute error! " + arguments +"\n" +e.getMessage(), e); }
return new FunctionResult(String.valueOf(result), FunctionConstants.FUNCTION_RESULT_TYPE_STRING);
}
}
我先简单说一下实现一个自定义函数要注意的:
- 所有创建的自定义函数都要实现jeval的function接口
- function 接口需要实现两个功能,一个是自定义函数的名字,后面会通过这个名字配置你的表达式,另一个是具体实现逻辑,即数据和参数传进来你打算如何处理
- 如果你想要创建的核心类evaluator使用这个函数,需要在使用前用evaluator的api,evaluator.putfunction(new yourfunction),先把你的自定义函数加载进去,否则会报错这个函数找不到
- jeval使用 ‘,’作为参数分隔符,所以要留意参数里面不能包含‘,’,一定要包含的话需要用引号引起来,否则会报错格式错误
- jeval默认使用‘’ 来标注字符串,但是我上面的例子在初始化的时候选择了“”标注字符串,主要还是为了和我们正常感官一致,运行我上面的demo需要初始化evaluator的时候留意。如下初始化
private static final Evaluator evaluator = new Evaluator(EvaluationConstants.DOUBLE_QUOTE, true, true, true, true);
上面这个函数我大概说一下功能,到时候会传进来三个参数,一个是源数据,一个是要求对象数组的最小值的属性,最后一个是筛选条件,这个条件可以是简单过滤条件 如name=‘liming’,可以是jeval内置函数,也可以是你的其他自定义函数
package cn.newhope.de.expr.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PatternUtil {
//表达式条件正则,需要嵌套函数的时候会用到
private static Pattern pattern = Pattern.compile("@\\{([^}]+)\\}");
//表达式正则
private static Pattern pattCond = Pattern.compile("#\\{([^}]+)\\}");
/**
* 用于获取表达式里面的key
*
* @param expr
* @return
*/
public static String getKeyByCondition(String expr) {
Matcher matcher = pattCond.matcher(expr);
while (matcher.find()) {
return matcher.group(1);
}
return "";
}
/**
* 替换表达式条件里面的特殊字符
* 防止分割条件字符串存在分割符
* 这里主要是为了防止筛选条件里面出现特殊的符号,如@,这样如果直 接替换的话可能会把本来不应该替换的字符替换掉,所以想了个办法是一点一点截取替换,每次匹配到需要的正则表达式,先把这部分截取下来替换里面的字符,再拼接回去,匹配下一个
* @param
* @return
*/
public static String replaceFilter(String filter) {
Matcher matcher = pattern.matcher(filter);
while (matcher.find()) {
String originalStr = matcher.group();
String newStr = originalStr.replace('@', '#');
filter = filter.replace(originalStr, newStr);
}
return filter;
}
/**
* @Description: 解析自定义函数参数
* 这个方法主要是为了切分配置的复杂表达式
* 因为jeval解析的时候会校验格式,如果表达式不标准就会报错,
* 而jeval默认是用,分割,字符串用‘’,可以想见传的数据很可能是带逗号、#、{}、等特殊字符的,这样解析会出现报错
* 目前常见方法有两种
* 1、对可能出现不规则数据的表达式参数先进行base64加密,传入之后再对数据进行base64解密,另外因为你很可能需要嵌套执行,所以对于自定义函数处理结果,需要进行再次加密,方便外面的嵌套函数执行,这样可以正常实现,但是可能会对性能有影响
* 2、实现一个正则表达式,他切割字符串不会考虑字符串里面本来包含的特殊字符,因为csv是一种比较常见并且通用的格式,我们如果能把参数按照csv 的格式拼接起来,并且能正确解析就能实现我们的目标
前面说了正因为csv常见,有现成成熟的正则可以使用,我们先按照csv格式组装参数,然后在函数内部再根据正则切分得到我们想要的参数
*/
public static List<String> splitCSV(String txt) {
String reg = "\\G(?:^|,)(?:\"([^\"]*+(?:\"\"[^\"]*+)*+)\"|([^\",]*+))";
Matcher matcherMain = Pattern.compile(reg).matcher(txt);
Matcher matcherQuoto = Pattern.compile("\"\"").matcher("");
List strList = new ArrayList();
while (matcherMain.find()) {
String field;
if (matcherMain.start(2) >= 0) {
field = matcherMain.group(2);
} else {
field = matcherQuoto.reset(matcherMain.group(1)).replaceAll("\"");
}
strList.add(field);
}
return strList;
}
}
这块因为我也是用不久,有些东西可能描述的不够清楚,读者有问题可以直接评价或者邮箱发我,大家互相学习,后续也会针对问题逐渐更新这边博文
总结一下吧:
- 首先创建自己的自定义函数实现jeval的function接口
- 然后定义自己的函数名字,后面你配置的表达式要和这个名字一致
- 创建evaluator实体类,这边可以做一些自定义的修改,比如字符串标志的单引号改为双引号,是否加载自带的数学函数,字符串函数等
- 最关键的一步,如何定义你的表达式,即你想实现的功能。要传过去的数据,以及其他参数,比如加个条件,age=12, 比如你想求对象数组某一属性的最大值,那你就需要传过去源数据和属性名等等
5.关于组装表达式,建议用笔者的这种方法,组装的表达式易于分析和拼接,即字符串用“”标注,格式用csv格式
最后贴一个测试类说明参数是怎么组装的吧:
package array;
import cn.newhope.de.expr.EvaluatorUtil;
import cn.newhope.de.expr.function.array.ArrayMinFunction;
import cn.newhope.de.util.Base64Util;
import com.alibaba.fastjson.JSONObject;
import net.sourceforge.jeval.Evaluator;
import java.util.Arrays;
import java.util.List;
public class ArrayMinTest {
public static void main(String[] args) throws Exception{
Evaluator evaluator= EvaluatorUtil.getEvaluator();
String expr="arrayMin(\"#{datas}\",\"age\",\"sfsf\")";
Students students1=new Students(12,"zhangsan");
Students students2=new Students(15,"lisi");
Students students3=new Students(34,"wangwu");
List list= Arrays.asList(students1,students2,students3);
String listStr= JSONObject.toJSONString(list);
evaluator.putFunction(new ArrayMinFunction());
//evaluator.putVariable("datas", Base64Util.encode(listStr.getBytes()));
//转义字符串中的“”,让csv正则能正确识别
listStr=listStr.replace("\"","\"\"");
evaluator.putVariable("datas", listStr);
String result=evaluator.evaluate(expr);
// System.out.println(Base64Util.decode(result));
}
}
class Students {
int age;
String name;
public Students(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
以上,感谢阅读,如有错误,请不吝指教,谢谢