一. 概述
在基础的 spring 框架中,spring EL 表达式起到一个举足轻重的地位;例如,我们往对象设置一个不是固定的值,而是通过计算,或者从某个对象中某个属性的值,再或者是集合中过滤出来的值进行属性注入,从而达到灵活的操作;都是依赖于 spring EL 表达式的实现;
在 spring EL 表达式框架中,对外暴露的主要的接口,
EvaluationContext 上下文,提供者 properties、methods、fields 的解析以及类型转换 typeconversion 等功能;
ExpressionParser 对表达式进行词法解析,形成语法树功能;
Expression 是 ExpressionParser 解析表达式创建的对象;我们可以通过其获取对应的值,或赋值等操作;
SpelParserConfiguration 控制 Expression 的行为;例如,当通过角标定位到集合中的元素为 null 时,SpEL 可以创建一个对象等;
ParserContext 是 ExpressionParser 解析表达式过程提供的配置项,从而影响解析规则;不要被这个 Context 所蒙蔽
二. 原理
1. ExpressionParser
我们查看其接口所提供的方法,代码如下:
public interface ExpressionParser {
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
复制代码
其会对 expressionString 进行词法解析,从而形成一个语法树对象,存放到 Expression 对象中去;
在 spring 中,对外提供的实现类是 SpelExpressionParser,然而主要解析过程是交给另外一个实现类,即内部类 InternalSpelExpressionParser 去实现;之所以不直接使用 InternalSpelExpressionParser 该,主要是由于该类是线程不安全的操作,而且不能重复使用;
2. ParserContext
我们查看其接口所提供的方法,代码如下:
public interface ParserContext {
boolean isTemplate();
String getExpressionPrefix();
String getExpressionSuffix();
}
复制代码
通过接口我们可以看出,主要是解析前缀以及后缀之间的表达式,其余的都当做字符串表示,对象表示为 LiteralExpression;
3. SpelParserConfiguration
我们查看其类的属性
compilerMode 编译模式;目前有三种编译模式;(这一块我不是不太清楚具体里面细节,主要是由于里面的代码过于复杂;目前没有进行解读)
OFF ,默认值,关闭。即采用解释模式执行语法树
IMMEDIATE 执行几次评估(即调用 getValue 方法)时,切换为编译模式
MIXED 混合模式,经过几次评估后会切换为编译模式。如果失败,则切换为解释模式;
compilerClassLoader 类加载
autoGrowNullReferences 当引用的对象为空,且需要其对象进行操作时,会自动创建对象;避免空指针异常;
autoGrowCollections 主要用于集合容量超过或者角标越界问题,会自动扩容;
maximumAutoGrowSize 自动扩容最大的数量
4. EvaluationContext
我们查看其接口所提供的方法,代码如下:
public interface EvaluationContext {
/**
* 获取根对象
*/
TypedValue getRootObject();
/**
* 获取属性访问策略,例如集合的,对象,访问对应的实现类
*/
List<PropertyAccessor> getPropertyAccessors();
/**
* 获取构造解析器
*/
List<ConstructorResolver> getConstructorResolvers();
/**
* 获取方法解析器
*/
List<MethodResolver> getMethodResolvers();
/**
* 获取Bean解析器
*/
BeanResolver getBeanResolver();
/**
* 获取类型定位器
*/
TypeLocator getTypeLocator();
/**
* 获取类型转换对象
*/
TypeConverter getTypeConverter();
/**
* 获取类型比较器
*/
TypeComparator getTypeComparator();
/**
* 获取操作装载,实现该接口,可以对操作其他类型的操作,具体都有哪些操作,可以查看Operation这个枚举
*/
OperatorOverloader getOperatorOverloader();
/**
* 设置变量
*/
void setVariable(String name, @Nullable Object value);
/**
* 根据名称查找变量
*/
@Nullable
Object lookupVariable(String name);
}
复制代码
该上下文,主要用于执行表达式时所需要的策略,提供 properties,、methods,,fields 的解析,以及提供类型转换。
我们可以这么理解,ExpressionParser 是专注解析表达式,而 EvalueationContext 是专注于表达式执行,既是获取操作结果;有点类似于机制+策略相分离的味道,或者也可以理解设计模式中的策略模式,介绍比较有趣的一片文章《深入理解编程艺术之策略与机制相分离》
5. Expression
我们查看其接口所提供的方法,代码如下:
public interface Expression {
/**
* 获取原表达式
*/
String getExpressionString();
/**
* 获取表达式执行后的值
*/
@Nullable
Object getValue() throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
<T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
Object getValue(@Nullable Object rootObject) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
<T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
Object getValue(EvaluationContext context) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
<T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException;
/**
* 获取表达式执行后的值
*/
@Nullable
<T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException;
/**
* 获取表达式执行后的值的类型
*/
@Nullable
Class<?> getValueType() throws EvaluationException;
/**
* 获取表达式执行后的值的类型
*/
@Nullable
Class<?> getValueType(@Nullable Object rootObject) throws EvaluationException;
/**
* 获取表达式执行后的值的类型
*/
@Nullable
Class<?> getValueType(EvaluationContext context) throws EvaluationException;
/**
* 获取表达式执行后的值的类型
*/
@Nullable
Class<?> getValueType(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;
/**
* 获取表达式执行后的值的类型描述
*/
@Nullable
TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
/**
* 获取表达式执行后的值的类型描述
*/
@Nullable
TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException;
/**
* 获取表达式执行后的值的类型描述
*/
@Nullable
TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
/**
* 获取表达式执行后的值的类型描述
*/
@Nullable
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;
}
复制代码
6. ExpressionState
在开发中我们并不需要理解该类,然而要了解底层的原理,就需要知道这个类在框架的处于什么的角色;我们查看一下其属性:
relatedContext 类型 EvaluationContext,提供上下文;
rootObject 根对象
configuration 类型 SpelParserConfiguration
contextObjects 上下文堆栈,主要是存放执行结果的值。
variableScopes 局部变量堆栈
scopeRootObjects 局部根对象堆栈
其主要用于语法树对象获取值所提供值得来源;
7. 原理
为了更好理解它们之前是如何合作,以至于了解其底层原理;提供一个例子,然后进行关键代码解读;然而不对词法解析过程进行解读;例子:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor();
tesla.setInventions(new String[]{"0","1","2","3"});
//invention = "3"
String invention = parser.parseExpression("inventions[3]").getValue(context, tesla, String.class);
复制代码
第三步, 通过语法树,获取表达式的最终的值;
先获取 inventions 所对应的对象属性的值;通过 EvaluationContext 提供的 PropertyAccessor 从对象中找到属性名对应的值【new String[]{"0","1","2","3"}】,如图:
三. 语法
接下来,重点介绍语法规则;
有语法表达式基础的,可以直接看语法表达式即可;
没有的话,可以参照代码以及其语法表达式,进行代码阅读;对其语法表达式进行猜想;这样子对其解析的原理有一定的了解;而不是无头苍蝇的乱看;
1. 语法规则
对词法进行解析,主要交由 InternalSpelExpressionParser 去解析,下面列的方法都是该类 InternalSpelExpressionParser 中的方法;
语法规则有一个根表达式,就是 expression;对应的方法 eatExpression
expression
: logicalOrExpression
( ('='^ logicalOrExpression)
| (DEFAULT^ logicalOrExpression)
| ('?'^ expression COLON! expression)
| ('?:'^ expression))?;
复制代码
1.1 logicalOrExpression
对应的方法 eatLogicalOrExpression,对应的对象 OpOr;
logicalOrExpression : logicalAndExpression (('or' | '||')^ logicalAndExpression)*;
复制代码
1.2 logicalAndExpression
对应的方法 eatLogicalAndExpression,对应的对象 OpAnd
logicalAndExpression : relationalExpression (('and' | '&&')^ relationalExpression)*;
复制代码
1.3 relationalExpression
对应的方法 eatRelationalExpression,对应的对象 Operator 的子类;
relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
relationalOperator
: '=' | '!=' | '<' | '<=' | '>'
| '>=' | 'instanceof' | 'between' | 'matches'
复制代码
1.4 sumExpression
对应的方法 eatSumExpression。对应的对象 OpPlus 或 OpMinus
sumExpression: productExpression ( ('+'^ | '-'^) productExpression)*;
复制代码
1.5 productExpression
对应的方法 eatProductExpression。对应的对象 OpMultiply 或 OpDivide,OpModulus
productExpression: powerExpr (('*'^ | '/'^| '%'^) powerExpr)* ;
复制代码
1.6 powerExpr
对应的方法 eatUnaryExpression。对应的对象 OperatorPower 或 OpInc,OpDec
powerExpr : unaryExpression ('^'^ unaryExpression)? ('--' || '++') ;
复制代码
1.7 unaryExpression
对应的方法 eatUnaryExpression。对应的对象 OperatorNot 或 OpPlus,OpMinus,OpInc,OpDec
unaryExpression: ('+'^ | '-'^ | '!'^ | '++'^ | '--'^) unaryExpression | primaryExpression ;
复制代码
1.8 primaryExpression
对应的方法 eatPrimaryExpression。对应的对象 CompoundExpression
primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
复制代码
1.9 startNode
语法如下:
startNode
: parenExpr | literal
| type
| methodOrProperty
| functionOrVar
| projection
| selection
| firstSelection
| lastSelection
| indexer
| constructor
node : (('.' dottedNode) | ('?.' dottedNode) | nonDottedNode)+;
nonDottedNode : indexer;
dottedNode
: ((methodOrProperty
| functionOrVar
| projection
| selection
| firstSelection
| lastSelection
))
;
复制代码
对应的方法 maybeEatLiteral,节点对象 Literal 的子类
literal
: INTEGER_LITERAL
| boolLiteral
| STRING_LITERAL
| HEXADECIMAL_INTEGER_LITERAL
| REAL_LITERAL
| DQ_STRING_LITERAL
| NULL_LITERAL
#整数字面表达式
INTEGER_LITERAL
: (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?;
DECIMAL_DIGIT : '0'..'9' ;
INTEGER_TYPE_SUFFIX : ( 'L' | 'l' );
#字符串字面表达式
STRING_LITERAL: '\''! (APOS|~'\'')* '\''!;
DQ_STRING_LITERAL: '"'! (~'"')* '"'!;
APOS : '\''! '\'';
#布尔值字面表达式
boolLiteral: TRUE | FALSE;
#空字面表达式
NULL_LITERAL: 'null';
#十六进制字面表达式
HEXADECIMAL_INTEGER_LITERAL : ('0x' | '0X') (HEX_DIGIT)+ (INTEGER_TYPE_SUFFIX)?;
HEX_DIGIT : '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f';
INTEGER_TYPE_SUFFIX : ( 'L' | 'l' );
#实数字面表达式
REAL_LITERAL :
('.' ('0'..'9')+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?)
| (('0'..'9')+ '.' ('0'..'9')+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?)
| (('0'..'9')+ (EXPONENT_PART) (REAL_TYPE_SUFFIX)?)
| (('0'..'9')+ (REAL_TYPE_SUFFIX));
EXPONENT_PART : 'e' ('+' | '-')* (DECIMAL_DIGIT)+
| 'E' ('+' | '-')* (DECIMAL_DIGIT)+ ;
REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd';
复制代码
对应的方法 maybeEatParenExpression,
parenExpr : '('! expression ')'!;
复制代码
对应的方法 maybeEatTypeReference,对应的节点对象 TypeReference
type: 'T(' qualifiedId ')' -> ^(TYPEREF qualifiedId);
qualifiedId : ID (DOT ID)* -> ^(QUALIFIED_IDENTIFIER ID*);
复制代码
对应的方法 maybeEatNullReference,对应的节点对象 NullLiteral
对应的方法 maybeEatConstructorReference,对应的节点对象 ConstructorReference
constructor
: ('new' qualifiedId '(') => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
ctorArgs : '('! (expression (','! expression)*)? ')'!;
复制代码
对应的方法 maybeEatIndexer,对应的节点对象 Indexer。
indexer : '[' expression ']'
复制代码
对应的方法 maybeEatMethodOrProperty,对应的节点对象 PropertyOrFieldReference 或 MethodReference;
methodOrProperty
: (ID LPAREN) => id=ID methodArgs -> ^(METHOD[$id] methodArgs) #类中方法
| id=ID -> ^(PROPERTY_OR_FIELD[$id]) # 类中属性
;
methodArgs : '('! (argument (COMMA! argument)* (COMMA!)?)? ')'!;
复制代码
对应的方法 maybeEatFunctionOrVar,对应的节点对象 VariableReference 或 FunctionReference
functionOrVar
: ('#' ID '(') => function
| var;
function : '#' id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);
var : '#' id=ID -> ^(VARIABLEREF[$id]);
复制代码
对应的方法 maybeEatProjection。对应的节点对象 Projection
projection: '!['^ expression ']'!;
复制代码
对应的方法 maybeEatSelection,对应的对象 Selection
selection: '?['^ expression ']'!;
firstSelection: '^['^ expression ']'!;
lastSelection: '$['^ expression ']'!;
复制代码
对应的方法 maybeEatBeanReference,对应的对象 BeanReference
beanReference: ('@' | '&')! ID
复制代码
对应的方法 maybeEatInlineListOrMap,对应的对象 InlineList,InlineMap
list = '{' (element (',' element)*) '}';
map = '{' (key ':' value (',' key ':' value)*) '}';
复制代码
2. 语法树
上面简单介绍了词法表达式以及词法关键的方法,以及对应的创建的语法树对象;然而并没有过多讲解语法树对象;语法树的接口为 SpelNode,简单列一下关键的 SpelNode 关键的实现类的类图:
这里不在过多讲解里面的细节;具体需要看起对应的源代码即可看到其逻辑;
SpelNodeImpl 子类中,关键查看 getValueInternal 方法即可。
四. 运用
在 spring 中,哪些运用了 Spring EL 表达式框架呢?
1. StringValueResolver
该接口是对字符串进行解析; 对应的主要实现类是 EmbeddedValueResolver。其并没有直接使用 ExpressionParser。而是通过 StandardBeanExpressionResolver 间接使用;
我们重点看 StandardBeanExpressionResolver 中的 evaluate 方法,重点里面构造了 StandardEvaluationContext 对象,代码如下:
public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
if (!StringUtils.hasLength(value)) {
return value;
}
try {
Expression expr = this.expressionCache.get(value);
if (expr == null) {
expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
this.expressionCache.put(value, expr);
}
StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
if (sec == null) {
sec = new StandardEvaluationContext(evalContext);
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
sec.addPropertyAccessor(new BeanFactoryAccessor());
sec.addPropertyAccessor(new MapAccessor());
sec.addPropertyAccessor(new EnvironmentAccessor());
sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
if (conversionService != null) {
sec.setTypeConverter(new StandardTypeConverter(conversionService));
}
customizeEvaluationContext(sec);
this.evaluationCache.put(evalContext, sec);
}
return expr.getValue(sec);
}
catch (Throwable ex) {
throw new BeanExpressionException("Expression parsing failed", ex);
}
}
复制代码
我们重点构建 StandardEvaluationContext 对象,传入了哪些值。
BeanExpressionContextAccessor 调用 BeanExpressionContext.getObject 方法获取该名称所对应的对象;
BeanFactoryAccessor 调用 BeanFactory.getBean 方法获取该名称所对应的对象
EnvironmentAccessor 调用 Environment.getProperty 方法获取该名称所对应的对象
BeanFactoryResolver 调用 BeanFactory.getBean 方法获取该名称所对应的对象
我们重点看 BeanExpressionContextAccessor,因为在上面的代码中,传入的根对象是 BeanExpressionContext。我们查看该类的代码:
public class BeanExpressionContext {
private final ConfigurableBeanFactory beanFactory;
@Nullable
private final Scope scope;
public BeanExpressionContext(ConfigurableBeanFactory beanFactory, @Nullable Scope scope) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
this.scope = scope;
}
public final ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
}
@Nullable
public final Scope getScope() {
return this.scope;
}
public boolean containsObject(String key) {
return (this.beanFactory.containsBean(key) ||
(this.scope != null && this.scope.resolveContextualObject(key) != null));
}
@Nullable
public Object getObject(String key) {
if (this.beanFactory.containsBean(key)) {
return this.beanFactory.getBean(key);
}
else if (this.scope != null){
return this.scope.resolveContextualObject(key);
}
else {
return null;
}
}
//.....
}
复制代码
我们可以看到该对象里有关键的两个属性,一个是 spring 的 BeanFactory 对象,一个是 Scope;
以及提供了默认 ParserContext 的实现类:是基于“#{}”进行解析的;
那么谁会用到 StringValueResolver 这个类呢?
CommonAnnotationBeanPostProcessor 该类主要是对 @Resource 等注解进行属性注入,具体使用 EL 表达式的是 @Resource 中的名称
ScheduledAnnotationBeanPostProcessor 有关定时调度的处理器;表达式处理的主要的 @Scheduled 中的 initialDelayString、cron、zone、fixedDelayString、fixedRateString 等属性的字符串;
EmbeddedValueResolutionSupport 有关类型转换的抽象类,主要处理时间戳的类型;具体细节需要查看对应的代码;
BeanFactory 该对象中的 doResolveDependency 方法使用了表达式。例如做属性注入以及构造函数创建对象时,就会调用该 BeanFactory.doResolveDependency 进行属性注入;这个时候我们就可以使用 EL 表达式;主要是 @Value 注解上使用表达式。
五. 总结
通过对 Spring EL 表达式的提供的主要关键类,语法规则,以及在 spring 框架哪个过程使用该技术进行了粗略的介绍后,对其底层原理有一定的了解,以便于高效的开发 spring 应用;
虽然说,spring EL 表达式中还有一些细节以及一些特性没有进行介绍,但可以通过这篇文章,可以很快速的找到对应的代码处理逻辑,从而进一步的了解;
评论