概述
spring
版本6.0.12
xml方式
org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 中关于表达式解析的原理
相关链接
在阅读文章前,可以浏览一下
spring官方文档链接
案例中详细请移步spring系列文章一 此处
两个例子
通过两个例子,深入了解一下几个类
org.springframework.expression.Expression
org.springframework.expression.ExpressionParser及相关实现类
org.springframework.expression.spel.standard.Token
代码
测试时使用的代码
// pojo
@Component
public class TestBean {
// 用户名
private String name;
// 年龄
private int age;
// 按年龄分 青年,中年,老年
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void print() {
System.out.println("test bean method ...");
System.out.println(this);
System.out.println("spring源码环境构建完成");
}
@Override
public String toString() {
return "TestBean{" +
"name='" + name + '\'' +
", age=" + age +
", type='" + type + '\'' +
'}';
}
}
// 测试类
package org.springframework.test.my;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELTest {
public static void main(String[] args) {
//helloWorld();
strMethod();
}
/**
* 'Hello World'.concat('!')
* 解析成 Token集合
* 'Hello World'
* . DOT
* concat IDENTIFIER
* [LPAREN(()](20,21)
* [LITERAL_STRING:'!'](21,24)
* [RPAREN())](24,25)
*/
private static void strMethod() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
System.out.println(message);
}
private static void helloWorld() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("'hello world'");
String value = (String) expression.getValue();
System.out.println(value);
}
通过 strMethod() 方法进行代码调试,注意下面源码关键处,将在这些地方打上断点
重要步骤概括: 将表达式转成 Token 集合,将 token 转换成对应的什么 (是字符串,构造器,方法,还是属性) 引用,从表达式中获取值
源代码定位
根据以下全类名,在
idea
中快速定位 ,打上断点
// 重点:这个类spring封装中也是重点
org.springframework.expression.spel.standard.InternalSpelExpressionParser#doParseExpression
// 将表达式转成 Token 集合
org.springframework.expression.spel.standard.Tokenizer#process
// 将 token 转换成对应的 是字符串,构造器,方法,还是属性 引用
org.springframework.expression.spel.standard.InternalSpelExpressionParser#eatExpression
// 从表达式中获取值
org.springframework.expression.spel.ast.CompoundExpression#getValueInternal
// 如果是方法,则执行此方法
org.springframework.expression.spel.ast.MethodReference#getValueInternal(org.springframework.expression.EvaluationContext, java.lang.Object, org.springframework.core.convert.TypeDescriptor, java.lang.Object[])
调试代码
调试代码,关键的地方截图标记文字,降低源码阅读难度
从表达式中获取值
此处代码定位org.springframework.expression.spel.ast.MethodReference#getValueInternal(org.springframework.expression.EvaluationContext, java.lang.Object, org.springframework.core.convert.TypeDescriptor, java.lang.Object[])
spring
spring在此处配置了表达解析相关类
源码定准
根据以下全类名,在
idea
中快速定位,打上断点
org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
// 设置EL表达式解析器(Bean初始化完成后填充属性时会用到)
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
spring断点
在下面几处打上断点
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
org.springframework.context.expression.StandardBeanExpressionResolver#evaluate
// 此处就已和自己构建表达式解析一样的逻辑
org.springframework.expression.spel.standard.InternalSpelExpressionParser#doParseExpression
最终结果
结束
本文主要概述了
spring
中SpEL
表达式 的源理,在指出处快速打上断点,可以快速的进行代码调试,表达式解析过程中有嵌套调用及递归,调试过程需要一定的耐心