SpringEL 应用场景
目录
场景一: 计算值
1,单位的转换
把 毫秒的值,转换为分钟
把bytes的值转成Gb
2,数值之间的计算
场景:
初始化话配置数据
初始化数据:
进行计算处理:
输出:
小结:
场景二:替换值
1,使用SpringEL 进行替换:
2,另一种替换方式: replaceByStrSubstitutor
3,正则表达替换方式:
为什么要使用SpringEL?
SpringEL可以进行赋值再进行计算,功能还是很强大的。
long us = 234567;
EvaluationContext context = new StandardEvaluationContext(); // 表达式的上下文
ExpressionParser parser = new SpelExpressionParser();
context.setVariable("value", us); // 为了让表达式可以访问该对象, 先把对象放到上下文中
Object ss = parser.parseExpression("#value/(60*60)").getValue(context);
System.out.println("minutes: " + ss);
long bytes = 53456712312L;
EvaluationContext context = new StandardEvaluationContext(); // 表达式的上下文
ExpressionParser parser = new SpelExpressionParser();
context.setVariable("value", bytes); // 为了让表达式可以访问该对象, 先把对象放到上下文中
Object ss = parser.parseExpression("#value/(1024*1024*1024)").getValue(context);
System.out.println("Gb size: " + ss);
获取到订单的总数(orderTotalCount),超过10分钟的数量(orderDurationOverTenMinute),点击数(orderClicks),耗时(秒)(orderDealDuration)
目标计算出订单中: 超过10分钟的占比(orderOverTenMinuteRate),平均点击数(orderAvgClicks),平均耗时(分钟)(orderAvgDealDuration)
公式:
超过10分钟的占: orderOverTenMinuteRate = orderDurationOverTenMinute / orderTotalCount
平均点击数: orderAvgClicks = orderClicks / orderTotalCount
平均耗时(分钟) orderAvgDealDuration = orderDealDuration / orderTotalCount * 100
这些一般配置在表格,这边就直接模拟数据
考虑到这边有值之间的相互计算,SpringEL 替换值,再进行计算,考虑使用SpringEL 来解决。
public static List initEsFormulaData() {
String orderDurationOverTenMinuteStr = "{\"kpiKey\":\"orderDurationOverTenMinute\",\"pattern\":\"#\",\"unit\":\"\",\"formula\":\"#orderDurationOverTenMinute\"}";
EsQueryFormula orderDurationOverTenMinute = JSON.parseObject(orderDurationOverTenMinuteStr, EsQueryFormula.class);
String orderTotalCountStr = "{\"kpiKey\":\"orderTotalCount\",\"pattern\":\"#\",\"unit\":\"\",\"formula\":\"#orderTotalCount\"}";
EsQueryFormula orderTotalCount = JSON.parseObject(orderTotalCountStr, EsQueryFormula.class);
String orderAvgClicksStr = "{\"kpiKey\":\"orderAvgClicks\",\"pattern\":\"#\",\"unit\":\"\",\"formula\":\" #orderTotalCount>0?#orderClicks/#orderTotalCount:0\"}";
EsQueryFormula orderAvgClicks = JSON.parseObject(orderAvgClicksStr, EsQueryFormula.class);
String orderOverTenMinuteRateStr = "{\"kpiKey\":\"orderOverTenMinuteRate\",\"pattern\":\"#.##\",\"unit\":\"%\",\"formula\":\"#orderTotalCount>0?#orderDurationOverTenMinute/#orderTotalCount*100:0\"}";
EsQueryFormula orderOverTenMinuteRate = JSON.parseObject(orderOverTenMinuteRateStr, EsQueryFormula.class);
String orderAvgDealDurationStr = "{\"kpiKey\":\"orderAvgDealDuration\",\"pattern\":\"#.##\",\"unit\":\"min\",\"formula\":\"#orderTotalCount>0?#orderDealDuration/#orderTotalCount/60:0\"}";
EsQueryFormula orderAvgDealDuration = JSON.parseObject(orderAvgDealDurationStr, EsQueryFormula.class);
List esQueryFormulaList = new ArrayList<>();
esQueryFormulaList.add(orderDurationOverTenMinute);
esQueryFormulaList.add(orderTotalCount);
esQueryFormulaList.add(orderAvgClicks);
esQueryFormulaList.add(orderOverTenMinuteRate);
esQueryFormulaList.add(orderAvgDealDuration);
return esQueryFormulaList;
}
// 配置实体类
public class EsQueryFormula {
private String kpiKey;
private String pattern;
private String unit;
private String formula;
public EsQueryFormula(String kpiKey, String pattern, String unit, String formula) {
this.kpiKey = kpiKey;
this.pattern = pattern;
this.unit = unit;
this.formula = formula;
}
public String getKpiKey() {
return kpiKey;
}
public void setKpiKey(String kpiKey) {
this.kpiKey = kpiKey;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getFormula() {
return formula;
}
public void setFormula(String formula) {
this.formula = formula;
}
@Override
public String toString() {
return "EsQueryFormula{" +
"kpiKey='" + kpiKey + '\'' +
", pattern='" + pattern + '\'' +
", unit='" + unit + '\'' +
", formula='" + formula + '\'' +
'}';
}
}
private static List
private static final ExpressionParser PARSER = new SpelExpressionParser();
public static void main(String[] args) {
testElCalculate();
}
public static void testElCalculate(){
List> dataList = initData();
List esQueryFormulaList = initEsFormulaData();
List> aimList = ListUtils.emptyIfNull(dataList).stream().map(e -> getEsQueryMap(esQueryFormulaList, e)).collect(Collectors.toList());
aimList.forEach(System.out::println);
}
// 计算值
public static Map getEsQueryMap(List esQueryFormulaList, Map dataMap) {
return ListUtils.emptyIfNull(esQueryFormulaList).stream()
.collect(Collectors.toMap(EsQueryFormula::getKpiKey, e -> getEsQueryExpress(e, dataMap), (x, y) -> x));
}
public static String getEsQueryExpress(EsQueryFormula esQueryFormula, Map dataMap) {
String unit = esQueryFormula.getUnit();
return getExpressionResult(dataMap, esQueryFormula) + (unit != null ? unit : "");
}
private static String getExpressionResult(Map dataMap, EsQueryFormula esQuery) {
return parseExpression(dataMap, esQuery.getFormula(), esQuery.getPattern());
}
public static String parseExpression(Map data, String formula, String pattern) {
Double expressionResult = parseDoubleExpression(data, formula);
if (Objects.nonNull(expressionResult)) {
return decimalFormat(expressionResult, pattern);
}
return null;
}
private static String decimalFormat(Double value, String pattern) {
DecimalFormat df = new DecimalFormat(pattern);
df.setRoundingMode(RoundingMode.HALF_UP);
return df.format(value);
}
public static Double parseDoubleExpression(Map data, String formula) {
Expression expression = PARSER.parseExpression(formula);
return parseDoubleExpression(data, expression);
}
public static Double parseDoubleExpression(Map data, Expression expression) {
EvaluationContext ctx = new StandardEvaluationContext();
if (Objects.nonNull(data)) {
data.forEach(ctx::setVariable);
}
return expression.getValue(ctx, Double.class);
}
{orderDurationOverTenMinute=10, orderAvgDealDuration=23.42min, orderAvgClicks=4, orderTotalCount=88, orderOverTenMinuteRate=11.36%}
{orderDurationOverTenMinute=69, orderAvgDealDuration=198.27min, orderAvgClicks=5, orderTotalCount=188, orderOverTenMinuteRate=36.7%}
{orderDurationOverTenMinute=97, orderAvgDealDuration=20.27min, orderAvgClicks=4, orderTotalCount=151, orderOverTenMinuteRate=64.24%}
回到开头的提问:为什么要使用SpringEL?
因为SpringEL可以进行赋值再进行计算,上面的场景刚好就是用了SpringEL这个强大的功能,解决了问题。再遇到类似需要赋值,计算的场景,就可以把SpringEL用起来了。
比如es的查询串:
"{\"size\":#{#size},\"from\":#{#from},\"query\":{\"bool\":"
+ "{\"must\":[{\"range\":{\"timeStamp\":{\"gte\":\"#{#startDate}\","
+ "\"lte\":\"#{#endDate}\",\"format\":\"yyyy-MM-dd HH:mm:ss\","
+ "\"time_zone\":\"+00:00\"}}}]}}}";
最直接的方式是用 StringUtils 的方法 replace() 挨个进行替换。
如果使用SpringEL 怎么进行替换呢?
/**
* SpringEL 的基本语法 #{}, 值之前加#,比如 #size
*/
public static void replaceByEl() {
String esStr = "{\"size\":#{#size},\"from\":#{#from},\"query\":{\"bool\":" +
"{\"must\":[{\"range\":{\"timeStamp\":{\"gte\":\"#{#startDate}\",\"lte\":\"#{#endDate}\"," +
"\"format\":\"yyyy-MM-dd HH:mm:ss\",\"time_zone\":\"+00:00\"}}}]}}}";
System.out.println(esStr);
Map paramMap = new HashMap<>();
paramMap.put("size", 20);
paramMap.put("from", 0);
paramMap.put("startDate", "2020-11-22 00:00:00");
paramMap.put("endDate", "2020-11-25 00:00:00");
String esSearchStr = createEsQuery(esStr, paramMap);
System.out.println("esSearchStr EL "+esSearchStr);
}
public static String createEsQuery(String esQueryStr, Map map) {
ExpressionParser parser = new SpelExpressionParser();
// TemplateParserContext 处理 这个的符号 this("#{", "}");
Expression expression = parser.parseExpression(esQueryStr, new TemplateParserContext());
EvaluationContext ctx = new StandardEvaluationContext();
StandardEvaluationContext ctx1 = new StandardEvaluationContext();
//
// public void setVariables(Map variables) {
// variables.forEach(this::setVariable);
// }
if (Objects.nonNull(map)) {
// StandardEvaluationContext 可以用两种方式一 setVariable 和 setVariables
ctx1.setVariables(map);
// EvaluationContext 只能用一种方式 setVariable
map.forEach(ctx::setVariable);
}
return expression.getValue(ctx, String.class);
}
使用 StrSubstitutor
/**
* StrSubstitutor 占位符替换
* public StrSubstitutor(Map valueMap) {
* this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
* }
*
* // Constant for the default escape character.
*
* public static final char DEFAULT_ESCAPE = '$';
*
* // Constant for the default variable prefix.
*
* public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
*
* // Constant for the default variable suffix.
*
* public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
*
* 基本语法结构 ${}
*/
public static void replaceByStrSubstitutor() {
String esStr = "{\"size\":${size},\"from\":${from},\"query\":{\"bool\":" +
"{\"must\":[{\"range\":{\"timeStamp\":{\"gte\":\"${startDate}\",\"lte\":\"${endDate}\"," +
"\"format\":\"yyyy-MM-dd HH:mm:ss\",\"time_zone\":\"+00:00\"}}}]}}}";
System.out.println(esStr);
Map paramMap = new HashMap<>();
paramMap.put("size", 20);
paramMap.put("from", 0);
paramMap.put("startDate", "2020-11-22 00:00:00");
paramMap.put("endDate", "2020-11-25 00:00:00");
StrSubstitutor sub = new StrSubstitutor(paramMap);
String esSearchStr = sub.replace(esStr);
System.out.println("esSearchStr StrSubstitutor "+esSearchStr);
}
两个比较起来,StrSubstitutor会简单些。
在注入值的时候,被占位符挤压,在替换中也被占位符挤压,SpringEL表示:“我好难啊!”
public static void main(String[] args) {
String esStr = "{\"size\":${size},\"from\":${from},\"query\":{\"bool\":" +
"{\"must\":[{\"range\":{\"timeStamp\":{\"gte\":\"${startDate}\",\"lte\":\"${endDate}\"," +
"\"format\":\"yyyy-MM-dd HH:mm:ss\",\"time_zone\":\"+00:00\"}}}]}}}";
System.out.println(esStr);
Map paramMap = new HashMap<>();
paramMap.put("size", 20);
paramMap.put("from", 0);
paramMap.put("startDate", "2020-11-22 00:00:00");
paramMap.put("endDate", "2020-11-25 00:00:00");
// 表达式 $ 开头, { } 匹配这里面的, 里面的是字母
String pattern = "\\$\\{([a-zA-Z_]*)\\}";
getMatchReplace(esStr, pattern, paramMap);
}
public static void getMatchReplace(String content, String pattern, Map param)
{
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(content);
StringBuffer sb = new StringBuffer();
while (m.find())
{
String key = m.group(1);
String value = MapUtils.getString(param, key);
m.appendReplacement(sb, value == null ? "" : ("\"").concat(value).concat("\""));
}
m.appendTail(sb);
System.out.println(sb.toString());
}
结果:
{"size":${size},"from":${from},"query":{"bool":{"must":[{"range":{"timeStamp":{"gte":"${startDate}","lte":"${endDate}","format":"yyyy-MM-dd HH:mm:ss","time_zone":"+00:00"}}}]}}}
{"size":20,"from":0,"query":{"bool":{"must":[{"range":{"timeStamp":{"gte":"2020-11-22 00:00:00","lte":"2020-11-25 00:00:00","format":"yyyy-MM-dd HH:mm:ss","time_zone":"+00:00"}}}]}}}
在SpringEL的应用中,基于SpringEL的特性:赋值和计算。尤其是两者结合起来,更显得有优势。赋值(注入和替换值)可以优先考虑占位符(${}),比较简便。需要赋值和计算,考虑使用SpringEL。
上一篇: SpringEL 学习