五分钟学会Spring表达式语言SpEL,不但学会使用也知道底层原理?
#头条群星9月榜#
sprign 表达式
SpEL:Spring Expression Language,支持在运行时查询和操作对象图的一种强大的表达式语言。该语言的语法类似于Unified EL,但提供了额外的特性,最显著的是方法调用和基本的字符串模板功能。SpEL为Spring社区提供表达式语言的支持,但是并没有和Spring绑定,可以单独使用。
Spring 表达式中的几个接口类
几个重要接口类
接口ExpressionParser类,这个接口主要用来解析表达式字符串,并且返回一个表达式对象Expression,这个接口ExpressionParser有个安全并且可以重用的实现类SpelExpressionParser。
package org.springframework.expression;
public interface ExpressionParser {
/**
* 解析表达式字符串并且返回一个可重复获取返回值的表达式对象
*/
Expression parseExpression(String expressionString) throws ParseException;
/**
* 解析表达式字符串并且返回一个可重复获取返回值的表达式对象,这个方法传递一个Context对象
* Context对象为表达式对象提供输入
*/
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
通过一个例子了解解析字符串字面量(string literal),比如表达式'Hello World',程序执行完输出字符串Hello World,这个字符串需要使用单引号括起来,先看看测试程序,后续再了解字符串字面量的语法,代码如下:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
System.out.printf(message);
//输出:Hello World
如果不使用单引号,程序则抛出SpelParseException异常,从异常信息看是解析完表达式后面还有数据导致,主要是字符串字面量中有空格导致,如果字符串改变成'Hello_World'则会正常解析,测试结果如下:
Expression exp = parser.parseExpression("Hello World");
//结果如下:
Exception in thread "main" org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'World'
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:141)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:61)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:33)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:52)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:43)
at antlr4.TestSPEL.main(TestSPEL.java:12)
再简单分析类Expression,这个类会根据上下文对自身求值,并封装了求值的公共方法。常用的方法是getValue(),这个方法有多个重载的版本,如果没有传参desiredResultType(预期的结果类型),怎需要对结果类型进行转换,如果类型不能转换则抛出SpelEvaluationException异常。
@Nullable
Object getValue() throws EvaluationException;
@Nullable
<T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException;
@Nullable
Object getValue(@Nullable Object rootObject) throws EvaluationException;
@Nullable
<T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException;
@Nullable
Object getValue(EvaluationContext context) throws EvaluationException;
@Nullable
Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
@Nullable
<T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException;
@Nullable
<T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException;
最后了解一下接口EvaluationContext,这个接口用来解析属性、方法、字段,并帮助执行类型转换。在类定义了很多get方法,例如获取rootObject,MethodResolvers,BeanResolver等,并且有个setVariable方法,用于设置变量。这个接口的实现类有三个StandardEvaluationContext、SimpleEvaluationContext和MethodBasedEvaluationContext。
使用Spring的表达式接口来表达式求值
表达式求值
1、字面量表达式,支类型包括字符串、数值(int、real、十六进制)、布尔值和null。字符串需要使用单引号括起来,如果字符中有单引号需要使用两个单引号。下面是几个例子:
// 这个Hello 'World中带有一个单引号
String helloWorld = (String) parser.parseExpression("'Hello ''World'").getValue();
// 输出:Hello 'World
double avogadrosNumber = (Double) parser.parseExpression("3.0221415E+23").getValue();
// 输出:3.0221415E23
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
// 输出:2147483647
boolean falseValue = (Boolean) parser.parseExpression("false").getValue();
// 输出:false
Object nullValue = parser.parseExpression("null").getValue();
// 输出:null
2、获取对象属性,数组,列表,映射,索引:首先定义一个测试类,类中有数组、列表和映射(Map)等
class SpringTestObject {
public String[] item = new String[]{"苹果", "香蕉", "梨", "西瓜"};
public List<String> list = Arrays.asList("矿泉水", "雪碧", "可乐", "牛奶");
public Map<String,String> map = new HashMap() {
{
put("man", "男");
put("woman", "女");
put("other", "中性");
}
};
public String property = "测试类";
public String getListItem(int index){
return list.get(index);
}
}
首先测试获取对象的属性,测试前需要简单介绍下rootObject,从名字理解就是根对象,Spring会从这个对象中读取属性或者调用对象的方法。
ExpressionParser parser = new SpelExpressionParser();
SpringTestObject rootObj = new SpringTestObject();
//调用方法getListItem参数是1
Object value = parser.parseExpression("getListItem(1)").getValue(rootObj);
System.out.println(""+value);
// 输出结果:雪碧
String property = (String) parser.parseExpression("property").getValue(rootObj);
System.out.println(property);
// 输出结果:测试类
获取数组中的值和设置新的值,设置使用setValue()方法,第一个参数是rootObject,第二个参数是新的值。
ExpressionParser parser = new SpelExpressionParser();
SpringTestObject rootObj = new SpringTestObject();
// 获取item数组中的第二个值
String item = (String) parser.parseExpression("item[1]").getValue(rootObj);
System.out.println(item);
// 输出结果:香蕉
// 重新设置数组中第二个值为榴莲
parser.parseExpression("item[1]").setValue(rootObj,"榴莲");
System.out.println(rootObj.item[1]);
// 输出:榴莲
列表,映射和数组是类似的就不单独解释了,示例如下:
ExpressionParser parser = new SpelExpressionParser();
SpringTestObject rootObj = new SpringTestObject();
// 获取item列表中的第二个值
String list = (String) parser.parseExpression("list[1]").getValue(rootObj);
System.out.println(list);
// 输出结果:雪碧
// 重新设置数组中第二个值为脉动
parser.parseExpression("list[1]").setValue(rootObj,"脉动");
System.out.println(""+rootObj.list.get(1));
// 输出结果:脉动
String map = (String) parser.parseExpression("map['man']").getValue(rootObj);
System.out.println(map);
// 输出结果:男
parser.parseExpression("map['man']").setValue(rootObj,"男人");
System.out.println(""+rootObj.map.get("man"));
// 输出结果:男人
3、创建数组、映射和列表:创建映射和列表使用大括号开始和结尾,映射使用key:value形式,列表使用逗号分隔,他们都可以嵌套。
//Map对象
Map map = (Map) parser.parseExpression("{'key':'value'}").getValue();
//列表
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue();
//嵌套列表
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue();
//一维数组
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
//二维数组
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();
4、方法调用:对于字符串可以直接调用字符串的方法,比如length()、size()方法等,对于rootObject可以直接调用它的方法。
ExpressionParser parser = new SpelExpressionParser();
SpringTestObject rootObject = new SpringTestObject();
Integer length = (Integer) parser.parseExpression("'hello'.length()").getValue();
String property = (String) parser.parseExpression("getListItem(0)").getValue(rootObject);
另一种方法调用,注册一个方法到StandardEvaluationContext上,registerFunction接受两个参数,第一个是方法名,第二个是Method对象,这种方法调用支持静态方法,表达式需要再前面加个#。
//首先把getListItem改成静态方法
public static String getListItem(int index){
return list.get(index);
}
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("getListItem",
SpringTestObject.class.getDeclaredMethod("getListItem", new Class[] { int.class }));
String item = parser.parseExpression("#getListItem(1)").getValue(context,String.class);
5、关系运算、逻辑运算和算术运算:关系运算符有lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!');逻辑运算符有and, or和not;算术运算有加减乘除和求余等。
ExpressionParser parser = new SpelExpressionParser();
Boolean falseValue = parser.parseExpression("1 > 3").getValue(Boolean.class);
Boolean trueValue1 = parser.parseExpression("10 gt null and 6 < 10").getValue(Boolean.class);
Integer intValue = parser.parseExpression("10 - 2").getValue(Integer.class);
//正则表达式
boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?#39;").getValue(Boolean.class);
对于和'null'比较,null代表设么也没有,其他和null大于比较总返回true。
6、获取类型和调用构造方法:获取类型使用T操作符,这个操作符也可以调用静态方法。对于在java.lang包中的类可以忽略包名,比如T(int),对于其他包需要使用全名T(java.util.Date)。
ExpressionParser parser = new SpelExpressionParser();
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
// 输出:class java.lang.String
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
// 输出:class java.util.Date
String strValue = parser.parseExpression("T(antlr4.SpringTestObject).getListItem(1)").getValue(String.class);
//输出:雪碧
String hw = parser.parseExpression("new String('Hello World')").getValue(String.class);
//输出:Hello World
7、Bean引用:需要在StandardEvaluationContext上设置BeanResolver的实例,BeanResolver意识就是从什么地方获取bean,在Spring项目上可以注入BeanFactory,在非Spring项目中可以自定义一个BeanResolver的实现。表达式中使用@字符获取bean。以下是一个Spring项目中获取bean的方式。
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
List list = parser.parseExpression("@shoppingCart.getItems()").getValue(context,List.class);
自定义MyBeanResolver实现接口BeanResolver,并实现resolve方法。
class MyBeanResolver implements BeanResolver{
Map<String,Object> beans = new HashMap(){{
put("test",new SpringTestObject());
}};
@Override
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
return beans.get(beanName);
}
}
//使用自定义的MyBeanResolver
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
String invokeBeanMethod = parser.parseExpression("@test.getListItem(2)").getValue(context, String.class);
// 输出:可乐
8、三元运算符和Elvis运算符:学过编程的都熟悉三元运算符,因为在很多语言中都存在,三元运算符使用类似if-then-else条件逻辑计算,在表达式中类似这样的"true ? 'trueExp' : 'falseExp'"。
Boolean value = parser.parseExpression("3 gt 1? 'true':'false'").getValue(Boolean.class);
//输出:true
Elvis运算符在Groovy中使用,为了简化三元运算符,语法为'notnull'?:null
String name = parser.parseExpression("'notnull'?:'null'").getValue(String.class);
//输出:notnull
9、安全的导航操作符:从一个对象中获取另一个对象并使用它属性的时候,为了防止NullPointerException会对每个对象判空,在Groovy语言中可以使用安全的导航避免空指针,语法如下:obj1?.obj2?.field。在Spring表达式中可以使用类似的语法。
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
String invokeBeanMethod = parser.parseExpression("@notTest?.getListItem(2)").getValue(context, String.class);
// notTest并不存在,结果为null,并不会抛出SpelEvaluationException
10、集合选择:从集合中选择一部分数据是比较常用的,在Java中可以循环对集合每个元素处理,在Spring 表达式中可以使用语法?[selectionExpression]获取全部符合条件的数据,使用^[selectionExpression]获取第一个元素,使用$[selectionExpression]获取最后一个元素。
ExpressionParser parser = new SpelExpressionParser();
List list = parser.parseExpression("{1,2,3,4,5,6,7,8,9}.?[#this>3]").getValue(List.class);
// [4, 5, 6, 7, 8, 9]
List firstlist = parser.parseExpression("{1,2,3,4,5,6,7,8,9}.^[#this>3]").getValue(List.class);
// [4]
List lasttlist = parser.parseExpression("{1,2,3,4,5,6,7,8,9}.$[#this>3]").getValue(List.class);
// [9]
11、表达式模板:在一个字符串中可以使用#{ }在文本中使用表达式,在解析的方法中需要传递TemplateParserContext。
String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class)
Spring SpEL的原理
在spring-expression-5.3.26.jar包中有个SpringExpresions.g文件,这个文件就是SpEL的语法文件,通过语法文件解析输入的表达式生成语法树,在语法正确的前提下计算表达式的值。
spring的语法文件
在类InternalSpelExpressionParser中处理输入的表达式,处理成TokenStream,最后生成一颗编译的语法树。
解析表达式
[注:本文部分图片来自互联网!未经授权,不得转载!每天跟着我们读更多的书]
互推传媒文章转载自第三方或本站原创生产,如需转载,请联系版权方授权,如有内容如侵犯了你的权益,请联系我们进行删除!
如若转载,请注明出处:http://www.hfwlcm.com/info/187405.html