SpEL基本操作--Week12企业级开源框架课程内容

本课知识点:SpEL语句的书写 (考试内容直接看最后一部分)

一、SpEL语法

1.SpEL概念:

SpEL全称Spring Expression Language,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等。

2.SpEL的使用:

首先我们先简单说明四个基本概念:(在接下来会进行讲解)

  1. 用户表达式:我们定义的表达式,如1+1!=2
  2. 解析器:ExpressionParser 接口,负责将用户表达式解析成SpEL认识的表达式对象
  3. 表达式对象:Expression接口,SpEL的核心,表达式语言都是围绕表达式进行的
  4. 评估上下文: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);
与获取类的成员变量相似

参考文章:

Spring系列19:SpEL详解 - kongxubihai - 博客园 (cnblogs.com)

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

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值