目录

spring-SpEL表达式总结

[TOC]

SpEL(Spring Expression Language),即Spring表达式语言。它是一种类似JSP的EL表达式、但又比后者更为强大有用的表达式语言。

为什么要用SpEL:因为它可以在spring容器内实时查询和操作数据,尤其是操作List列表型、Array数组型数据。所以使用SpEL可以有效缩减代码量,优化代码结构,笔者认为很有用。

  1. #{…} 用于执行SpEl表达式,并将内容赋值给属性
  2. ${…} 主要用于加载外部属性文件中的值
  3. #{…} 和${…} 可以混合使用,但是必须#{}外面,${}在里面,#{ ‘${}’ } ,注意单引号,注意不能反过来

一、用法

常规SpEL有三种用法:

  • 在注解**@Value**中使用
  • XML配置中使用
  • 代码中创建Expression对象,利用Expression对象来执行SpEL

@Value注解

@Value可以加在class的成员变量和形参上。用法如下

1
2
3
4
5
//@Value能修饰成员变量和方法形参
//#{}内就是SpEL表达式的语法
//Spring会根据SpEL表达式语法,为变量arg赋值
@Value("#{表达式}")
public String arg;1234

如果修饰成员变量,Spring容器会根据SpEL表达式语法筛选修改数据,然后赋值给所@Value修饰的变量;

如果修饰方法形参,则是过滤传进来的参数值。

XML配置

XML配置用在Spring的applicationContext.xml配置文件内的<bean>元素上,用法如下:

1
2
3
4
<bean id="xxx" class="com.java.XXXXX.xx">
    <!-- @Value,#{}内是表达式的值可放在property或constructor-arg内 -->
    <property name="arg" value="#{表达式}">
</bean>

用法跟注解@Value修饰形参类似

Expression

代码中创建Expression对象,利用Expression对象来执行SpEL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
 
public class SpELTest {
 
    public static void main(String[] args) {
 
    	//创建ExpressionParser解析表达式
        ExpressionParser parser = new SpelExpressionParser();
        //SpEL表达式语法设置在parseExpression()入参内
        Expression exp = parser.parseExpression("表达式");
        //执行SpEL表达式,执行的默认Spring容器是Spring本身的容器:ApplicationContext
        Object value = exp.getValue();
        
 
        /**也可以使用非Spring的ApplicationContext容器,则用下面的方法*/
        //创建一个虚拟的容器EvaluationContext
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        //向容器内添加bean
        BeanA beanA = new BeanA();
        ctx.setVariable("bean_id", beanA);
        //setRootObject并非必须;一个EvaluationContext只能有一个RootObject,引用它的属性时,可以不加前缀
        ctx.setRootObject(XXX);        
        //getValue有参数ctx,从新的容器中根据SpEL表达式获取所需的值
        Object value = exp.getValue(ctx);
    }
}

Expression用法可以在代码中使用SpEL进行数据的读取过滤和修改,十分方便。

由上面可以看出,SpEL与Spring容器本身紧密相关,且用法特别灵活,可以直接操作Spring管理的各种bean、变量、properties配置文件等数据。

二、SpEL表达式语法

知道SpEL的使用场景范围和用法后,我们来看下SpEL表达式的具体语法。

SpEL语法决定了程序员能通过编写SpEL,从容器内取到什么样的数据,以及可以把数据处理成什么结果。有时用java代码写很多行的逻辑,用SpEL表达式一句话就可以搞定。

具体如下:

1.直接量表达式

1
2
@Value("#{'Hello World'}")        
String word;        //变量word赋值直接量:字符串"Hello World"

2.直接使用java代码

如在SpEL中直接试用new/instance of,像写Java代码一样。注意:在SpEL中直接使用某个类名时,此类必须是java.lang 包中的类,才可以在SpEL中省略包名;否则需要写全名

1
Expression exp = parser.parseExpression("new Spring('Hello World')"); 

3.使用T(Type)

使用“T(Type)”来表示java.lang.Class类的实例,即如同java代码中直接写类名。同样,只有java.lang 下的类才可以省略包名。此方法一般用来引用常量或静态方法

1
parser.parseExpression("T(Integer).MAX_VALUE");    //等同于java代码中的:Integer.MAX_VALUE

4.变量

获取容器内的变量,可以使用“#bean_id”来获取。有两个特殊的变量,可以直接使用。

  • #this 使用当前正在计算的上下文
  • #root 引用容器的root对象,即applicationContext本身
1
2
3
4
5
6
7
8
//从ctx容器内,获取rootObject,并转换为String类型
String result1 = parser.parseExpression("#root").getValue(ctx, String.class);  

//在ctx容器内,设置abc的值为"abcdef"
String s = new String("abcdef");
ctx.setVariable("abc",s);
//取id为abc的bean,然后调用其中的substring方法,得到结果赋值给result2
String result2 = parser.parseExpression("#abc.substring(0,1)").getValue(ctx, String.class);

5.方法调用

SpEL的方法调用与直接编写Java代码没有什么区别。具体可见上例abc.substring(0,1)与java代码”abcdef”.substring(0,1)效果一样

SpEL也可以自定义方法,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//创建ctx容器
StandardEvaluationContext ctx = new StandardEvaluationContext();
//获取java自带的Integer类的parseInt(String)方法
Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
//将parseInt方法注册在ctx容器内
ctx.registerFunction("parseInt", parseInt);
//再将parseInt方法设为parseInt2
ctx.setVariable("parseInt2", parseInt);

//创建ExpressionParser解析表达式
ExpressionParser parser = new SpelExpressionParser();
//SpEL语法,比对两个方法执行完成后,结果是否相同
String expreString = "#parseInt('2') == #parseInt2('3')";
Expression expression = parser.parseExpression(expreString);
return expression.getValue(ctx, Boolean.class);    //执行结果为false

/** 如果String expreString = "#parseInt('2') == #parseInt2('3')",执行结果为true */
/** 可见SpEL正常执行*/

“registerFunction”和“setVariable”都可以注册自定义函数,但是两个方法的含义不一样,推荐使用“registerFunction”方法注册自定义函数。

6.运算符表达式

  1. 算数表达式(“1+2-3*4/2″) +、-、*、/、%、^
  2. 比较表达式(“1>2”) <、>、==、<=、>=、lt、gt、rq、le、ge eq
  3. 逻辑表达式(“2>1 and (!true or !false)”) and、or、not、|
  4. 赋值表达式(“#variableName=value”)
  5. 三目表达式(“表达式1?表达式2:表达式3”)
  6. 正则表达式(“123′ matches ‘\d{3}”)

等运算符,都可以直接放在SpEL中,执行结果为运算符表达式的结果

1
2
3
4
5
6
7
#{2*T(java.lang.Math).PI * circle.radius}               //圆周长计算
#{T(java.lang.Math).PI * circle.radius^2}               //圆面积计算
#{disc.title + 'by' + disc.artist}                      // + 是连接符
#{counter.total == 100}  #{counter.total eq 100}        //判断是否一致,返回true和false
#{counter.total > 100 ? "Winner" : "Loser"}             //三元表达式 
#{disc.title ?: 'Rattle'}                   //Elvis,如果是null的话结果则为Rattle
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9._-]+\\.com'}  //正则表达式

7.Elvis运算符

是三目运算符的特殊写法,可以避免null报错的情况

1
2
3
4
5
//SpEL可简写为:
name?:"other"

//等同于java代码
name != null? name : "other"

8.安全保证

为了避免操作对象本身可能为null,取属性时报错,可以使用SpEL检验语法

语法: “对象?.变量|方法

1
2
3
4
5
 //SpEL表达式简写
list?.length

//等同于java代码
list == null? null: list.length

当对象为null时,直接返回“null”,不会抛出NullPointerException

9.集合定义

使用**“{表达式,……}”**定义List,如“{1,2,3}”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//SpEL的@Value注解设置List
@Value("1,2,3")
private List<Integer> f1;

@RequestMapping(value = "/a", method = RequestMethod.POST)
public List<Integer> a() throws NoSuchMethodException {

    //SpEL
    List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);

    //等同于如下java代码
    Integer[] integer = new Integer[]{1,2,3};
    List<Integer> result2 = Arrays.asList(integer);

    return result1;
}

对于字面量表达式列表,SpEL会使用java.util.Collections.unmodifiableList 方法将列表设置为不可修改。

10.集合访问

SpEL目前支持所有集合类型和字典类型的元素访问

语法:“集合[索引]”、“map[key]”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
EvaluationContext context = new StandardEvaluationContext();
 
//即list.get(0)
int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class); 
 
//list获取某一项
Collection<Integer> collection = new HashSet<Integer>();
collection.add(1);
collection.add(2);
 
context.setVariable("collection", collection);
int result2 = parser.parseExpression("#collection[1]").getValue(context, int.class); 
 
//map获取
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
 
context.setVariable("map", map);
int result3 = parser.parseExpression("#map['a']").getValue(context, int.class);

11.集合修改

可以使用赋值表达式或Expression接口的setValue方法修改;

1
2
3
4
5
//赋值语句
int result = parser.parseExpression("#array[1] = 3").getValue(context, int.class); 
 
//serValue方法
parser.parseExpression("#array[2]").setValue(context, 4);

12.集合选择

通过一定的规则对及格进行筛选,构造出另一个集合

语法:“(list|map).?[选择表达式]”

选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。

1
parser.parseExpression("#collection.?[#this>2]").getValue(context, Collection.class); 

上面的例子从数字的collection集合中选出数字大于2的值,重新组装成了一个新的集合

13.集合投影

根据集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素

语法:“SpEL使用“(list|map).![投影表达式]”

1
2
3
4
5
6
7
8
public class Book {
 
	public String name;         //书名
	public String author;       //作者
	public String publisher;    //出版社
	public double price;        //售价
	public boolean favorite;    //是否喜欢
}
1
2
3
4
5
6
7
public class BookList {
 
    @Autowired
    protected ArrayList<Book> list = new ArrayList<Book>() ;
	
    protected int num = 0;
}

将BookList的实例映射为bean:readList,在另一个bean中注入时,进行投影

1
2
3
//从readList的list下筛选出favorite为true的子集合,再将他们的name字段投为新的list
	@Value("#{list.?[favorite eq true].![name]}")
	private ArrayList<String> favoriteBookName;

三、案例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/**
 * ymm56.com Inc.
 * Copyright (c) 2013-2021 All Rights Reserved.
 */
package com.eh.frog.sample.controller;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author f90fd4n david
 * @version 1.0.0: SpELController.java, v 0.1 2021-11-15 3:12 下午 david Exp $$
 */
@Slf4j
@RestController
@RequestMapping("/SpEL")
public class SpELController {

	@Value("#{'Hello World1'}")
	String helloWorld1;        //变量word赋值直接量:字符串"Hello World"

	@Value("Hello World2")
	String helloWorld2;        //变量word赋值直接量:字符串"Hello World"

	//注入list
	@Value("#{{7,2,3,5,1}}")
	private List<Integer> fList;


	/**
	 * {@code @Value} 注入String
	 *
	 * @return
	 */
	@RequestMapping(value = "/valueAnnoString", method = RequestMethod.GET)
	public String valueAnnoString() {
		return helloWorld1 + " & " + helloWorld2;
	}


	/**
	 * {@code @Value} 注入List
	 *
	 * @return
	 * @throws NoSuchMethodException
	 */
	@RequestMapping(value = "/valueAnnoList", method = RequestMethod.GET)
	public List<Integer> valueAnnoList() {
		return fList;
	}


	/**
	 * 测试通过ExpressionParser调用SpEL表达式
	 * @return
	 */
	@RequestMapping(value = "/expressionParse", method = RequestMethod.GET)
	public List<Integer> expressionParse() {

		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();
		List<Integer> result1 = parser.parseExpression("{4,5,5,6}").getValue(List.class);
		return result1;
	}



	/**
	 * 使用java代码
	 * @return
	 */
	@RequestMapping(value = "/javaCode", method = RequestMethod.GET)
	public Integer javaCode() {

		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();

		//等同于直接用java代码,还有方法调用
		String str = parser.parseExpression("new String('Hello World').substring(3)").getValue(String.class);
		log.info("str=={}", str);

		//TType 等同于java的Integer.MAX_VALUE
		Integer integer = parser.parseExpression("T(Integer).MAX_VALUE").getValue(Integer.class);
		log.info("integer=={}", integer);
		return integer;
	}




	/**
	 * 注入并调用method方法
	 * @return
	 * @throws NoSuchMethodException
	 */
	@RequestMapping("methodInvoke")
	private boolean methodInvoke() throws NoSuchMethodException {
		//创建ctx容器
		StandardEvaluationContext ctx = new StandardEvaluationContext();
		//获取java自带的Integer类的parseInt(String)方法
		Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
		//将parseInt方法注册在ctx容器内, 推荐这样使用
		ctx.registerFunction("parseInt", parseInt);
		//再将parseInt方法设为parseInt2
		ctx.setVariable("parseInt2", parseInt);

		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();
		//SpEL语法,比对两个方法执行完成后,结果是否相同
		String expreString = "#parseInt('2') == #parseInt2('3')";
		//执行SpEL
		Expression expression = parser.parseExpression(expreString);
		Boolean value = expression.getValue(ctx, Boolean.class);
		return value;
	}



	/**
	 * 运算符
	 * @return
	 */
	@RequestMapping(value = "/operator", method = RequestMethod.GET)
	public boolean operator() {

		//创建ctx容器
		StandardEvaluationContext ctx = new StandardEvaluationContext();
		//将字符串defg放在 ctx容器内
		ctx.setVariable("abc", new String("defg"));
		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();
		String abc = parser.parseExpression("#abc").getValue(ctx, String.class);
		log.info("abc=={}", abc);

		//运算符判断
		Boolean result = parser.parseExpression("#abc.length() > 3").getValue(ctx, Boolean.class);
		log.info("result=={}", result);
		return result;
	}


	/**
	 * Elvis等用法
	 * @return
	 */
	@RequestMapping(value = "/elvis", method = RequestMethod.GET)
	public void elvis(){
		//创建ctx容器
		StandardEvaluationContext ctx = new StandardEvaluationContext();
		//将字符串defg放在 ctx容器内
		ctx.setVariable("name", null);
		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();
		String name = parser.parseExpression("#name?:'other'").getValue(ctx, String.class);
		log.info("name=={}",name);
		log.info("saved length() == {}", parser.parseExpression("#name?.lenth()").getValue(ctx));

		//将字符串defg放在 ctx容器内
		ctx.setVariable("name", "abc");
		name = parser.parseExpression("#name?:'other'").getValue(ctx, String.class);
		log.info("changed name=={}",name);
		log.info("changed saved length() == {}", parser.parseExpression("#name?.length()").getValue(ctx));


		//map获取
		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("a", 1);
		ctx.setVariable("map", map);
		int mapA = parser.parseExpression("#map['a']").getValue(ctx, int.class);
		log.info("map['a']=={}", mapA);
		//修改
		parser.parseExpression("#map['a']").setValue(ctx, 6);
		mapA = parser.parseExpression("#map['a']").getValue(ctx, int.class);
		log.info("changed map['a']=={}", mapA);

		return ;
	}


	@RequestMapping("/listFunction")
	private void listFunction() {
		//创建ctx容器
		StandardEvaluationContext ctx = new StandardEvaluationContext();
		//创建ExpressionParser解析表达式
		ExpressionParser parser = new SpelExpressionParser();

		//list过滤
		ctx.setVariable("aList",fList);
		List<Integer> cList = parser.parseExpression("#aList.?[#this>3]").getValue(ctx, List.class);
		log.info("filter list=={}", cList);


		List<Book> books = new ArrayList<>();
		books.add(new Book("JAVA Program", 2000, 102.5));
		books.add(new Book("C Program", 1985, 36));
		books.add(new Book("scala", 2015, 60));

		//object过滤
		ctx.setVariable("books", books);
		List<Book> filterBooks1 = (List<Book>) parser.parseExpression("#books.?[year>2000]").getValue(ctx);
		log.info("filterBooks1=={}", filterBooks1);

		//投影
		List<String> filterBooksName = parser.parseExpression("#books.?[price<100].![name]").getValue(ctx, List.class);
		log.info("filterBooksName=={}", filterBooksName);

		// .name相当于getName()方法
		String ssss = parser.parseExpression("#books[0].name").getValue(ctx, String.class);
		System.out.println(ssss);

		return;
	}



	@Data
	class Book{
		private String name;
		private int year;
		private double price;

		public Book(String name, int year, double price) {
			this.name = name;
			this.year = year;
			this.price = price;
		}
	}

}

参考

http://www.enmalvi.com/2020/07/23/spring-spel/#i-2