1 概述
Spring表達式語言全稱為“Spring Expression Language”,縮寫為“SpEL”。在運行時構建復雜表達式、存取對象圖屬性、對象方法調用等等,并且能與Spring功能完美整合,如能用來配置Bean定義。
SpEL是單獨模塊,只依賴于core模塊,不依賴于其他模塊,可以單獨使用。
2 使用場景
- Bean 的香瓜屬性的配置
- 結合 AOP 完成業(yè)務系統(tǒng)的日志記錄
3 功能概覽
SpEL支持如下表達式:
一、基本表達式: 字面量表達式、關系,邏輯與算數運算表達式、字符串連接及截取表達式、三目運算及Elivis表達式、正則表達式、括號優(yōu)先級表達式;
二、類相關表達式: 類類型表達式、類實例化、instanceof表達式、變量定義及引用、賦值表達式、自定義函數、對象屬性存取及安全導航表達式、對象方法調用、Bean引用;
三、集合相關表達式: 內聯(lián)List、內聯(lián)數組、集合,字典訪問、列表,字典,數組修改、集合投影、集合選擇;不支持多維內聯(lián)數組初始化;不支持內聯(lián)字典定義;
四、其他表達式:模板表達式。
注:SpEL表達式中的關鍵字是不區(qū)分大小寫的。
4 基本使用
4.1 使用步驟
1)創(chuàng)建解析器:SpEL使用ExpressionParser接口表示解析器,提供 SpelExpressionParser默認實現;
2)解析表達式:使用 ExpressionParser 的 parseExpression 來解析相應的表達式為Expression對象。
3)構造上下文:準備比如變量定義等等表達式需要的上下文數據。
4)求值:通過Expression接口的getValue方法根據上下文獲得表達式值。
示例:
public class SpelTest {
@Test
public void test1() {
// 創(chuàng)建解析器
ExpressionParser parser = new SpelExpressionParser();
// 解析表達式
Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");
// 構建上下文
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
// 獲取 表達式值
System.out.println(expression.getValue(context));
}
}
4.2 原理
一、表達式: 表達式是表達式語言的核心,所以表達式語言都是圍繞表達式進行的,從我們角度來看是“干什么”;
二、解析器: 用于將字符串表達式解析為表達式對象,從我們角度來看是“誰來干”;
三、上下文: 表達式對象執(zhí)行的環(huán)境,該環(huán)境可能定義變量、定義自定義函數、提供類型轉換等等,從我們角度看是“在哪干”;
四、根對象及活動上下文對象: 根對象是默認的活動上下文對象,活動上下文對象表示了當前表達式操作的對象,從我們角度看是“對誰干”
4.3 基本語法
4.3.1 字面量表達式
SpEL支持的字面量包括:字符串、數字類型(int、long、float、double)、布爾類型、null類型。

4.3.2 算數運算表達式

4.3.3 關系表達式
等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),區(qū)間(between)運算。
如parser.parseExpression("1>2").getValue(boolean.class);將返回false;
而parser.parseExpression("1 between {1, 2}").getValue(boolean.class);將返回true。
between運算符右邊操作數必須是列表類型,且只能包含2個元素。第一個元素為開始,第二個元素為結束,區(qū)間運算是包含邊界值的,即 xxx>=list.get(0) && xxx<=list.get(1)。
SpEL同樣提供了等價的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”來表示等于、不等于、大于、大于等于、小于、小于等于,不區(qū)分大小寫。
4.3.4 邏輯表達式
且(and或者&&)、或(or或者||)、非(!或NOT)。
public void test4() {
ExpressionParser parser = new SpelExpressionParser();
boolean result1 = parser.parseExpression("2>1 and (!true or !false)").getValue(boolean.class);
boolean result2 = parser.parseExpression("2>1 && (!true || !false)").getValue(boolean.class);
boolean result3 = parser.parseExpression("2>1 and (NOT true or NOT false)").getValue(boolean.class);
boolean result4 = parser.parseExpression("2>1 && (NOT true || NOT false)").getValue(boolean.class);
}
4.3.5 變量定義及引用
變量定義通過EvaluationContext接口的setVariable(variableName, value)方法定義;在表達式中使用"#variableName"引用;除了引用自定義變量,SpE還允許引用根對象及當前上下文對象,使用"#root"引用根對象,使用"#this"引用當前上下文對象;
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("name", "路人甲java");
context.setVariable("lesson", "Spring系列");
//獲取name變量,lesson變量
String name = parser.parseExpression("#name").getValue(context, String.class);
System.out.println(name);
String lesson = parser.parseExpression("#lesson").getValue(context, String.class);
System.out.println(lesson);
//StandardEvaluationContext構造器傳入root對象,可以通過#root來訪問root對象
context = new StandardEvaluationContext("我是root對象");
String rootObj = parser.parseExpression("#root").getValue(context, String.class);
System.out.println(rootObj);
//#this用來訪問當前上線文中的對象
String thisObj = parser.parseExpression("#this").getValue(context, String.class);
System.out.println(thisObj);
}
輸出:
路人甲java
Spring系列
我是root對象
我是root對象
4.3.6 對象屬性及安全導航表達式
對象屬性獲取非常簡單,即使用如“a.property.property”這種點綴式獲取,SpEL對于屬性名首字母是不區(qū)分大小寫的;SpEL還引入了Groovy語言中的安全導航運算符“(對象|屬性)?.屬性”,用來避免“?.”前邊的表達式為null時拋出空指針異常,而是返回null;修改對象屬性值則可以通過賦值表達式或Expression接口的setValue方法修改。
4.3.7 Bean 引用
SpEL支持使用“@”符號來引用Bean,在引用Bean時需要使用BeanResolver接口實現來查找Bean,Spring提供BeanFactoryResolver實現。
示例:
public void test6() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
User user = new User();
Car car = new Car();
car.setName("保時捷");
user.setCar(car);
factory.registerSingleton("user", user);
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(factory));
ExpressionParser parser = new SpelExpressionParser();
User userBean = parser.parseExpression("@user").getValue(context, User.class);
System.out.println(userBean);
System.out.println(userBean == factory.getBean("user"));
}
4.3.8 集合相關表達式
1. 內聯(lián)List
從Spring3.0.4開始支持內聯(lián)List,使用{表達式,……}定義內聯(lián)List,如“{1,2,3}”將返回一個整型的ArrayList,而“{}”將返回空的List,對于字面量表達式列表,SpEL會使用java.util.Collections.unmodifiableList方法將列表設置為不可修改。
public void test7() {
ExpressionParser parser = new SpelExpressionParser();
//將返回不可修改的空List
List<Integer> result2 = parser.parseExpression("{}").getValue(List.class);
//對于字面量列表也將返回不可修改的List
List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);
Assert.assertEquals(new Integer(1), result1.get(0));
try {
result1.set(0, 2);
} catch (Exception e) {
e.printStackTrace();
}
//對于列表中只要有一個不是字面量表達式,將只返回原始List,
//不會進行不可修改處理
String expression3 = "{{1+2,2+4},{3,4+4}}";
List<List<Integer>> result3 = parser.parseExpression(expression3).getValue(List.class);
result3.get(0).set(0, 1);
System.out.println(result3);
//聲明二維數組并初始化
int[] result4 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class);
System.out.println(result4[1]);
//定義一維數組并初始化
int[] result5 = parser.parseExpression("new int[1]").getValue(int[].class);
System.out.println(result5[0]);
}
輸出:
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
at com.javacode2018.spel.SpelTest.test7(SpelTest.java:315)
[[1, 6], [3, 8]]
2
0
2. 集合,字典元素訪問
SpEL目前支持所有集合類型和字典類型的元素訪問,使用“集合[索引]”訪問集合元素,使用“map[key]”訪問字典元素;
//SpEL內聯(lián)List訪問
int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
//SpEL目前支持所有集合類型的訪問
Collection<Integer> collection = new HashSet<Integer>();
collection.add(1);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("collection", collection);
int result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
//SpEL對Map字典元素訪問的支持
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable("map", map);
int result3 = parser.parseExpression("#map['a']").getValue(context3, int.class);
3. 列表、字典、數組元素修改
可以使用賦值表達式或Expression接口的setValue方法修改;
@Test
public void test8() {
ExpressionParser parser = new SpelExpressionParser();
//修改list元素值
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("collection", list);
parser.parseExpression("#collection[1]").setValue(context1, 4);
int result1 = parser.parseExpression("#collection[1]").getValue(context1, int.class);
System.out.println(result1);
//修改map元素值
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
parser.parseExpression("#map['a']").setValue(context2, 4);
Integer result2 = parser.parseExpression("#map['a']").getValue(context2, int.class);
System.out.println(result2);
}
輸出:
4
4
5 示例 -- 操作日志
1. controller
@TenantLog(module = "custer", description = "get ...", content = "parameter: id = #{ #customer?.id }, "
+ "accountId = #{ #customer?.accountId}, orderNumber = #{ #customer.orderNumber }, des = #{ #customer.description }")
@RequestMapping(value = "/create", method = RequestMethod.POST)
public CustomerTicket createCustomerTicket(@RequestBody CustomerTicket customer) {
log.info("customer is 888888 {}", customer.toString());
CustomerTicket customerTicket = new CustomerTicket();
customerTicket.setId(customer.getId());
customerTicket.setAccountId(customer.getAccountId());
customerTicket.setOrderNumber(customer.getOrderNumber());
customerTicket.setDescription(customer.getDescription());
customerTicket.setCreateTime(new Date());
return customerTicket;
}
@TenantLog(module = "custer", description = "get ...", content = "parameter: #{ #customer.toString() }")
@RequestMapping(value = "/create1", method = RequestMethod.POST)
public CustomerTicket createCustomerTicket1(@RequestBody CustomerTicket customer) {
log.info("customer is 888888 {}", customer.toString());
CustomerTicket customerTicket = new CustomerTicket();
customerTicket.setId(customer.getId());
customerTicket.setAccountId(customer.getAccountId());
customerTicket.setOrderNumber(customer.getOrderNumber());
customerTicket.setDescription(customer.getDescription());
customerTicket.setCreateTime(new Date());
return customerTicket;
}
2. AOP + SpEL
@Slf4j
@Aspect
@Component
public class TenantLogOperateAspect {
// 需要被SpEl解析的模板前綴和后綴 {{ expression }}
public static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("#{", "}");
// SpEL解析器
public static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
@Pointcut("@annotation(com.chenjunjie.webpro.aop.TenantLog)")
public void tenantLogPointcut() {
}
@Around("tenantLogPointcut()")
public Object addAspect(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 參數
Object[] args = joinPoint.getArgs();
// 參數名稱
String[] parameterNames = signature.getParameterNames();
// 目標方法
Method targetMethod = signature.getMethod();
TenantLog operationLog = targetMethod.getAnnotation(TenantLog.class);
// request
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
/**
* SpEL解析的上下文,把 HandlerMethod 的形參都添加到上下文中,并且使用參數名稱作為KEY
*/
EvaluationContext evaluationContext = new StandardEvaluationContext();
for (int i = 0; i < args.length; i ++) {
evaluationContext.setVariable(parameterNames[i], args[i]);
}
String logContent = EXPRESSION_PARSER.parseExpression(operationLog.content(), TEMPLATE_PARSER_CONTEXT).getValue(evaluationContext, String.class);
// TODO 異步存儲日志
System.out.println("**************************");
log.info("operationLog={}", logContent);
System.out.println("**************************");
// 執(zhí)行方法
Object proceed = joinPoint.proceed();
return proceed;
} catch (Exception e) {
log.error("操作日志SpEL表達式解析異常: {}", e.getMessage());
}
return null;
}
}