因爲系統有許多需要加工數據的需求,爲了避免重複開發同樣的功能,決定引入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;
}
}
以上,感謝閱讀,如有錯誤,請不吝指教,謝謝