上一章讲解了#{…}中可以写SpEL表达式,本章详细讲一讲如何使用。
什么是SpEL
即the Spring Expression Language的缩写,是一个强大的表达式语言,能支持在运行时查询和处理对象图。
它的语法跟jsp中的EL表达式类似,但提供了额外的特性,最显著的是方法调用(method invocation)和基本的字符串处理功能。
除了SpEL, 还有其他类似的框架,如:OGNL,MVEL,JBoss EL。 从SpEL名字可以看出,它创建出来致力于被所有spring产品使用,它的特性由spring生态需求驱动。且SpEL是技术无关的,如果需要也可以允许其他表达式语言框架集成进来。
SpEL完全不依赖Spring执行环境,可当作工具独立使用。
SpEL有哪些特性
- 文字表达式(Literal expressions)
- 布尔类型和相关操作(Boolean and relational operators)
- 正则表达式(Regular expressions)
- 类表达式(Class expressions)
- 访问属性,数组,列表和字典(Accessing properties, arrays, lists, and maps)
- 方法调用(Method invocation)
- 变量(Variables)
- … (etc)
如何使用
通常用在三个地方:
- 在注解@Value中使用
@Value("#{...}")
private String arg;
- 在XML中配置
<bean id="xxx" class="com.xx.xx.Xxx">
<property name="arg" value="#{...}"/>
</bean>
- 在代码中使用Expression
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("...");
Object value = exp.getValue();
具体语法详解
使用时在项目中添加如下依赖(一般会间接依赖进来):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
1. 字面量赋值
// 整数
@Value("#{5}")
private Integer arg;
// 浮点数
@Value("#{3.14}")
private Double arg;
// 科学计数法
@Value("#{1e4}")
private Long arg;
// 字符串
@Value("#{'我是字符串'}")
private String arg;
// 布尔值
@Value("#{true}")
private Boolean arg;
注:
- 字面量赋值必须要和对应属性的类型兼容,否则会报异常。
- 一般情况下不需要使用SpEL字面量赋值,因为多此一举,可以直接赋值。
2. 引用Bean, 属性和方法(必须是public修饰)
package win.elegentjs.spring.ioc.spel.bean;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
/**
* 演示SpEL引用Bean,属性和方法
*/
@Data
public class BeanSpEL {
// 注入bean
@Value("#{animal}")
private Animal animal;
// 注入bean的属性
@Value("#{animal.name}")
private String animalName;
// 通过bean的方法调用注入
@Value("#{animal.getAge()}")
private String animalAge;
}
// 测试结果
package win.elegentjs.spring.ioc.spel.bean;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 演示SpEL引用Bean,属性和方法
*/
@Slf4j
public class BeanSpELSample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
BeanSpEL beanSpEL = context.getBean(BeanSpEL.class);
log.info("==> beanSpEL: {}", beanSpEL);
}
}
// result:
2021-06-01 18:53:46.200 [main] INFO win.elegentjs.spring.ioc.spel.bean.BeanSpELSample-==> beanSpEL: BeanSpEL(animal=Animal(name=fox, age=2), animalName=fox, animalAge=2)
注:直接引用bean的用法不常见,因为使用@Autowired注入更直观。
3. 运算符
3.1 算术运算符: +,-,*,/,%,^
// 算术运算符:加法
@Value("#{10 + 1}")
private Integer num1;
// 算术运算符:减法
@Value("#{10 - 1}")
private Integer num2;
// 算术运算符:乘法
@Value("#{10 * 1}")
private Integer num3;
// 算术运算符:除法
@Value("#{10 / 1}")
private Integer num4;
// 算术运算符:取余
@Value("#{10 % 1}")
private Integer num5;
// 算术运算符:指数
@Value("#{10 ^ 3}")
private Integer num6;
3.2 字符串连接符: +
// 字符串拼接
@Value("#{21 + '年' + 6 + '月'}")
private String str1;
3.3 比较运算符:<, lt, >, gt, =, eq, <=, le, >=, gt
// 比较运算符 <
@Value("#{1 < 10}")
private Boolean oper1;
// 比较运算符 lt
@Value("#{1 lt 10}")
private Boolean oper2;
// 比较运算符 >
@Value("#{1 > 10}")
private Boolean oper3;
// 比较运算符 gt
@Value("#{1 gt 10}")
private Boolean oper4;
// 比较运算符 ==
@Value("#{1 == 10}")
private Boolean oper5;
// 比较运算符 eq
@Value("#{1 eq 10}")
private Boolean oper6;
// 比较运算符 <=
@Value("#{1 <= 10}")
private Boolean oper7;
// 比较运算符 le
@Value("#{1 le 10}")
private Boolean oper8;
// 比较运算符 >=
@Value("#{10 >= 1}")
private Boolean oper9;
// 比较运算符 ge
@Value("#{10 ge 1}")
private Boolean oper10;
3.4 逻辑运算符:and, or, not, &&, ||, !
// 逻辑运算符 and
@Value("#{true and false}")
private Boolean logic1;
// 逻辑运算符 &&
@Value("#{true && false}")
private Boolean logic2;
// 逻辑运算符 or
@Value("#{true or false}")
private Boolean logic3;
// 逻辑运算符 ||
@Value("#{true || false}")
private Boolean logic4;
// 逻辑运算符 not
@Value("#{not false}")
private Boolean logic5;
// 逻辑运算符 !
@Value("#{!false}")
private Boolean logic6;
3.5 三元运算符:? :
// 三元运算
@Value("#{(10 > 3) ? 'YES': 'FALSE'}")
private String condition1;
3.6 正则表达式: matches
@Value("#{'19898990988' matches '\\d+'}")
private Boolean regExp;
4. 调用静态方法或静态属性
@Value("#{T(Integer).MAX_VALUE}")
private Integer intVal;
@Value("#{T(Math).random()}")
private Double pi;
5. 方法调用
使用registerFunction和setVariable方法注册自定义函数。了解即可。
package win.elegentjs.spring.ioc.spel.method;
import lombok.extern.slf4j.Slf4j;
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 java.lang.reflect.Method;
/**
* 演示自定义方法调用示例,向ctx注册了方法parseInt和parseInt2
* 看看执行效果
*/
@Slf4j
public class MethodInvocationSample {
public static void main(String[] args) 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')";
Expression expression = parser.parseExpression(expreString);
Boolean result = expression.getValue(ctx, Boolean.class);
log.info("==> expreString: {}, result: {}", expreString, result);
expreString = "#parseInt('2') == #parseInt2('2')";
expression = parser.parseExpression(expreString);
result = expression.getValue(ctx, Boolean.class);
log.info("==> expreString: {}, result: {}", expreString, result);
}
}
// result:
2021-06-02 09:11:10.043 [main] INFO w.e.spring.ioc.spel.method.MethodInvocationSample-==> expreString: #parseInt('2') == #parseInt2('3'), result: false
2021-06-02 09:11:10.046 [main] INFO w.e.spring.ioc.spel.method.MethodInvocationSample-==> expreString: #parseInt('2') == #parseInt2('2'), result: true
6. Elvis运算符
是三元运算符的特殊写法,避免null报错的情况
name ?: "other"
等价于:name != null ? name : "other"
7. 安全保证
list?.length
等价于: list == null ? null : list.length
8. 直接使用java代码new/instance of
9. 其他
集合定义访问
package win.elegentjs.spring.ioc.spel.collection;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import java.util.List;
import java.util.Map;
@Data
public class Person {
@Value("#{{1,2,3}}")
private List<Integer> cards;
@Value("#{{1,2,3}[0]}")
private Integer first;
@Value("#{{'name': 'zhangsan', 'age': 20}}")
private Map<String, Object> dict;
@Value("#{{'name': 'zhangsan', 'age': 20}['name']}")
private String dictName;
}
执行结果:
package win.elegentjs.spring.ioc.spel.collection;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@Slf4j
public class PersonSample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
Person p = context.getBean(Person.class);
log.info("==> p: {}", p);
}
}
// result:
2021-06-02 09:38:41.933 [main] INFO w.e.spring.ioc.spel.collection.PersonSample-==> p: Person(cards=[1, 2, 3], first=1, dict={name=zhangsan, age=20}, dictName=zhangsan)