写点什么

Spring 之 EL 表达式

用户头像
邱学喆
关注
发布于: 27 分钟前
Spring之 EL表达式

一. 概述

在基础的 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);
复制代码
  • 第一步,进行词法解析;从而形成一颗语法树,如图所示:

  • 第二步, 创建 ExpressionState,如图所示:

  • 第三步, 通过语法树,获取表达式的最终的值;

  • 先获取 inventions 所对应的对象属性的值;通过 EvaluationContext 提供的 PropertyAccessor 从对象中找到属性名对应的值【new String[]{"0","1","2","3"}】,如图:

  • 【new String[]{"0","1","2","3"}】向 state 对象中的 contextObjects 堆栈压栈,如图所示:

  • 接着将根对象向 state 对象中的 contextObjects 堆栈进行压栈,如图所示:

  • 获取【[3]】对象 Indexer 角标的值为 3;

  • 封装其对象 ArrayIndexingValueRef,结果如图所示:

  • 调用 ArrayIndexingValueRef 对象的 getValue 方法,获取真正的值“3”:如图所示:

  • 第四步,进行类型转换,得到最终的值,如图所示;有关类型转换的处理,请看《Spring 类型转换

三. 语法

接下来,重点介绍语法规则;

有语法表达式基础的,可以直接看语法表达式即可;

没有的话,可以参照代码以及其语法表达式,进行代码阅读;对其语法表达式进行猜想;这样子对其解析的原理有一定的了解;而不是无头苍蝇的乱看;

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 )) ;
复制代码
  • literal

对应的方法 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';
复制代码
  • parenExpr

对应的方法 maybeEatParenExpression,

parenExpr : '('! expression ')'!;
复制代码
  • type

对应的方法 maybeEatTypeReference,对应的节点对象 TypeReference

type:	'T(' qualifiedId ')' -> ^(TYPEREF qualifiedId);qualifiedId : ID (DOT ID)* -> ^(QUALIFIED_IDENTIFIER ID*);
复制代码
  • null

对应的方法 maybeEatNullReference,对应的节点对象 NullLiteral

null: 'null'
复制代码
  • ConstructorOrReference

对应的方法 maybeEatConstructorReference,对应的节点对象 ConstructorReference

constructor    :	('new' qualifiedId '(') => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)ctorArgs : '('! (expression (','! expression)*)? ')'!;
复制代码
  • Indexer

对应的方法 maybeEatIndexer,对应的节点对象 Indexer。

indexer : '[' expression ']'
复制代码
  • MethodOrProperty

对应的方法 maybeEatMethodOrProperty,对应的节点对象 PropertyOrFieldReference 或 MethodReference;

methodOrProperty	:	(ID LPAREN) => id=ID methodArgs -> ^(METHOD[$id] methodArgs) #类中方法	|	id=ID -> ^(PROPERTY_OR_FIELD[$id]) # 类中属性	;methodArgs : '('! (argument (COMMA! argument)* (COMMA!)?)? ')'!;
复制代码
  • FunctionOrVar

对应的方法 maybeEatFunctionOrVar,对应的节点对象 VariableReference 或 FunctionReference

functionOrVar	 : ('#' ID '(') => function	 | var;	function : '#' id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);var : '#' id=ID -> ^(VARIABLEREF[$id]);
复制代码
  • Projection

对应的方法 maybeEatProjection。对应的节点对象 Projection

projection: '!['^ expression ']'!;
复制代码
  • Selection

对应的方法 maybeEatSelection,对应的对象 Selection

selection: '?['^ expression ']'!;firstSelection:	'^['^ expression ']'!;lastSelection: '$['^ expression ']'!;
复制代码
  • BeanReference

对应的方法 maybeEatBeanReference,对应的对象 BeanReference

beanReference: ('@' | '&')! ID
复制代码
  • InlineListOrMap

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


发布于: 27 分钟前阅读数: 7
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
Spring之 EL表达式