SpEL(Spring Expression Language):Spring表达式语言。
SpEL能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。
依赖配置
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-expressionartifactId>
<version>5.1.9.RELEASEversion>
dependency>
SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用。
常见的三种用法:
@Configuration("testConfig11")
public class TestConfig {
@Bean(name = "user11")
public User user11() {
User user = new User();
user.setId("123");
return user;
}
}
@RestController
public class TestController {
@Value("#{user11.id}")
private String userId;
}
将Spring容器中name为user11的bean的id属性值赋值给userId。
<bean id="testController " class="com.joker.controller.TestController ">
<property name="userId" value="#{user11.id}">
</bean>
和@Value类似,将Spring容器中name为user11的bean的id属性值赋值给userId。
@GetMapping("test4")
public String test4() {
// 创建ExpressionParser解析表达式
ExpressionParser parser = new SpelExpressionParser();
// 得到SpelExpression
// 使用ctx.setVariable时,需要前缀
SpelExpression exp = (SpelExpression)parser.parseExpression("#user.id");
// 使用ctx.setRootObject时,不需要#符号和前缀
// SpelExpression exp = (SpelExpression)parser.parseExpression("id");
// 创建一个虚拟的容器EvaluationContext
StandardEvaluationContext ctx = new StandardEvaluationContext();
// 向容器内添加bean
User user = new User();
user.setId("123");
ctx.setVariable("user", user);
// ctx.setRootObject(user);
// 得到user对象的id属性值
String value = (String)exp.getValue(ctx);
return value;
}
吃段代码主要为了得到user对象的id属性值。这样的写法看着有点废哈,确实是的,但这只是个用法举例哈。
实际上这种在方式在Spring用的挺广泛的,比如Spring Security的权限校验注解(PreAuthorize、PostAuthorize、PreFilter、PostFilter)都用到了SpEL。
用法举例:
当你想对接口做防止重复提交的拦截,你一般会考虑加锁,锁的key会根据请求中的一些特殊参数来生成。如果直接在每个接口里面写这个生成key和加锁的逻辑,显然很臃肿。
你可以这么做:
表达式解析器:负责解析表达式字符串,将字符串表达式解析为表达式对象。
接口的方法:
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
接口的实现:
表达式:提供getValue方法用于获取表达式值,提供setValue方法用于设置对象值。
接口的方法:
String getExpressionString();
Object getValue() throws EvaluationException;
<T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException;
Object getValue(@Nullable Object rootObject) throws EvaluationException;
<T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException;
Object getValue(EvaluationContext context) throws EvaluationException;
Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
<T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException;
<T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException;
Class<?> getValueType() throws EvaluationException;
Class<?> getValueType(@Nullable Object rootObject) throws EvaluationException;
Class<?> getValueType(EvaluationContext context) throws EvaluationException;
Class<?> getValueType(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException;
TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
boolean isWritable(@Nullable Object rootObject) throws EvaluationException;
boolean isWritable(EvaluationContext context) throws EvaluationException;
boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException;
void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException;
void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value) throws EvaluationException;
主要方法
接口的实现:
上下文环境:使用setRootObject方法来设置根对象,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等。
接口的方法:
TypedValue getRootObject();
List<PropertyAccessor> getPropertyAccessors();
List<ConstructorResolver> getConstructorResolvers();
List<MethodResolver> getMethodResolvers();
BeanResolver getBeanResolver();
TypeLocator getTypeLocator();
TypeConverter getTypeConverter();
TypeComparator getTypeComparator();
OperatorOverloader getOperatorOverloader();
void setVariable(String name, @Nullable Object value);
Object lookupVariable(String name);
主要方法:
接口的实现:
基本表达式:
类相关表达式:
集合相关表达式:
其他表达式:
需要注意:SpEL表达式中的关键字是不区分大小写的。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 字符串
String str1 = parser.parseExpression("'test'").getValue(String.class);
String str2 = parser.parseExpression("\"test\"").getValue(String.class);
// 数字类型
int int1 = parser.parseExpression("1").getValue(Integer.class);
long long1 = parser.parseExpression("1L").getValue(long.class);
float float1 = parser.parseExpression("1.1").getValue(Float.class);
double double1 = parser.parseExpression("1.1E+1").getValue(double.class);
int hex1 = parser.parseExpression("0xf").getValue(Integer.class);
// 布尔类型
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
// null类型
Object null1 = parser.parseExpression("null").getValue(Object.class);
}
SpEL支持:加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)等算数运算。
并且支持用英文替代符号,如:MOD等价%、DIV等价/,且不区分大小写。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
int int1 = parser.parseExpression("3+2").getValue(Integer.class);// 5
int int2 = parser.parseExpression("3*2").getValue(Integer.class);// 6
int int3 = parser.parseExpression("3%2").getValue(Integer.class);// 1
int int4 = parser.parseExpression("3^2").getValue(Integer.class);// 9
}
SpEL支持:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)等关系运算。
“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”来表示等于、不等于、大于、大于等于、小于、小于等于
并且支持用英文替代符号,如:EQ等价==、NE等价!=、GT等价>、GE等价>=、LT等价<、LE等价<=,且不区分大小写。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
boolean b1 = parser.parseExpression("3==2").getValue(Boolean.class);// false
boolean b2 = parser.parseExpression("3>=2").getValue(Boolean.class);// true
boolean b3 = parser.parseExpression("2 between {2, 3}").getValue(Boolean.class);// true
}
SpEL支持:或(or)、且(and)、非(!或NOT)。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
boolean b1 = parser.parseExpression("2>1 and false").getValue(Boolean.class);// false
boolean b2 = parser.parseExpression("2>1 or false").getValue(Boolean.class);// true
boolean b3 = parser.parseExpression("NOT false and (2>1 and 3>1)").getValue(Boolean.class);// true
}
SpEL支持字符串拼接和字符串截取(目前只支持截取一个字符)。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
String str1 = parser.parseExpression("'hello' + ' java'").getValue(String.class);// hello java
String str2 = parser.parseExpression("'hello java'[0]").getValue(String.class);// h
String str3 = parser.parseExpression("'hello java'[1]").getValue(String.class);// e
}
SpEL支持三目运算。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Boolean b1 = parser.parseExpression("3 > 2 ? true : false").getValue(Boolean.class);// true
System.out.println(b1);
}
SpEL支持Elivis表达式。
Elivis运算符
表达式格式:表达式1?:表达式2
Elivis运算符是从Groovy语言引入用于简化三目运算符(表达式1? 表达式1:表达式2)的。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
String str1 = parser.parseExpression("'a' ?: 'b'").getValue(String.class);// a
String str2 = parser.parseExpression("null ?: 'b'").getValue(String.class);// b
Boolean b1 = parser.parseExpression("3 > 2 ?: false").getValue(Boolean.class);// true
Boolean b2 = parser.parseExpression("null ?: false").getValue(Boolean.class);// false
Boolean b3 = parser.parseExpression("false ?: true").getValue(Boolean.class);// false
}
SpEL支持正则表达式
具体使用格式:str matches regex,其中str表示需要校验的字符串,regex表示正则表达式。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Boolean b1 = parser.parseExpression("'123' matches '\\d{3}'").getValue(Boolean.class);// true
Boolean b2 = parser.parseExpression("'123' matches '\\d{2}'").getValue(Boolean.class);// false
}
SpEL支持使用T(Type)来表示java.lang.Class实例,Type必须是类全限定名,java.lang包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// java.lang包
Class class1 = parser.parseExpression("T(String)").getValue(Class.class);
// 其他包
Class class2 = parser.parseExpression("T(com.joker.pojo.User)").getValue(Class.class);
// 类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
// 类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('2')").getValue(int.class);
}
SpEL支持类实例化,使用java关键字new,类名必须是全限定名,但java.lang包内的类型除外,如String、Integer。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// java.lang包
String str = parser.parseExpression("new String('str')").getValue(String.class);
// 其他包
Date date = parser.parseExpression("new java.util.Date()").getValue(Date.class);
}
SpEL支持instanceof运算符,跟Java内使用方法相同。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Boolean b1 = parser.parseExpression("'haha' instanceof T(String)").getValue(Boolean.class);// true
Boolean b2 = parser.parseExpression("123 instanceof T(String)").getValue(Boolean.class);// false
Boolean b3 = parser.parseExpression("123 instanceof T(java.util.Date)").getValue(Boolean.class);// false
}
SpEL支持:
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// #variableName
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("name1", "value1");
String str1 = parser.parseExpression("#name1").getValue(context, String.class);// value1
User user = new User();
user.setId("1");
context = new StandardEvaluationContext(user);
// #root(#root可以省略)
String str2 = parser.parseExpression("#root.id").getValue(context, String.class);// 1
String str3 = parser.parseExpression("id").getValue(context, String.class);// 1
// #this
String str4 = parser.parseExpression("#this.id").getValue(user, String.class);// 1
}
SpEL支持给自定义变量赋值,也允许给根对象赋值,直接使用#variableName=value即可赋值。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// #variableName
EvaluationContext context = new StandardEvaluationContext();
User user = new User();
user.setId("1");
context.setVariable("name1", user);
String str1 = parser.parseExpression("#name1.id='2'").getValue(context, String.class);// 2
context = new StandardEvaluationContext(user);
// #root(#root可以省略)
String str2 = parser.parseExpression("#root.id='3'").getValue(context, String.class);// 3
String str3 = parser.parseExpression("id='4'").getValue(context, String.class);// 4
// #this
String str4 = parser.parseExpression("#this.id='5'").getValue(user, String.class);// 5
}
SpEL支持类静态方法注册为自定义函数。SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数(等同于使用setVariable)。
public static void main(String[] args) throws NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Method parseInt = Integer.class.getDeclaredMethod("valueOf", String.class);
context.registerFunction("value", parseInt);
// 等同于Integer.valueOf("2").byteValue()
Byte b = parser.parseExpression("#value('2').byteValue()").getValue(context, Byte.class);// 2
}
SpEL支持:
public static void main(String[] args) throws NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
user.setId("1");
StandardEvaluationContext context = new StandardEvaluationContext(user);
// 对象属性获取
String str1 = parser.parseExpression("id").getValue(context, String.class);// 1
String str2 = parser.parseExpression("Id").getValue(context, String.class);// 1
// 安全导航
User user1 = parser.parseExpression("#root?.#root").getValue(context, User.class);// {"id":"1"}
String str3 = parser.parseExpression("id?.#root.id").getValue(context, String.class);// 1
String str4 = parser.parseExpression("userName?.#root.userName").getValue(context, String.class);// null
}
注意:
SpEL支持对象方法调用,使用方法跟Java语法一样。对于根对象可以直接调用方法。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext("user");
// 对象方法调用
String str1 = parser.parseExpression("#root.substring(1, 2)").getValue(context, String.class);// s
String str2 = parser.parseExpression("substring(1, 2)").getValue(context, String.class);// 1
}
SpEL支持使用@符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean,Spring提供BeanFactoryResolver实现。
@Autowired
private ApplicationContext applicationContext;
@GetMapping("test1")
public ApiResult test1() {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
// 获取Spring容器中beanName为systemProperties的bean
Properties systemProperties = parser.parseExpression("@systemProperties").getValue(context, Properties.class);
System.out.println(JSON.toJSONString(systemProperties));
// RedisProperties redisProperties = parser.parseExpression("@spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties").getValue(context, RedisProperties.class);
// System.out.println(JSON.toJSONString(redisProperties));
return ApiUtil.success();
}
注意:特殊的bean名称直接使用@beanName会报错。
比如RedisProperties,它的beanName为spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties,会误解析为:
这种具体怎么处理暂未研究。
注意:SpEL不支持内联字典(Map)定义。
SpEL支持内联数组(Array)定义。但不支持多维内联数组初始化。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 定义一维数组并初始化
int[] array1 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class);
System.out.println(JSON.toJSONString(array1));
// 定义二维数组但不初始化
String[][] array2 = parser.parseExpression("new String[2][2]").getValue(String[][].class);
System.out.println(JSON.toJSONString(array2));
// 定义二维数组并初始化(会报错)
String[][] array3 = parser.parseExpression("new String[2][2]{{'1','2'},{'3','4'}}").getValue(String[][].class);
System.out.println(JSON.toJSONString(array3));
}
SpEL支持内联集合(List)定义。使用{表达式,……}定义内联List,如{1,2,3}将返回一个整型的ArrayList,而{}将返回空的List。
对于字面量表达式列表,SpEL会使用java.util.Collections.unmodifiableList方法将列表设置为不可修改。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 将返回不可修改的空List
List<Integer> list1= parser.parseExpression("{}").getValue(List.class);
// 对于字面量列表也将返回不可修改的List
List<Integer> list2 = parser.parseExpression("{1,2,3}").getValue(List.class);
//对于列表中只要有一个不是字面量表达式,将只返回原始List(可修改)
String expression3 = "{{1+2,2+4},{3,4+4}}";
List<List<Integer>> list3 = parser.parseExpression(expression3).getValue(List.class);
result3.get(0).set(0, 1);
}
SpEL支持集合、字典元素访问。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 数组元素访问
int[] array1 = {1,2,3};
Integer int1 = parser.parseExpression("[0]").getValue(array1, int.class);
// 集合元素访问
Integer int2 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
List<Integer> list1 = Stream.of(1, 2, 3).collect(Collectors.toList());
Integer int3 = parser.parseExpression("[0]").getValue(list1, Integer.class);
// 字典元素访问
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Integer int4 = parser.parseExpression("['b']").getValue(map, Integer.class);
}
SpEL支持数组、集合、字典元素修改。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 数组元素修改
int[] array = new int[] {1, 2, 3};
parser.parseExpression("[0] = 3").getValue(array, int.class);
// 集合元素修改
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
parser.parseExpression("[0] = 3").getValue(list, Integer.class);
// 字典元素修改
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
parser.parseExpression("['b'] = 3").getValue(map, Integer.class);
}
SpEL支持数组、集合、字典投影。SpEL根据原集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素。数组和集合类似,字典构造后是集合(不是字典)。
SpEL使用list|map.![投影表达式]来进行投影运算。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 数组投影(#this可省略)
int[] array = new int[] {1, 2, 3};
int[] array1 = parser.parseExpression("#root.![#this+1]").getValue(array, int[].class);
// 集合投影(#this可省略)
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
List list1 = parser.parseExpression("#root.![#this+1]").getValue(list, List.class);
// 字典投影(#this可省略)
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
List list2 = parser.parseExpression("#root.![#this.key+1]").getValue(map, List.class);
List list3 = parser.parseExpression("#root.![#this.value+1]").getValue(map, List.class);
}
SpEL支持数组、集合、字典选择。SpEL根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合。数组和字典类似。
SpEL使用“(list|map).?[选择表达式]”,其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 数组选择
int[] array = new int[] {1, 2, 3};
int[] array1 = parser.parseExpression("#root.?[#this>1]").getValue(array, int[].class);
// 集合选择
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
List list1 = parser.parseExpression("#root.?[#this>1]").getValue(list, List.class);
// 字典选择
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Map map1 = parser.parseExpression("#root.?[#this.key!='a']").getValue(map, Map.class);
Map map2 = parser.parseExpression("#root.?[#this.value>1]").getValue(map, Map.class);
}
SpEL支持模板表达式。模板表达式由字面量与一个或多个表达式块组成。
表达式块由:"前缀+表达式+后缀"形式组成。
SpEL使用ParserContext接口实现来定义表达式是否是模板及前缀和后缀定义。
常见的前缀后缀:#{}、${}
模板表达式举例:
SpEL模板表达式
MyBatis中的占位符,以预编译的方式传入参数,可以有效的防止SQL注入。
向占位符输入参数,MyBatis会自动进行Java类型jdbc类型的转换,且不需要考虑参数的类型,例如:传入字符串,MyBatis最终拼接好的SQL就是参数两边加单引号。
SpEL模板表达式
作用于@Value等注解的属性,用于获取配置文件的配置值
MyBatis的SQL拼接,将参数的内容字符串替换方式拼接在SQL中,可能存在SQL注入的安全隐患。
在用于传入数据库对象,例如传入表名和列名,还有排序时使用order by动态参数时可以使用 ${ } 。