一. 概述
在基础的 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 | constructornode : (('.' 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 表达式中还有一些细节以及一些特性没有进行介绍,但可以通过这篇文章,可以很快速的找到对应的代码处理逻辑,从而进一步的了解;
评论