文本引用:http://leeyee.github.io/blog/2011/06/19/spring-expression-language/
spring Expression Language (SpEL)语言支持在运行时操作和查询对象,其语法类似统一的EL语言,但是SpEL提供了额外的功能。SpEL支持以下功能:
- 文本表达式
- 布尔类和关系操作
- 正则表达式
- 类表达式
- 访问属性、数组、集合和map
- 方法调用
- 关系操作
- 赋值操作
- 调用构造函数
- Bean 引用
- 数组构造
- 内联list
- 三元操作
- 变量
- 用户自定义函数
- 集合投影
- 集合选择
- 模板表达式
使用SpEl进行表达式操作,基本操作如下:
第一步,构建解析
ExpressionParser parser =newSpelExpressionParser();
第二步,使用表达式进行解析
Expression exp = parser.parseExpression(SpEl);
第三步,获取结果
exp.getValue()
文本表达式支持字符表达式、日期、数字(正数、实数及十六进制数)、布尔类型及null.其中字符表达式需要用单引号声明。
对数字支持负数、指数及小数。默认情况下实数使用Double.parseDouble()
进行表达式类型转换
String[] lELs ={"'hello SpEL'","1.028E+7","0x12EF","true","null"};
assertEquals("hello SpEL",
exp.parseExpression(lELs[0]).getValue(String.class));
assertEquals(newDouble(10280000), exp.parseExpression(lELs[1])
.getValue(Double.class));
assertEquals(newInteger(4847),
exp.parseExpression(lELs[2]).getValue(Integer.class));
assertTrue(exp.parseExpression(lELs[3]).getValue(Boolean.class));
assertNull(exp.parseExpression(lELs[4]).getValue());
在表达式中访问类属性时直接使用属性名,属性名首字母大小写均可。
访问数组时可以使用[index]进行元素对象范围。
访问列表时,可直接使用类表的方法,通过点操作符
// 属性测试。time为SpElUtil类Date型数据,这里调用Date的属性Year
assertEquals(newInteger(2011), exp.parseExpression("time.Year + 1900")
.getValue(secontext,Integer.class));
// 属性测试。innerClass为SpElUtil类中引入的其他类。
assertEquals(29,
exp.parseExpression("innerClass.age").getValue(secontext));
// 设置SpElUtil类的numbers属性
spel.setNumbers(Arrays.asList(2,3,4,5,6,7,9));
// 访问对象属性数组通过索引
assertEquals(2, exp.parseExpression("numbers[0]").getValue(secontext));
// 访问map
assertEquals("string1",
exp.parseExpression("maps[1]")
.getValue(secontext,String.class));
2.3 内置列表
列表可以直接表示在表达式中使用{}
符号表达。{}
本身代表一个空的list
// 构造list
List<String> nums =(List<String>) exp.parseExpression(
"{'a','b','c','d'}").getValue();
assertEquals(Arrays.asList("a","b","c","d"), nums);
// 构造List<List<>>
List listOfLists =(List) exp.parseExpression("{ {1,2},{3,4} }")
.getValue(secontext);
assertEquals(Arrays.asList(1,2), listOfLists.get(0));
可以通过熟悉的java语法在表达是语言中定义。但目前不支持定义一个初始化的多维数组
// 创建没有初始值的数组
int[] a =(int[]) exp.parseExpression("new int[4]").getValue();
assertEquals(4, a.length);
// 创建带有初始值的数组
int[] b =(int[]) exp.parseExpression("new int[4]{1,2,3,4}").getValue();
assertEquals(3, b[2]);
// 创建二维数组
int[][] c =(int[][]) exp.parseExpression("new int[4][5]").getValue();
assertEquals(4, c.length);
assertEquals(5, c[0].length);
表达式中的方法调用遵循java语法。
assertEquals(
"abC2def",
exp.parseExpression("'abcdef'.replace('c','C2')").getValue(
String.class));
// 自定义类方法测试
assertFalse(exp.parseExpression("innerClass.isGt30ForAge()").getValue(
secontext,Boolean.class));
spel.getInnerClass().setAge(34);
assertTrue(exp.parseExpression("innerClass.isGt30ForAge()").getValue(
secontext,Boolean.class));
2.6 操作符
支持eq("==")
、ne("!=")
、le("<=")
、lt("<")
、gt(">")
、ge(">=")
、div("/")
、mod("%")
、not("!")
、正则表达式及instanceof
操作
assertTrue(exp.parseExpression("1 == 1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 eq 1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 > -1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 gt -1").getValue(Boolean.class));
assertTrue(exp.parseExpression("'a' < 'b'").getValue(Boolean.class));
assertTrue(exp.parseExpression("'a' lt 'b'").getValue(Boolean.class));
assertTrue(exp.parseExpression(
" new Integer(123) instanceof T(Integer) ").getValue(
Boolean.class));
assertTrue(exp.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'")
.getValue(Boolean.class));
2.6.2 逻辑操作符
逻辑操作符支持and
,or
,not
assertTrue(exp.parseExpression("true and true").getValue(Boolean.class));
2.6.3 数学运算操作符
加法运算符可以用于数字,字符串和日期。减法可以用在数字和日期。乘法和除法只能用于对数字。其他受支持的数学运算是模数(%)和指数幂(^)。运行顺序按标准运算符优先级执行
assertEquals(25.0,
exp.parseExpression("1 + 2 * 8 div 4 mod 2 + 2 ^ 3 * 3e0")
.getValue());
通过赋值操作进行属性设置。通常是调用setValue
方法,但也可以在调用getValue
时设置。
Date oldDate = spel.getTime();// 获取当前time属性值
exp.parseExpression("time").setValue(secontext,newDate(113,2,25));// 为time属性重新赋值
Date newDate = spel.getTime();// 获取赋值后的time属性值
assertEquals(2013,
exp.parseExpression("time.Year + 1900").getValue(secontext));
assertNotSame(oldDate, newDate);
// 或者使用下属方法赋值
assertEquals("abc",
exp.parseExpression("Name = 'abc'").getValue(secontext));
2.8 类型
通过特殊的T
操作符可以用来指定一个java.lang.Class
的实例。在实例话对象的静态方法将会被调用。
Class dateClass = exp.parseExpression("T(java.util.Date)").getValue(Class.class);
assertEquals("java.util.Date", dateClass.getName());
assertTrue(exp
.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class));
构造器通过new
操作被调用。在new
操作时需要指明类的完全类名(包括包路径)
SpelTestInnerClass spt = exp
.parseExpression(
"new leeyee.study.spring3.bean.SpelTestInnerClass('constructTest',23)")
.getValue(SpelTestInnerClass.class);
变量可以通过 #变量名
在表达式中被引用。变量通过StandardEvaluationContext
类的setVariable
方法进行设置
List<Integer> list =newArrayList<Integer>();
list.addAll(Arrays.asList(2,3,4,5,6,7,9));
secontext.setVariable("list", list);
List<Integer> vList =(List<Integer>) exp.parseExpression("#list")
.getValue(secontext);
assertEquals(vList, list);
#this
变量
变量#this
被定义为当前操作对象的引用。
List<Integer> nums =(List<Integer>) exp.parseExpression(
"#list.?[#this >5]").getValue(secontext);// 获取值大于5的元素集合
assertEquals(nums,Arrays.asList(6,7,9));
你可以扩展SpEL通过注册自定义函数。注册后的函数可以在表达式中通过其名称进行调用。函数的注册是通过StandardEvaluationContext
类的registerFunction
方法进行声明
context.registerFunction("len",SpElUtil.class.getDeclaredMethod("len",
newClass[]{String.class}));
assertEquals(3, exp.parseExpression("#len('abc')").getValue(context));
assertTrue(exp.parseExpression(" true ? true :false").getValue(
Boolean.class));
2.13 Elvis操作
Elvis
操作是一个短的三元操作符语法,通常在Groovy语言中使用。
Note: Elvis
操作在表达式中可以用来生成默认值,当被访问属性为空时。比如@Value
@Value("#systemPro['mail.port'] ? : 25}")
// 当mail.port为空时将默认为25
Expression ex = exp.parseExpression("name?:'name is null'");
assertEquals("override", ex.getValue(secontext,String.class));
spel.setName(null);
assertEquals("name is null", ex.getValue(secontext,String.class));
spel.setName("override");
2.14 安全导航操作
该操作是为避免空指针异常。他是来自Groovy语言的。典型的当你有一个指向对象的引用,在你访问其方法或属性时,可能需要验证该对象的方法或属性是否为空,为了避免验证,使用安全导航操作将简单的返回null
而不是空指针异常。
assertEquals("innerClass", exp.parseExpression("innerClass?.name")
.getValue(secontext,String.class));
spel.setInnerClass(null);
// 使用这种表达式可以避免抛出空指针异常
assertNull(exp.parseExpression("innerClass?.name").getValue(secontext,
String.class));
选择是一个强大的表达式语言属性,可以使用选择表达式过滤源集合,从而生成一个新的符合选择条件的集合
选择的语法为?[selectionExpression]
。他将过滤集合并且返回一个新的集合(原集合的子集)。选择语句也可用在Map中,过滤keySet及valueSet分别使用key和value关键字。另外:选择语法中,选择符合条件的结果集的第一个元素的语法为 ^[selectionExpression]
,选择最后一个元素的语法为$[selectionExpression]
spel.setNumbers(Arrays.asList(2,3,4,5,6,7,9));
List<Integer> nums =(List<Integer>) exp.parseExpression(
"numbers.?[#this >5]").getValue(secontext);
assertEquals(nums,Arrays.asList(6,7,9));
// 获取第一个元素
assertEquals(6,
exp.parseExpression("numbers.^[#this > 5]").getValue(secontext));
// 获取最后一个元素
assertEquals(9,
exp.parseExpression("numbers.$[#this > 5]").getValue(secontext));
Map<Integer,String> maps =(Map<Integer,String>) exp.parseExpression(
"maps.?[value == 'string3' ]").getValue(secontext);
Map<Integer,String> tmap =newHashMap<Integer,String>();
tmap.put(3,"string3");
assertEquals(maps, tmap);
Map<Integer,String> mapk =(Map<Integer,String>) exp.parseExpression(
"maps.?[key > 2 and key < 4 ]").getValue(secontext);
assertEquals(mapk, tmap);
语法![projectionExpression]
判断集合中每个元素是否符合语法要求
assertEquals(Arrays.asList(5,6,7,8,9), exp
.parseExpression("numbers.![#this+3]").getValue(secontext));
表达式模板允许混合文字表达式,一个或多个值计算块。每一个值计算块被声明通过可被自定义的前缀和后缀,一般选择使用#{}
作为一个定界符。
assertEquals(
" this is a test 4",
exp.parseExpression(" this is a test #{ maps.![key].get(3)}",
newTemplateParserContext()).getValue(secontext,
String.class));
一大段测试用例:
importstatic org.junit.Assert.*;
import java.util.*;
import org.junit.*;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import study.spring.context.factory.ApplicationContextFactory;
publicclassSpElUtilTest{
// spring配置文件上下文
ApplicationContext context =null;
// spring el测试辅助类
SpElUtil spel =null;
// 表达式解析对象
ExpressionParser exp =null;
// 标准赋值上下文
StandardEvaluationContext secontext;
@Before
publicvoid setUp()throwsException{
context =ApplicationContextFactory.createInstance();
spel = context.getBean(SpElUtil.class);
secontext =newStandardEvaluationContext(spel);
exp =newSpelExpressionParser();
}
@After
publicvoid tearDown()throwsException{
context =null;
spel =null;
secontext =null;
exp =null;
}
/**
* 文字表达式测试用例
*
* @throws Exception
*/
@Test
publicvoid testSpELLiteralExpression()throwsException{
// 定义各种文字表达式
String[] lELs ={"'hello SpEL'","1.028E+7","0x12EF","true","null"};
assertEquals("hello SpEL",
exp.parseExpression(lELs[0]).getValue(String.class));
assertEquals(newDouble(10280000), exp.parseExpression(lELs[1])
.getValue(Double.class));
assertEquals(newInteger(4847),
exp.parseExpression(lELs[2]).getValue(Integer.class));
assertTrue(exp.parseExpression(lELs[3]).getValue(Boolean.class));
assertNull(exp.parseExpression(lELs[4]).getValue());
}
/**
* 访问属性、数组、集合和 map 测试
*
* @throws Exception
*/
@Test
publicvoid testSpELProOrArrayOrIndexEtcExpression()throwsException{
// 属性测试。time为SpElUtil类Date型数据,这里调用Date的属性Year
assertEquals(newInteger(2011), exp.parseExpression("time.Year + 1900")
.getValue(secontext,Integer.class));
// 属性测试。innerClass为SpElUtil类中引入的其他类。
assertEquals(29,
exp.parseExpression("innerClass.age").getValue(secontext));
// 设置SpElUtil类的numbers属性
spel.setNumbers(Arrays.asList(2,3,4,5,6,7,9));
// 访问对象属性数组通过索引
assertEquals(2, exp.parseExpression("numbers[0]").getValue(secontext));
// 访问map
assertEquals("string1",
exp.parseExpression("maps[1]")
.getValue(secontext,String.class));
}
/**
* 内联list测试
*
* @throws Exception
*/
@SuppressWarnings({"unchecked","rawtypes"})
@Test
publicvoid testSpELInnerListExpression()throwsException{
// 构造list
List<String> nums =(List<String>) exp.parseExpression(
"{'a','b','c','d'}").getValue();
assertEquals(Arrays.asList("a","b","c","d"), nums);
// 构造List<List<>>
List listOfLists =(List) exp.parseExpression("{ {1,2},{3,4} }")
.getValue(secontext);
assertEquals(Arrays.asList(1,2), listOfLists.get(0));
}
/**
* Array 构造测试
*
* @throws Exception
*/
@Test
publicvoid testSpELArrayConstructionExcpression()throwsException{
// 创建没有初始值的数组
int[] a =(int[]) exp.parseExpression("new int[4]").getValue();
assertEquals(4, a.length);
// 创建带有初始值的数组
int[] b =(int[]) exp.parseExpression("new int[4]{1,2,3,4}").getValue();
assertEquals(3, b[2]);
// 创建二维数组
int[][] c =(int[][]) exp.parseExpression("new int[4][5]").getValue();
assertEquals(4, c.length);
assertEquals(5, c[0].length);
}
/**
* 方法表达式测试
*
* @throws Exception
*/
@Test
publicvoid testSpELMethodExcpression()throwsException{
// String.replace方法测试
assertEquals(
"abC2def",
exp.parseExpression("'abcdef'.replace('c','C2')").getValue(
String.class));
// 自定义类方法测试
assertFalse(exp.parseExpression("innerClass.isGt30ForAge()").getValue(
secontext,Boolean.class));
spel.getInnerClass().setAge(34);
assertTrue(exp.parseExpression("innerClass.isGt30ForAge()").getValue(
secontext,Boolean.class));
}
/**
* 操作符、正则表达式测试
*
* @throws Exception
*/
@Test
publicvoid testSpElOperatorAndRegExpression()throwsException{
// 关系操作
assertTrue(exp.parseExpression("1 == 1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 eq 1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 > -1").getValue(Boolean.class));
assertTrue(exp.parseExpression("1 gt -1").getValue(Boolean.class));
assertTrue(exp.parseExpression("'a' < 'b'").getValue(Boolean.class));
assertTrue(exp.parseExpression("'a' lt 'b'").getValue(Boolean.class));
assertTrue(exp.parseExpression(
" new Integer(123) instanceof T(Integer) ").getValue(
Boolean.class));
assertTrue(exp.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'")
.getValue(Boolean.class));
// 逻辑操作
assertTrue(exp.parseExpression("true and true").getValue(Boolean.class));
assertTrue(exp.parseExpression("true or false").getValue(Boolean.class));
assertFalse(exp.parseExpression("innerClass.isGt30ForAge() and false ")
.getValue(secontext,Boolean.class));
assertFalse(exp.parseExpression("!innerClass.isGt30ForAge() and true ")
.getValue(secontext,Boolean.class));
assertTrue(exp.parseExpression("!false").getValue(Boolean.class));
// 运算操作
assertEquals(2, exp.parseExpression("1 + 1").getValue());
assertEquals("ABab",
exp.parseExpression("'AB' + 'ab'").getValue(String.class));
assertEquals(25.0,
exp.parseExpression("1 + 2 * 8 div 4 mod 2 + 2 ^ 3 * 3e0")
.getValue());
assertEquals(exp.parseExpression("1 + 2 * 8 / 4 % 2 + 2 ^ 3 ")
.getValue(),
exp.parseExpression("1 + 2 * 8 div 4 mod 2 + 2 ^ 3 ")
.getValue());
}
/**
* 赋值表达式测试
*
* @throws Exception
*/
@SuppressWarnings("deprecation")
@Test
publicvoid testSpelAssignmentExpression()throwsException{
Date oldDate = spel.getTime();// 获取当前time属性值
exp.parseExpression("time").setValue(secontext,newDate(113,2,25));// 为time属性重新赋值
Date newDate = spel.getTime();// 获取赋值后的time属性值
assertEquals(2013,
exp.parseExpression("time.Year + 1900").getValue(secontext));
assertNotSame(oldDate, newDate);
// 或者使用下属方法赋值
assertEquals("abc",
exp.parseExpression("Name = 'abc'").getValue(secontext));
// 还原time默认,避免后续测试错误
spel.setTime(oldDate);
spel.setName("override");
}
/**
* 类型操作表达式测试
*
* @throws Exception
*/
@SuppressWarnings("rawtypes")
@Test
publicvoid testSpelTypesExpression()throwsException{
Class dateClass = exp.parseExpression("T(java.util.Date)").getValue(
Class.class);
assertEquals("java.util.Date", dateClass.getName());
assertTrue(exp
.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class));
}
/**
* 构造函数调用测试
*
* @throws Exception
*/
@Test
publicvoid testSpelConstructorsExpression()throwsException{
SpelTestInnerClass spt = exp
.parseExpression(
"new study.spring.beans.SpelTestInnerClass('constructTest',23)")
.getValue(SpelTestInnerClass.class);
assertEquals(23, spt.getAge());
assertEquals("constructTest", spt.getName());
}
/**
* 设置变量测试
*
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
publicvoid testSpelVariablesExpression()throwsException{
List<Integer> list =newArrayList<Integer>();
list.addAll(Arrays.asList(2,3,4,5,6,7,9));
secontext.setVariable("list", list);
List<Integer> vList =(List<Integer>) exp.parseExpression("#list")
.getValue(secontext);
assertEquals(vList, list);
List<Integer> nums =(List<Integer>) exp.parseExpression(
"#list.?[#this >5]").getValue(secontext);// 获取值大于5的元素集合
assertEquals(nums,Arrays.asList(6,7,9));
}
/**
* 自定义函数表达式测试
*
* @throws Exception
*/
@Test
publicvoid testSpelFunctionExpression()throwsException{
StandardEvaluationContext context =newStandardEvaluationContext();
context.registerFunction("len",SpElUtil.class.getDeclaredMethod("len",
newClass[]{String.class}));
assertEquals(3, exp.parseExpression("#len('abc')").getValue(context));
}
@Test
publicvoid testSpelBeanExpression()throwsException{
}
/**
* 三元操作测试
*
* @throws Exception
*/
@Test
publicvoid testSpelTernaryOperatorExpression()throwsException{
assertTrue(exp.parseExpression(" true ? true :false").getValue(
Boolean.class));
assertEquals("is true",
exp.parseExpression(" 1 == 1 ? 'is true' :'is false'")
.getValue(String.class));
}
/**
* Elvis 操作测试
*
* @throws Exception
*/
@Test
publicvoid testSpeleElvisOperatorExpression()throwsException{
Expression ex = exp.parseExpression("name?:'name is null'");
assertEquals("override", ex.getValue(secontext,String.class));
spel.setName(null);
assertEquals("name is null", ex.getValue(secontext,String.class));
spel.setName("override");
}
/**
* 安全导航操作测试
*
* @throws Exception
*/
@Test
publicvoid testSpelSafeNavOperatorExpression()throwsException{
assertEquals("innerClass", exp.parseExpression("innerClass?.name")
.getValue(secontext,String.class));
spel.setInnerClass(null);
// 使用这种表达式可以避免抛出空指针异常
assertNull(exp.parseExpression("innerClass?.name").getValue(secontext,
String.class));
}
/**
* 集合选择表达式测试
*
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
publicvoid testSpelCollectionSelectExpression()throwsException{
spel.setNumbers(Arrays.asList(2,3,4,5,6,7,9));
List<Integer> nums =(List<Integer>) exp.parseExpression(
"numbers.?[#this >5]").getValue(secontext);
assertEquals(nums,Arrays.asList(6,7,9));
// 获取第一个元素
assertEquals(6,
exp.parseExpression("numbers.^[#this > 5]").getValue(secontext));
// 获取最后一个元素
assertEquals(9,
exp.parseExpression("numbers.$[#this > 5]").getValue(secontext));
Map<Integer,String> maps =(Map<Integer,String>) exp.parseExpression(
"maps.?[value == 'string3' ]").getValue(secontext);
Map<Integer,String> tmap =newHashMap<Integer,String>();
tmap.put(3,"string3");
assertEquals(maps, tmap);
Map<Integer,String> mapk =(Map<Integer,String>) exp.parseExpression(
"maps.?[key > 2 and key < 4 ]").getValue(secontext);
assertEquals(mapk, tmap);
}
/**
* 投影表达式测试
*
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
publicvoid testSpelProjectionExpression()throwsException{
spel.setNumbers(Arrays.asList(2,3,4,5,6));
assertEquals(Arrays.asList(5,6,7,8,9),
exp.parseExpression("numbers.![#this+3]").getValue(secontext));
List<Integer> keys =(List<Integer>) exp.parseExpression("maps.![key]")
.getValue(secontext);
assertEquals(keys,Arrays.asList(1,2,3,4));
List<String> mapv =(List<String>) exp.parseExpression("maps.![value]")
.getValue(secontext);
assertEquals(mapv,
Arrays.asList("string1","string2","string3","String4"));
List<Boolean> mapK =(List<Boolean>) exp.parseExpression(
"maps.![key > 2 and value !='String4']").getValue(secontext);
assertEquals(mapK,Arrays.asList(false,false,true,false));
}
/**
* 模板语言测试
*
* @throws Exception
*/
@Test
publicvoid testSpelTemplate()throwsException{
assertEquals(
" this is a test 4",
exp.parseExpression(" this is a test #{ maps.![key].get(3)}",
newTemplateParserContext()).getValue(secontext,
String.class));
}
}