OGNL表达式

SPEL表达式

简介

Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,类似于Struts2x中使用的OGNL表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用。

表达式语言给静态Java语言增加了动态功能。

用法

SPEL有三种用法,一种在注解@Value中,一种在XML配置中,一种是带代码中使用表达式。

@Value

1
2
3
4
//@Value能修饰成员变量和方法形参
//#{}内就是表达式的内容
@Value("#{表达式}")
public String arg;

配置

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

Expression

Spel支持以下表达式:

基本表达式

字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算及Elivis表达式、正则表达式、括号优先级表达式

类相关表达式

类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用

集合相关表达式

内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义

其他表达式

模板表达式

注:SpEL表达式中的关键字是不区分大小写的

SpEl基础

首先编写个Demo

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
package com.javacode2018.spel;

import com.javacode2018.spel.test1.LessonModel;
import com.javacode2018.spel.test1.MainConfig;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.*;

public class SpelTest {
@Test
public void test1() {
ExpressionParser parser = new SpelExpressionParser();
#使用ExpressionParser接口创造解析器,提供SpelExpressionParser默认实现
Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");
#调用parsexpression解析相应的表达式为Expression对象
EvaluationContext context = new StandardEvaluationContext();
#准备变量定义需要的上下文数据
context.setVariable("end", "!");
#传值 System.out.println(expression.getValue(context));
#输出值
}

输出结果:

Spel原理及接口

  1. 表达式: “干什么”
  2. 解析器: “谁来干”
  3. 上下文: “在哪干”
  4. 根对象及活动上下文对象: “对谁干”

Spel工作流程图:

工作原理

  1. 定义表达式

  2. 定义解析器ExpressionParser实现,Spel默认实现spelExpressionParser

    2.1 SpelExpressionParser解析器内部使用Tokenizer类进行词法分析,即把字符串流分析为记号流,记号在SpEL使用Token类来表示;
    2.2 有了记号流后,解析器便可根据记号流生成内部抽象语法树;在SpEL中语法树节点由SpelNode接口实现代表:如OpPlus表示加操作节点、IntLiteral表示int型字面量节点;使用SpelNodel实现组成了抽象语法树;
    2.3 对外提供Expression接口来简化表示抽象语法树,从而隐藏内部实现细节,并提供getValue简单方法用于获取表达式值;SpEL提供默认实现为SpelExpression

  3. 定义表达式上下文对象。SpEL使用EvaluationContext接口表示上下文对象,用于设置根对象、自定义变量、自定义函数、类型转换器等,SpEL提供默认实现StandardEvaluationContext

  4. 使用表达式对象根据上下文对象求值(getvalue方法获取)

ExpressionParser接口

表示解析器,默认实现是org.springframework.expression.spel.standard包中的SpelExpressionParser类,使用parseExpression方法将字符串表达式转换为Expression对象,对于ParserContext接口用于定义字符串表达式是不是模板,及模板开始与结束字符:

1
2
3
4
public interface ExpressionParser {
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}

Demo

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
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}

@Override
public String getExpressionPrefix() {
return "#{";
}
#设置表达式前缀
@Override
public String getExpressionSuffix() {
return "}";
#设置表达式后缀
}
};
String template = "#{'Hello '}#{'World!'}";
#传入表达式模板
Expression expression = parser.parseExpression(template, parserContext);
System.out.println(expression.getValue());
}

该代码演示的是调用ParserContext接口的情况,通过重写接口中isTemplate(),getExpressionPrefix()设置前缀”#{“,getExprssionSuffix()设置后缀“}”,然后设置表达式以该模板进行格式化定义。

EvaluationContext接口

表示上下文环境,默认实现是org.springframework.expression.spel.support包中的StandardEvaluationContext类,使用setRootObject方法来设置根对象,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等等。

Expression接口

表示表达式对象,默认实现是org.springframework.expression.spel.standard包中的SpelExpression,提供getValue方法用于获取表达式值,提供setValue方法用于设置对象值。

SpEl语法

基本表达式

字面量表达式

SpEL支持的字面量包括:字符串、数字类型(int、long、float、double)、布尔类型、null类型。

通过getvalue(类.class)来表达类型

示例:

String str1 = parser.parserExpression(“‘Hello World!’”).getvalue(Sting.class)

Demo

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
@Test
public void test2() {
ExpressionParser parser = new SpelExpressionParser();

String str1 = parser.parseExpression("'Hello World!'").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+2").getValue(double.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);
Object null1 = parser.parseExpression("null").getValue(Object.class);

System.out.println("str1=" + str1);
System.out.println("int1=" + int1);
System.out.println("long1=" + long1);
System.out.println("float1=" + float1);
System.out.println("double1=" + double1);
System.out.println("hex1=" + hex1);
System.out.println("hex2=" + hex2);
System.out.println("true1=" + true1);
System.out.println("false1=" + false1);
System.out.println("null1=" + null1);
}

运行结果:

算数运算表达式

SpEL支持加减乘除、求余、幂运算

Demo

1
2
int test1 = parser.parseExpression("(1+2-2)*4/2%1^5").getValue(Integer.class);
System.out.println("result="+test1);

输出结果:

关系表达式

等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)运算,等价的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”来表示等于、不等于、大于、大于等于、小于、小于等于,不区分大小写。

Demo

1
2
3
4
5
6
7
8
@Test
public void test3() {
ExpressionParser parser = new SpelExpressionParser();
boolean v1 = parser.parseExpression("1>2").getValue(boolean.class);
boolean between1 = parser.parseExpression("1 between {1,2}").getValue(boolean.class);
System.out.println("v1=" + v1);
System.out.println("between1=" + between1);
}

输出结果:

between运算符右边操作数必须是列表类型,且只能包含2个元素。第一个元素为开始,第二个元素为结束,区间运算是包含边界值的,即 xxx>=list.get(0) && xxx<=list.get(1)

逻辑表达式

且(and或者&&)、或(or或者||)、非(!或NOT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
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);

System.out.println("result1=" + result1);
System.out.println("result2=" + result2);
System.out.println("result3=" + result3);
System.out.println("result4=" + result4);
}

输出结果:

字符串连接及截取表达式

使用“+”进行字符串连接,使用“’String’[0] [index]”来截取一个字符,目前只支持截取一个,如“’Hello ‘ + ‘World!’”得到“Hello World!”;而“’Hello World!’[0]”将返回“H”

三目运算

三目运算符 “表达式1?表达式2:表达式3”用于构造三目运算表达式,如“2>1?true:false”将返回true。

Elivis运算符

Elivis运算符“表达式1?:表达式2”从Groovy语言引入用于简化三目运算符的,当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2,简化了三目运算符方式“表达式1? 表达式1:表达式2”,如“null?:false”将返回false,而“true?:false”将返回true

正则表达式

使用“str matches regex,如“’123’ matches ‘\d{3}’”将返回true

括号优先级表达式

使用“(表达式)”构造,括号里的具有高优先级

类相关表达式

类类型表达式

使用“T(Type)”来表示java.lang.Class实例,“Type”必须是类全限定名,“java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testClassTypeExpression() {
ExpressionParser parser = new SpelExpressionParser();
//java.lang包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println(result1);

//其他包类访问
String expression2 = "T(com.javacode2018.spel.SpelTest)";
Class<SpelTest> value = parser.parseExpression(expression2).getValue(Class.class);
System.out.println(value == SpelTest.class);

//类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println(result3 == Integer.MAX_VALUE);

//类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println(result4);
}

输出结果:

对于java.lang包里的可以直接使用“T(String)”访问;其他包必须是类全限定名;可以进行静态字段访问如“T(Integer).MA

类实例化

类实例化同样使用java关键字“new”,类名必须是全限定名,但java.lang包内的类型除外,如String、Integer

1
2
3
4
5
6
7
8
9
@Test
public void testConstructorExpression() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("new String('路人甲java')").getValue(String.class);
System.out.println(result1);

Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
System.out.println(result2);
}

输出结果;

instanceof表达式

SpEL支持instanceof运算符,跟Java内使用同义;如“’haha’ instanceof T(String)”将返回true

1
2
3
4
5
6
@Test
public void testInstanceOfExpression() {
ExpressionParser parser = new SpelExpressionParser();
Boolean value = parser.parseExpression("'路人甲' instanceof T(String)").getValue(Boolean.class);
System.out.println(value);
}

输出结果:

变量定义及引用

变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用"#variableName"引用;除了引用自定义变量,SpE还允许引用根对象及当前上下文对象,使用"#root"引用根对象,使用"#this"引用当前上下文对象

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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);
}

输出结果:

使用“#variable”来引用在EvaluationContext定义的变量;除了可以引用自定义变量,还可以使用“#root”引用根对象,“#this”引用当前上下文对象,此处“#this”即根对象

自定义函数

目前只支持类静态方法注册为自定义函数;SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数,其实完全可以使用setVariable代替,两者其实本质是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testFunctionExpression() throws SecurityException, NoSuchMethodException {
//定义2个函数,registerFunction和setVariable都可以,不过从语义上面来看用registerFunction更恰当
StandardEvaluationContext context = new StandardEvaluationContext();
Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
context.registerFunction("parseInt1", parseInt);
context.setVariable("parseInt2", parseInt);

ExpressionParser parser = new SpelExpressionParser();
System.out.println(parser.parseExpression("#parseInt1('3')").getValue(context, int.class));
System.out.println(parser.parseExpression("#parseInt2('3')").getValue(context, int.class));

String expression1 = "#parseInt1('3') == #parseInt2('3')";
boolean result1 = parser.parseExpression(expression1).getValue(context, boolean.class);
System.out.println(result1);
}

输出结果:

表达式赋值

Demo

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
@Test
public void testAssignExpression1() {
Object user = new Object() {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "$classname{" +
"name='" + name + '\'' +
'}';
}
};
{
//user为root对象
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(user);
parser.parseExpression("#root.name").setValue(context, "路人甲java");
System.out.println(parser.parseExpression("#root").getValue(context, user.getClass()));
}
{
//user为变量
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);
parser.parseExpression("#user.name").setValue(context, "路人甲java");
System.out.println(parser.parseExpression("#user").getValue(context, user.getClass()));
}
}

输出结果:

对象属性存取及安全导航表达式

对象属性获取非常简单,即使用如“a.property.property”这种点缀式获取,SpEL对于属性名首字母是不区分大小写的;SpEL还引入了Groovy语言中的安全导航运算符“**(对象|属性)?.属性**”,用来避免“?.”前边的表达式为null时抛出空指针异常,而是返回null;修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改

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
public static class Car {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
'}';
}
}

public static class User {
private Car car;

public Car getCar() {
return car;
}

public void setCar(Car car) {
this.car = car;
}

@Override
public String toString() {
return "User{" +
"car=" + car +
'}';
}
}

@Test
public void test5() {
User user = new User();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);

ExpressionParser parser = new SpelExpressionParser();
//使用.符号,访问user.car.name会报错,原因:user.car为空
try {
System.out.println(parser.parseExpression("#user.car.name").getValue(context, String.class));
} catch (EvaluationException | ParseException e) {
System.out.println("出错了:" + e.getMessage());
}
//使用安全访问符号?.,可以规避null错误
System.out.println(parser.parseExpression("#user?.car?.name").getValue(context, String.class));

Car car = new Car();
car.setName("保时捷");
user.setCar(car);

System.out.println(parser.parseExpression("#user?.car?.toString()").getValue(context, String.class));
}

@Test
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"));
}

参考链接

玩转Spring中强大的spel表达式! - 知乎 (zhihu.com)

SpEL表达式总结 - 简书 (jianshu.com)

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信