本文参考自Spring官方文档 Spring EL。
在Java上有很多表达式语言,在很多领域有各种各样的应用。我们应该很熟悉Java EE的表达式语言吧,让我们能在JSP中随意插入数据。Spring也提供了一个表达式语言并添加了自己的功能,以便可以方便的和各种Spring框架交互。我们在项目中不需要手动管理Spring表达式的这些接口和实例,只需要在合适的时候编写Spring表达式,转换器就会自动解析并转换表达式。
当然,为了说明一下Spring表达式,我们在这里还是手动创建一个解析器来解析表达式。下面是简单的单元测试。
public class SpringElTest {
private static ExpressionParser parser = new SpelExpressionParser();
@Test
public void testHelloWorld() {
Expression expression = parser.parseExpression("'你好世界!'");
String result = (String) expression.getValue();
System.out.println(result);
}
}
还可以使用更复杂的例子。
@Test
public void testStringOperation() {
Expression expression = parser.parseExpression("'你好'.concat('世界!')");
String result = (String) expression.getValue();
System.out.println(result);
expression = parser.parseExpression("'Hello world!'.toUpperCase()");
result = expression.getValue(String.class);
System.out.println(result);
}
Spring文档解释了如何创建和使用Spring表达式的各个接口、编译和配置等等。但是一般情况下我们用不到这些功能。这里就只介绍一下Spring El的语法。如果需要详细了解这些信息的话还是直接看文档吧。
这部分介绍了Spring EL表达式的使用。为了省事就直接引用了文档的代码了。下面这些代码没有说明的话都是Spring文档的例子。
表达式支持各种类型的字面值。字符串字面值需要使用单引号包括,其他类型字面值直接写就行。
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
Spring表达式支持属性,只要使用点号引用属性即可。
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
数组和列表可以使用方括号语法引用对应索引的元素。
ExpressionParser parser = new SpelExpressionParser();
// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
teslaContext, String.class);
// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
societyContext, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
societyContext, String.class);
Map类型也可以使用方括号语法引用键对应的值。
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
我们可以直接在表达式中定义集合,这就是内联。内联集合使用花括号语法。
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
内联Map则要复杂一点,使用类似JSON的语法,键和值之间用冒号分隔开。
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
数组使用类似Java的语法,可以给出初始值,多维数组也受支持。
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
方法和Java语法一样。
// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
表达式中支持各种运算符,运算规则和Java规则类似。唯一需要注意的是空值的处理,假设有非空值val
,那么下面的表达式恒为真:val > null
。这一点需要注意。
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
逻辑运算符可以使用and
、or
和!
。
特殊的T
运算符可以获取表达式对象的类型。
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
在表达式中,使用new关键字来调用构造器。
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
在表达式上下文中,我们可以设置新变量。然后在表达式中使用#变量名
访问变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context);
System.out.println(tesla.getName()) // "Mike Tesla"
#this
和#root
代表了表达式上下文的对象,#root
就是当前的表达式上下文对象,#this
则根据当前求值环境的不同而变化。下面的例子中,#this
即每次循环的值。
// create an array of integers
List primes = new ArrayList();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List primesGreaterThanTen = (List) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
这是Spring表达式独有的功能,我们可以在表达式中引用配置文件定义的其他Bean,这需要语法@Bean名称
。
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);
如果需要获取Bean工厂本身而不是它构造的Bean,可以使用&Bean名称
。
Object bean = parser.parseExpression("&foo").getValue(context);
和Java的三元运算符类似。
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
在一些编程语言中(比如C#、Kotlin等)提供该功能,语法是?:
。意义是当某变量不为空的时候使用该变量,当该变量为空的时候使用指定的默认值。
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
这是来自Groovy的一个功能,语法是?.
,当然有些语言也提供了这个功能。当我们对对象的某个属性求值时,如果该对象本身为空,就会抛出空指针异常,如果使用安全导航运算符,空对象的属性就会简单的返回空。
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
这有点类似Java 8的Filter流方法,作用是选择或者说是过滤,语法是集合对象.?[选择表达式]
,Spring会迭代集合对象的每一个元素,并使用选择表达式判断该元素是否满足条件,最后返回由满足条件的元素组成的新集合。下面的例子就返回了值大于27的新Map。
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
这类似Java 8的Map流方法或者SQL语言的选择语句,作用是将一个集合中所有元素的某属性抽取出来,组成一个新集合。语法是![投影表达式]
。下面的例子选出了由Member的placeOfBirth的city属性组成的新集合。
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
表达式模板使用#{}
定义,它允许我们混合多种结果。下面就是一个例子,首先Spring会先对模板中的表达式求值,在这里是返回一个随机值,然后将结果和外部的表达式组合起来。最终的结果就向下面这样了。
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// 结果是 "random number is 0.7038186818312008"
如果表达式只是一个简单的表达式,就不需要使用模板。只有表达式有很多表达式组成时才需要。