1. SpEL功能简介

它是spring生态里面的一个功能强大的描述语言,支在在运行期间对象图里面的数据查询和数据操作。语法和标准的EL一样,但是支持一些额外的功能特性,最显著的就是方法调用以及基本字符串模板函数功能。

SpEL是spring的产品列表中的基本功能。

2. 特性概要

Literal expressions
Method invocation
Accessing properties, arrays, lists, maps
Inline lists
Array construction
Relational operators
Assignment
Class Expression
Constructors
Variables
Ternary Operator (If-Then-Else)
Safe Navigation operator
Collection Selection
Collection Projection
Expression templating

3. 基本语法结构

#{ } 标记会提示Spring 这个标记里的内容是SpEL表达式。 当然,这个可以通过Expression templating功能扩展做改造
#{rootBean.nestBean.propertiy} “.”操作符表示属性或方法引用,支持层次调用
#{aList[0] } 数组和列表使用方括号获得内容
#{aMap[key] } maps使用方括号获得内容
#{rootBean?.propertiy} 此处"?"是安全导航运算符器,避免空指针异常
#{condition ? trueValue : falseValue} 三元运算符(IF-THEN-ELSE)
#{valueA?:defaultValue} Elvis操作符,当valueA为空时赋值defaultValue

4. 基本案例介绍

我这里要介绍的SpEL的特性案例,都是在Spring-boot 1.5.4的环境下进行的。SpEL不一定要是基于Web的,我们就在纯后台程序的环境下,验证这个SpEL的神秘而强大的特性。另外,SpEL默认的日志输出是SLF4j,我不习惯也不喜欢,我将其改造成了Log4j,改造配置也很简单,主要是pom.xml里面将Spring-boot默认的日志配置exclude掉。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.</modelVersion> <groupId>com.roomdis</groupId>
<artifactId>springel</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <name>Spring Expression Language</name>
<description>Spring boot project</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5..RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!-- Spring boot支持log4j,并停掉spring-boot默认的日志功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<artifactId>log4j-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加log4j的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>

</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

4.1 Literal expressions

@SpringBootApplication
public class LiteralExpressionApplication {
static Logger logger = Logger.getLogger(LiteralExpressionApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
logger.info(message);
}
}

输出的内容为:Hello World
接口ExpressionParser主要用来解析表达式字符串,在这个例子中,表达式字符串就是上面单引号里面的内容。接口Expression主要用来评估前面的表达式字符串。这里,调用parser.parseExpression可能抛出异常ParseException,调用exp.getValue可能抛出异常EvaluationException。

4.2 Method invocation

@SpringBootApplication
public class MethodInvocationApplication {
static Logger logger = Logger.getLogger(MethodInvocationApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
logger.info(message);
}
}

输出的内容为:Hello World!
表达式中,字符串'Hello World'进行了函数调用concat,将字符串'!'进行了拼接,最终得到了一个'Hello World!'的结果。

4.3 Accessing properties, arrays, lists, maps

这一步,将涉及好几个部分的功能介绍,每一步,只写相关的代码,每一个特性的验证处理,代码里面已经分隔开了。

public class AccessingPropertiesApplication {
static Logger logger = Logger.getLogger(AccessingPropertiesApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
//a. 调用String这个javaBean的'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
logger.info("字节内容:"+ bytes); //b.嵌套的bean实例方法调用,通过'.'运算符
exp = parser.parseExpression("'Hello World'.bytes.length");
int len = (Integer) exp.getValue();
logger.info("字节长度:" + len); //c. property訪問
GregorianCalendar c = new GregorianCalendar();
c.set(, , );
//Inventor的构造函数参数分别是:name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
parser = new SpelExpressionParser();
exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
logger.info("Inventor: " + name); //对对象实例的成员进行操作。 evals to 1856, 注意纪年中,起点是从1900开始。
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
//Inventor tesla设置出生地(瞎写的信息)。
tesla.setPlaceOfBirth(new PlaceOfBirth("America city", "America"));
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
logger.info("year: " + year + ", city: " + city); //d. array, list操作
// 先测试验证array
tesla.setInventions(new String []{"交流点","交流电发电机","交流电变压器","变形记里面的缩小器"});
EvaluationContext teslaContext = new StandardEvaluationContext(tesla);
String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class);
logger.info("Array: " + invention);
//list测试验证
Society society = new Society();
society.addMember(tesla);
StandardEvaluationContext societyContext = new StandardEvaluationContext(society);
// evaluates to "Nikola Tesla"
String mName = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String mInvention = parser.parseExpression("Members[0].Inventions[2]").getValue(societyContext, String.class);
logger.info("List: mName= " + mName + ", mInvention= " + mInvention); //e. Map的操作
//首先构建数据环境
GregorianCalendar cm = new GregorianCalendar();
cm.set(, , );
Inventor idv = new Inventor("Idovr", cm.getTime(), "China,haha");
Society soc = new Society();
idv.setPlaceOfBirth(new PlaceOfBirth("Wuhan","China"));
soc.addOfficer(Advisors, idv);
soc.addOfficer(President, tesla);
EvaluationContext socCtxt = new StandardEvaluationContext(soc);
Inventor pupin = parser.parseExpression("Officers['president']").getValue(socCtxt, Inventor.class);
String mCity = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(socCtxt, String.class);
logger.info("Map case 1: " + mCity);
// setting values
Expression mExp = parser.parseExpression("Officers['advisors'].PlaceOfBirth.Country");
mExp.setValue(socCtxt, "Croatia");
//下面注释掉的,是官方的做法,这个是有问题的,基于我的研究环境
//parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(socCtxt, "Croatia");
//String country = parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").getValue(socCtxt, String.class);
String country = mExp.getValue(socCtxt, String.class);
logger.info("Map case 2: " + country);
}
}

注意:

1. SpEL对表达式中的property的首字母不区分大小写,这个很重要。

2. 这里的操作,从另一个方面说明,SpEL操作,可以访问实例的成员,成员的成员等嵌套的访问,成员既可以是基础成员,也可以是bean。如此看来,获取实例的参数的值,不再是仅仅依赖反射可以实现,SpEL的操作,也一样可以解决某些场景的问题。

3. map的操作,和基本的操作一样,通过键的内容作为key获取对应的value。整个逻辑,同样是支持嵌套的操作。

4.4 Inline lists

list可以直接在一个表达式里面写出来,借助于{}括号,相当于创建list实例的方式多了一种,不仅仅只有new的这一套,通过SpEL也可以完成这个功能。

@SpringBootApplication
public class InlineListApplication {
static Logger logger = Logger.getLogger(InlineListApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue();
logger.info("单独列表:" + numbers.toString());
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue();
logger.info("二维列表:" + listOfLists.toString());
}
}

输出结果是这样的:
2018-07-16 17:39:09,035 INFO => com.roomdis.rurale.InlineListApplication.main(InlineListApplication.java:24): 单独列表:[1, 2, 3, 4]
2018-07-16 17:39:09,037 INFO => com.roomdis.rurale.InlineListApplication.main(InlineListApplication.java:26): 二维列表:[[a, b], [x, y]]

4.5 Array construction

通常情况下的数组定义,都是new出来的,这里,类似上面的inline lists,也可以通过SpEL来创建,空数组或者有初始值的都可以做到,一维或者多维的也不是问题。

@SpringBootApplication
public class ArrayConstructionApplication {
static Logger logger = Logger.getLogger(ArrayConstructionApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
for (int it: numbers1) {
logger.info("numbers1 element: " + it);
}
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();
for (int it: numbers2) {
logger.info("numbers2 element: " + it);
}
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();
for(int it[]: numbers3) {
logger.info("numbers3 dim 2 size: " + it.length);
}
}
}

输出结果如下:
2018-07-16 17:54:10,681 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 1
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 2
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 3
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5

注意:目前不支持给多维数组在创建的时候初始化。

4.6 Relational operators

常见的关系运算,包含==,!=,<,>,<=,>=

public class RelationalOperatorApplication {
static Logger logger = Logger.getLogger(RelationalOperatorApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
// evaluates to true
boolean trueValue1 = parser.parseExpression("2 == 2").getValue(Boolean.class);
logger.info("2 == 2: " + trueValue1);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
logger.info("2 < -5.0: " + falseValue);
// evaluates to true
boolean trueValue2 = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
logger.info("'black' < 'block': " + trueValue2);
//任何数都比null大
boolean nullTrue = parser.parseExpression("0 > null").getValue(Boolean.class);
logger.info("0 > null: " + nullTrue);
boolean nullFalse = parser.parseExpression("-1 < null").getValue(Boolean.class);
logger.info("-1 < null: " + nullFalse);
}
}

输出结果:
2018-07-16 19:16:28,936 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:18): 2 == 2: true
2018-07-16 19:16:28,939 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:21): 2 < -5.0: false
2018-07-16 19:16:28,939 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:24): 'black' < 'block': true
2018-07-16 19:16:28,940 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:27): 0 > null: true
2018-07-16 19:16:28,940 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:30): -1 < null: false

注意:大于/小于的比较,当遇到和null比较时,遵循一个很简单的规则:null被当做什么都没有(不是当做0哟)。因此,任何值都是比null大的(x > null永远都是true). 与此同时,没有什么值会比什么都没有小(x < null总是false)

除了常规的比较运算符操作外,SpEL还支持正则匹配,主要是matches操作符。

// evaluates to false
boolean instanceofValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
logger.info("'xyz' instanceof T(int): " + instanceofValue);
// evaluates to true
boolean matchTrueValue = parser.parseExpression("'5.00' matches '^\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
logger.info("'5.00' matches '^\\d+(\\.\\d{2})?$': " + matchTrueValue);
//evaluates to false
boolean matchFalseValue = parser.parseExpression("'5.0067' matches '^\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
logger.info("'5.0067' matches '^\\d+(\\.\\d{2})?$': " + matchFalseValue);

注意:每一种符号化的运算符,都有一个对应的字母符形式的符号,主要是考虑到在某些场合可能出现转义(XML文档),对应关系如下(不区分大小写):
lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!').

SpEL还支持逻辑运算以及算术运算,下面这里简单例举两个例子:

//逻辑运算,支持and,or,not以及对应的组合
// -- AND -- evaluates to false
boolean andFalseValue = parser.parseExpression("true and false").getValue(Boolean.class);
logger.info("true and false: " + andFalseValue);
// -- NOT -- evaluates to false
boolean notFalseValue = parser.parseExpression("!true").getValue(Boolean.class);
logger.info("!true: " + notFalseValue);
//算术运算,支持 +, -, *, /, mod
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);
logger.info("1 + 1: " + two);
String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class);
logger.info("'test' + ' ' + 'string': " + testString);
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);
logger.info("1 - -3: " + four);

4.7 Assignment

给一个javaBean对象赋值,通常都是采用setValue方法,当然,也可以通过getValue的方式实现同样的功能。

@SpringBootApplication
public class AssignmentApplication {
static Logger logger = Logger.getLogger(AssignmentApplication.class);
public static void main(String []args) {
Inventor inventor = new Inventor();
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
logger.info("assignment <> inventor'name is: " + inventor.getName());
// alternatively
String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
logger.info("assignment <> inventor'name is: " + aleks);
}
}

4.8 Class Expression

特殊的T运算符可以用来指定一个java.lang.Class类的实例,即Class的实例。另外,静态方法,也可以用T运算符指定。 T()可以用来指定java.lang包下面的类型,不必要全路径指明,但是,其他路径下的类型,必须全路径指明。

@SpringBootApplication
public class TypesApplication {
static Logger logger = Logger.getLogger(TypesApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
//非java.lang包下面的Class要指明全路径
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
//String是java.lang包路径下的类,所以可以不用指明全路径
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
//T用来指定静态方法
boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
logger.info("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR :" + trueValue);
}
}

注意:T(),这个运算符就告诉SpEL将运算符内的字符串当成是“类”处理,避免SpEL进行其他处理,尤其在调用某个类的静态方法时。

4.9 Constructors

构造器函数能够被new运算符调用,全路径类名需要指定,除了基础类型以及String类型。

@SpringBootApplication
public class ConstructorApplication {
static Logger logger = Logger.getLogger(ConstructorApplication.class);
public static void main(String []args){
ExpressionParser p = new SpelExpressionParser();
Inventor einstein = p.parseExpression("new com.roomdis.rurale.bean.Inventor('Albert Einstein','German')").getValue(Inventor.class);
logger.info("constructor <> inventor name and nation: " + einstein.getName() + ", " + einstein.getNationality());
}
}

4.10 Variables

变量在表达式中是可以被引用到的,通过#variableName。变量赋值是通过setValue在StandandExpressionContext中。

@SpringBootApplication
public class VariableApplication {
static Logger logger = Logger.getLogger(VariableApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
//在StandardEvaluationContext这个上下文环境中定义一个新的变量newName,并给他赋值Mike Tesla
context.setVariable("newName", "Mike Tesla");
//通过getValue的方式,给javaBean赋值。
parser.parseExpression("Name = #newName").getValue(context);
logger.info(tesla.getName()); //#this 和 #root的介绍
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(,,,,,,));
StandardEvaluationContext context_sharp = new StandardEvaluationContext();
context_sharp.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...}),即用到集合选择的功能
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context_sharp);
}
}

注意:SpEL中获取变量的值,是通过#variableName操作的,这个变量可以是任何类型,既可以是基础类型,也可以是bean实例。
#this表示当前操作的数据对象,#root表示SpEL表达式上下文的根节点对象,这里要说明的概念就是上下文环境,即StandardEvaluationContext定义的环境,虽然他不是SpEL必须定义的,但是SpEL默认是将ApplicationContext定义为根节点对象,即默认#root的值为ApplicationContext。一般的,给StandardEvaluationContext指定一个对象,例如本例中前半部分,将tesla作为StandardEvaluationContext的构造函数入参,即此时的#root为telsa。

4.11 Ternary Operator (If-Then-Else)

就是一般的正常使用的三目运算符。三目运算有一个特殊的运算,即Elvis Operator。
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);这个是正常的三目运算。
针对三目运算符,在SpEL里面,针对下面的场景,对其做了一种简化,即当变量不等null时取变量当前值的场景。
String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";
简写成String displayName = name?: "Unknown";
完整的验证案例程序:

public class TernaryApplication {
static Logger logger = Logger.getLogger(TernaryApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
//在StandardEvaluationContext这个上下文环境中定义一个新的变量newName,并给他赋值Mike Tesla
context.setVariable("newName", "Mike Tesla");
//通过getValue的方式,给javaBean赋值。
parser.parseExpression("Name = #newName").getValue(context);
String expression = "name == 'Mike Tesla'?'修改实例成员变量name成功.':'修改实例成员变量name失败.'";
String name = tesla.getName();
logger.info("new name: " + name);
String result = parser.parseExpression(expression).getValue(context, String.class);
logger.info(result); //下面是验证elivs运算符的案例
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
logger.info(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
logger.info(name); // Elvis Presley
}
}

4.12 Safe Navigation operator

安全导航运算符,主要用来避免实例操作出现NullPointerException。典型的是,当你访问一个对象的实例,你首先要确保这个实例不是null,才有可能去访问其方法或者属性。SpEL通过Safe Navigation运算符可以避免这种因为是null抛出异常,取而代之的是用null返回值。

public class SafeNavigationApplication {
static Logger logger = Logger.getLogger(SafeNavigationApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
logger.info("before <> city: " + city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
logger.info("after <> city: " + city); // null - does not throw NullPointerException!!!
}
}

4.13 Collection Selection

集合选择是一个非常有用的表达式语言特性,它方便你实现从一个集合选择符合条件的元素放入另外一个集合中。语法是:?[selectionExpression]
集合选择功能既可以用在list列表,也可以用于map结构。

public class CollectionApplication {
static Logger logger = Logger.getLogger(CollectionApplication.class);
public static void main(String []args) {
//map中指定key的查找
ExpressionParser parser = new SpelExpressionParser();
Inventor in1 = new Inventor("zhangsan", "China");
Inventor in2 = new Inventor("wangwu", "America");
Inventor in3 = new Inventor("tesla", "Serbian");
Society society = new Society();
society.addMember(in1);
society.addMember(in2);
society.addMember(in3);
EvaluationContext societyContext = new StandardEvaluationContext(society);
List<Inventor> list = (List<Inventor>) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
logger.info("Inventor list count: " + list.size()); //map中基于value的条件进行查找
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("zhangsan", );
map.put("lishi", );
map.put("wangwu", );
map.put("zhaoliu", );
society.addGust(map);
societyContext = new StandardEvaluationContext(society);
Map<String, Integer> newMap = (Map<String, Integer>)parser.parseExpression("mguest.?[value<27]").getValue(societyContext);
for(String ky : newMap.keySet()){
logger.info("name: " + ky + ", value: " + newMap.get(ky));
} //验证 ^[selectionExpression]取第一个满足条件的,$[selectionExpression]取最后一个满足条件的
Map<String, Integer> v1 = (HashMap) parser.parseExpression("mguest.^[value<27]").getValue(societyContext);
logger.info("first element: " + v1.toString());
Map<String, Integer> v2 = (HashMap) parser.parseExpression("mguest.$[value<27]").getValue(societyContext);
logger.info("last element: " + v2.toString());
}
}

这个时候,若Society这个类种的mguest成员是非public的话,会遇到下面的错误:

Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'mguest' cannot be found on object of type 'com.roomdis.rurale.bean.Society' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:)
at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:)
at com.roomdis.rurale.CollectionApplication.main(CollectionApplication.java:)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:)
at java.lang.reflect.Method.invoke(Method.java:)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:)

可以通过^[selectionExpression]获取第一个成员,通过$[selectionExpression]取最后一个元素。具体例子,上面的案例程序中最后一部分就是这个内容。

4.14 Collection Projection

集合投射就是在一个集合的基础上基于一定的规则抽取出相应的数据,重新生成一个集合。表达式![projectionExpression]

public class CollectionProjectionApplication {
static Logger logger = Logger.getLogger(CollectionProjectionApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
Inventor iv1 = new Inventor("Tesla","Serbian");
iv1.setPlaceOfBirth(new PlaceOfBirth("Buzhidao", "Serbian"));
Inventor iv2 = new Inventor("Mayun","China");
iv2.setPlaceOfBirth(new PlaceOfBirth("Hangzhou", "China"));
Inventor iv3 = new Inventor("Sunzhengyi", "Japan");
iv3.setPlaceOfBirth(new PlaceOfBirth("Tokyo", "Japan"));
Society society = new Society();
society.addMember(iv1);
society.addMember(iv2);
society.addMember(iv3);
EvaluationContext context = new StandardEvaluationContext(society);
List<String> placesOfBirth = (List<String>)parser.parseExpression("Members.![placeOfBirth.city]").getValue(context);
for (String pob: placesOfBirth){
logger.info("P O B: " + pob);
}
}
}

集合投射,对map也可以适用,只是获取后的结果是一个list,里面的成员是map.entry.

4.15 Expression templating

表达式模板允许用户将字符串和一个或者多个EL表达式连接起来。每个EL表达式用一个#{}括起来。

public class ExpressionTemplateApplication {
static Logger logger = Logger.getLogger(ExpressionTemplateApplication.class);
public static void main(String []args) {
ExpressionParser parser = new SpelExpressionParser();
String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class);
logger.info("Expression Template: " + randomPhrase);
}
} class TemplateParserContext implements ParserContext { /*
*启用表达式模板功能,要继承ParserContext接口,实现其中的三个基本方法,分别是告知解析器这个表达式是否启用模板,模板的起始和结束符。
*/ @Override
public boolean isTemplate() {
return true;
} @Override
public String getExpressionPrefix() {
return "#{";
} @Override
public String getExpressionSuffix() {
return "}";
}
}

SpEL的功能很大,

Spring生态研习【二】:SpEL(Spring Expression Language)的更多相关文章

  1. Spring Boot 2 (二):Spring Boot 2 动态 Banner

    Spring Boot 2 (二):Spring Boot 2 动态 Banner Spring Boot 2.0 提供了很多新特性,其中就有一个小彩蛋:动态 Banner. 一.配置依赖 使用 Sp ...

  2. Spring Boot(十二):spring boot如何测试打包部署

    Spring Boot(十二):spring boot如何测试打包部署 一.开发阶段 1,单元测试 在开发阶段的时候最重要的是单元测试了,springboot对单元测试的支持已经很完善了. (1)在p ...

  3. Spring生态研习【一】:定时任务Spring-task

    本系列具体研究一下spring生态中的重要或者常用的功能套件,今天从定时任务开始,主要是spring-task.至于quartz,下次找个时间再总结. 我的验证环境,是SpringCloud体系下,基 ...

  4. Spring学习(二)——Spring中的AOP的初步理解[转]

      [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring. ...

  5. Spring学习(二)——Spring中的AOP的初步理解

    [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...

  6. Spring知识点总结(二)之Spring IOC

    1.创建bean类,并在spring中进行配置交由spring来管理1. IOC(DI) - 控制反转(依赖注入)    所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管 ...

  7. Spring实战(二)Spring容器和bean的生命周期

    引入问题: 在XML配置文件中配置bean后,这些文件又是如何被加载的?它们被加载到哪里去了? Spring容器——框架核心 1.什么是Spring容器?它的功能是什么? 在基于Spring的应用中, ...

  8. Spring学习(二)--Spring的IOC

    1.依赖反转模式 依赖反转:高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口.抽象接口不应该依赖于具体实现.而具体实现则应该依赖于抽象接口. 在面向对象编程领域中,依赖反转原则(Depe ...

  9. Spring生态研习【四】:Springboot+mybatis(探坑记)

    这里主要是介绍在springboot里面通过xml的方式进行配置,因为xml的配置相对后台复杂的系统来说,能够使得系统的配置和逻辑实现分离,避免配置和代码逻辑过度耦合,xml的配置模式能够最大限度的实 ...

随机推荐

  1. 【分布式搜索引擎】Elasticsearch中的基本概念

    一.Elasticsearch中的基本概念 以下概念基于这个例子:存储员工数据,每个文档代表一个员工 1)索引(index)  在Elasticsearch中存储数据的行为就叫做索引(indexing ...

  2. Qt-Designer打不开

    安装Qt后双击桌面的Designer没有反应,解决办法就是将安装路径里的qwebengineview.dll文件后缀名加个".bak".

  3. SpringCloud服务负载均衡实现原理01

  4. unity中实现监听鼠标的进入和退出某一个UI按钮

    using UnityEngine; using System.Collections; using Assets.Code.myclass; using UnityEngine.UI; using ...

  5. Vue 学习Day001

    入门 基本使用 安装Vue 直接引入本地或者cdn Vue地址 使用npm 使用cli 示例 <!DOCTYPE html> <html lang="en"> ...

  6. selenium 常用操作

    官方文档: https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver. ...

  7. 获取进程ID,父进程ID,进程完整路径

    准备写一个进程管理的功能模块,今天下午先写了扫描获取本机各个进程路径,获取各个进程映像名称,进程完整路径. 要获取进程信息,第一步想到的就是提权,提权代码用过多次了,今天也小结了一下(http://w ...

  8. 一月分四周的JAVA实现方法

    需求:给定任意一个月,如何按照中国周的习惯,把一个月分成四个时间段 (1)以自然周为划分依据 (2)不能跨月 (3)把首尾自然周,天数较少的合并到其最近的自然周里面 (4)最后结果应该是吧一个月分成四 ...

  9. JavaScript入门经典(第四版)读书笔记

    第一部分 Web脚本编写与JavaScript语言的概念 1.document.lastModified()    ->    返回文档修改日期 2.<head>标签中的<sc ...

  10. [Scala] [Coursera]

    Week 1 Cheat Sheet Link Evaluation Rules Call by value: evaluates the function arguments before call ...