本课知识点:SpEL语句的书写 (考试内容直接看最后一部分)
一、SpEL语法
1.SpEL概念:
SpEL全称Spring Expression Language,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等。
2.SpEL的使用:
首先我们先简单说明四个基本概念:(在接下来会进行讲解)
- 用户表达式:我们定义的表达式,如
1+1!=2
- 解析器:ExpressionParser 接口,负责将用户表达式解析成SpEL认识的表达式对象
- 表达式对象:Expression接口,SpEL的核心,表达式语言都是围绕表达式进行的
- 评估上下文:EvaluationContext 接口,表示当前表达式对象操作的对象,表达式的评估计算是在上下文上进行的。
(1)最基本的字符串输出
//表达式解析器
ExpressionParser parser = new SpelExpressionParser();
//设置表达式
Expression exp = parser.parseExpression("'hello world'");
String str = (String) exp.getValue();
//第二种获取值的方法
//String str = exp.getValue(String.class);
System.out.println(str);
注意:
在pareseExpression()方法中,各参数均由String类型进行传递,即你的表达式外要加" "(双引号)。
在这个例子中,需要注意的是String类型的变量在传递的时候,还要在字符串两边添加' '(单引号),否则解析器的parseExpression()方法将无法识别。
接下来通过Expression对象的getValue()方法获取表达式的值,可以通过强制类型转化或者设置参数来指定表达式结果的返回类型。见源码中getValue方法的定义:
<T> T getValue(Class<T> desiredResultType) throws EvaluationException;
这是只有一个参数的getValue方法,其参数用来指定返回值类型,值为静态类,如String.class
接下来看其他类型的表达式:
(2)调用含有方法的字符串
ExpressionParser exp = parser.parseExpression("'hello world'.charAt(0)");
char ch = (Character) exp.getValue();
System.out.println(ch);
在pareseExpression方法中,""(双引号)中依旧是你传递的参数,但当前'hello world'这个字符串调用了charAt()方法,则" 'hello world'.charAt(0) "现在是一个Character类型的参数,你应当将返回的结果转化为Character类型,或者在getValue()方法中设置参数为Character.class。
下面两个表达式说明解析器(parseExpression())不仅仅是简单地获取并储存用户表达式:
(3)布尔表达式
Expression exp = parser.parseExpression("1+1 != 2");
Boolean b = exp.getValue(boolean.class);
System.out.println(b);
解析器对用户表达式(1+1 != 2)进行解析后,返回一个boolen类型的结果false。说明Expression对象不仅仅是简单储存用户表达式,而是对其进行了处理,后面会根据源码来分析。
(4)针对特定对象解析表达式
在课程文档中,我们有一个角色类Role,接下来我们将使用解析器获取这个类对象的信息。
//创建角色对象
Role role = new Role(1L, "role_name", "note1");
//设置表达式
ExpressionParser exp = parser.parseExpression("note");
//相当于从role中获取备注信息
String note = (String) exp.getValue(role);
System.out.println(note);
现在,解析器返回了role对象中的note属性值,输出为note1。
这说明,exp表达式操作getValue()中的对象role,通过这个方法指定性获取对象信息。
注意:
getValue()方法有重载, 此时传入的role参数不是指返回值类型。接口定义如下:
Object getValue(Object rootObject) throws EvaluationException;
3.SpEL执行原理(摘抄)
1.首先定义表达式:“1+2”;
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方法)获得结果。
4.parseExpression()和getValue()的实现原理
(1)parseExpression()源码分析:
包:package org.springframework.expression.spel.standard;
//用于储存分词流
private List<Token> tokenStream;
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
try {
//读取用户书写的表达式
this.expressionString = expressionString;
//定义分词器
Tokenizer tokenizer = new Tokenizer(expressionString);
//分词器将字符串拆分为分词流
tokenizer.process();
this.tokenStream = tokenizer.getTokens();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
//将分词流解析成抽象语法树
SpelNodeImpl ast = eatExpression();
if (moreTokens()) {
throw new SpelParseException(peekToken().startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty());
//将抽象语法树包装成Expression对象
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
throw ex.getCause();
}
}
(2)getValue()源码:
private final SpelNodeImpl ast;
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
//应用活动上下文和解析器的配置
ExpressionState expressionState = new ExpressionState(context, this.configuration);
//在上下中抽象语法树进行评估求值
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
//将结果进行类型转换
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
流程(摘抄):
(考试可以不看这一步)
二、设置环境变量(Context)
1.概念--EvaluationContext(评估上下文)接口:
表示上下文环境,默认实现是StandardEvaluationContext类,使用SetRootObject方法来设置根对象,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等。
2.实践1:定义变量并引用
//1.创建表达解析器
ExpressionParser parser = new SpelExpressionPareser();
//2.创建环境变量类
EvaluationContext ctx = new StandardEvaluationContext();
//3.创建一个(任意类型的)变量
List<String> list = new ArrayList<String>();
list.add("value1");
list.add("value2");
//4.给变量环境ctx增加变量"list",值为list--刚才定义的数组
ctx.setVariable("list", list);
//5.6.通过表达式去读写环境变量的值
parser.parseExpression("#list[1]").setValue(ctx, "update_value2");
System.out.println(parser.parseExpression("#list[1]").getValue(ctx));
(1)创建表达解析器,parser是解析表达式的基础。
(2)创建环境变量类对象ctx,将通过它给自定义的变量进行赋值。
(3)创建一个普通的变量List<String> list; 并添加两个值。
(4)使用setVariable()方法在ctx对象中定义一个变量(以键值对方式储存key--"list",value--list)。
(5)使用"#variableName"来引用EvaluationContext中定义的变量。在这个例子中,#variableName就是#list[1],一个List<String>中的元素。
(重点)parser.parserExpression("#list[1]")是一个Expression类型的表达式,它表示的含义是--变量list地址位[1]的值。这个表达式指定了ctx中的一个变量。
setValue中的参数为:ctx--环境变量,"update_value2"--String类型的值。
(如果赋值前打印parser.parserExpression("#list[1]").getValue(ctx),则会得到初始的值value2)
(6)使用getValue(),打印表达式指定的ctx中的信息。
3.setVariable()解析(源码):
private final Map<String, Object> variables = new HashMap<String, Object>();
public void setVariable(String name, Object value) {
//将name和value都装入成员变量variables
this.variables.put(name, value);
}
你可以在EvaluationContext对象中定义变量,它们被以String name -- Object value的形式储存到对象的成员变量variables中。
3.实践2:环境变量根节点、类对象的操作
//创建表达式解析器
ExpressionParser parser = new SpelExpressionParser();
//创建Role对象
Role role = new Role(1L, "role_name", "note1");
Expression exp = parser.parseExpression("note");
String note = (String) exp.getValue(role);
//变量环境类,并且将角色对象role作为其根节点
EvaluationContext ctx = new StandardEvaluationContext(role);
//变量环境类操作根节点
parser.parseExpression("note").setValue(ctx, "new_note");
//System.out.println(role.getNote());
//获取备注,这里的String.class指明,我们希望返回的是一个字符串
note = parser.parseExpression("note").getValue(ctx, String.class);
System.out.println(note);
//调用getRoleName方法
String roleName = parser.parseExpression("getRoleName()")
.getValue(ctx, String.class);
System.out.println(roleName);
(1)创建解析器和Role对象我们都进行过学习
(2)在实践1中,我们直接创建了ctx对象,而这次创建我们在StandardEvaluationContext的构造函数中传入Role对象,这个类对象作为根节点。
(3)我们知道,表达式parser.parseExpression("note")表示名为note的类的属性,还是使用setValue
对属性进行修改。值得注意的是,Java在传参的时候,传递的是地址,所以当你调用那行被注释的打印语句时,会发现role的note属性也被setValue()方法修改了(称为new_note)。
(4)在表达式中,可以直接使用方法名,调用类的方法,获取方法返回值:
String roleName = parser.parseExpression("getRoleName()").getValue(ctx, String.class);
三、总结(应付考试)
对于考试的操作,直接看这部分即可。
1.SpEL使用:
(1)创建表达式解析器:
ExpressionParse parser = new spelExpressionParser();
(2)设置表达式:
Expression exp = parser.parserExpression("xxx")
其中,xxx可以是字符串(要加'')、字符、方法、整数、表达式等。
(3)获取表达式的值
Type value = exp.getValue(Type.class);
其中Type是你需要的表达式返回值类型,getValue()中的参数指定这种类型。
(4)特殊情况:获取类对象中的属性值(成员变量)
Expression exp = parser.parserExpression("成员变量名");
Type value = exp.getValue(类对象);
2.配置上下文环境(Context)
(1)创建环境变量类:
EvaluationContext ctx = new StandardEvaluationContext();
ctx对象能够储存我们定义的变量。
(2)向ctx中添加我们定义的变量:
ctx.setVariable("name", value);
name是我们定义的变量名称,value是Object类型的值(可以是任意类型)
(3)使用解析表达式对ctx中的变量进行赋值
parser.parserExpression("#name").setValue(ctx, value);
其中,parser.parserExpression()我们学过,是一个解析表达式(Expression对象),它的值如果是以#开头,则说明它指定一个Context的变量名称。setValue()中的两个变量分别是ctx和你要对名为name的变量赋的值。
(4)创建ctx的根节点:
EvaluationContext ctx = new StandardEvaluationContext(变量/类对象);
在创建StandardEvaluationContext对象时,传入的参数就是它的根节点。
(5)修改根节点的属性值:
parser.parserExpression("#root").setValue(ctx, value);
可以在表达式中使用#root表示这个变量。(当然也可以用常规方式)
(6)使用表达式调用类中的方法:
Type value = parser.parseExpression("方法名()").getValue(ctx, Type.class);
与获取类的成员变量相似
参考文章: