表达式本质上类似于计算机编程语言的求值语句,它需要进行编译,词法/语法解析、变量/函数映射、求值、存储等一系列步骤串联起来进行求值。
JUEL实现了UEL("通用表达式语言")的接口,并扩展出一些类型定义以便能够被更加容易地使用。
JUEL基本框架
1. 表达式工厂(ExpressionFactory/ExpressionFactoryImpl):
UEL整体上使用的是虚拟工厂创建型模式,工厂创建了一系列产品:类型转换器、表达式、表达式语法编译器、表达式存储器和其余一些有助于优化的工具。
1.执行类型强制转换(所以需要自定义类型转换工具类)
@Override public final Object coerceToType(Object obj, Class<?> targetType) { return converter.convert(obj, targetType); }
2.创建值表达式(JUEL采用Tree来组织于存储表达式语法,因此更确切地说应该是创建树形的值表达式)
@Override public final TreeValueExpression createValueExpression(ELContext context, String expression, Class<?> expectedType) { return new TreeValueExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter, expression, expectedType); }
3.创建对象值表达式(JUEL采用Tree来组织于存储表达式语法,因此更确切地说应该是创建树形的对象值表达式)
@Override public final ObjectValueExpression createValueExpression(Object instance, Class<?> expectedType) { return new ObjectValueExpression(converter, instance, expectedType); }
4.创建方法表达式(JUEL采用Tree来组织于存储表达式语法,因此更确切地说应该是创建树形的方法表达式)
@Override public final TreeMethodExpression createMethodExpression(ELContext context, String expression, Class<?> expectedReturnType, Class<?>[] expectedParamTypes) { return new TreeMethodExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter, expression, expectedReturnType, expectedParamTypes); }
5.缓存(因为表达式的编译解释是一个耗时过程,引入缓存是为了提升效力,把编译解释过的表达式缓存起来以便复用)
java.util.Properties properties = new java.util.Properties(); properties.put("javax.el.cacheSize", "5000"); javax.el.ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl(properties);
2. 表达式上下文(ELContext/SimpleContext):
表达式上下文用来辅助表达式创建时即规定了求值规则以及运行时才指定求值规则过程。
1.创建时
表达式上下文在表达式的创建时引出setFunction()
用来绑定表达式串中的token到类静态方法的映射,以便求值时转换成方法调用;(内部由function mapper维护映射关系)
表达式上下文在表达式的创建时引出setVariable()
用来绑定表达式串中的token到值的映射,以便求值时转换成值。(内部由variables mapper维护映射关系)
上代码:import javax.el.*; import de.odysseus.el.util.SimpleContext; import de.odysseus.el.ExpressionFactoryImpl; ... ExpressionFactory factory = new ExpressionFactoryImpl(); SimpleContext context = new SimpleContext(); context.setFunction("math", "sin", Math.class.getMethod("sin", double.class)); context.setVariable("pi", factory.createValueExpression(Math.PI, double.class)); ValueExpression expr = factory.createValueExpression(context, "${math:sin(pi/2)}", double.class); System.out.println("math:sin(pi/2) = " + expr.getValue(context)); // 1.0
创建时通过制定token到变量和函数的映射然后进行
getValue()
和invoke()
对表达式进行求值,这个容易理解。然而对于创建时没有制定/不容易制定映射规则时,也就是运行时改怎么办呢?2.运行时
表达式上下文在表达式的运行时求值通过resolver对未决的variables进行赋值以及对property进行计算。
一个resolver解释一对base.property
,确定base的值后进行property的getValue()/setValue()
。
由于表达式串语法的多样性,往往一个resolver并不能解决整个表达式的求值,因此,resolver是可以组合使用的。CompositeELResolver
组合resolver形成职责链路,表达式流经链路后最终完成求值。
链路里的resolver通过context.setPropertyResolved(true)
调用告知context上下文“表达式在我这里已经确定”来中断表达式继续在链路里流转。
上代码:import java.util.Date; import javax.el.*; import de.odysseus.el.util.SimpleContext; import de.odysseus.el.util.SimpleResolver; import de.odysseus.el.ExpressionFactoryImpl; ... ExpressionFactory factory = new ExpressionFactoryImpl(); SimpleContext context = new SimpleContext(new SimpleResolver()); // resolve top-level property factory.createValueExpression(context, "#{pi}", double.class).setValue(context, Math.PI); ValueExpression expr1 = factory.createValueExpression(context, "${pi/2}", double.class); System.out.println("pi/2 = " + expr1.getValue(context)); // 1.5707963... // resolve bean property factory.createValueExpression(context, "#{current}", Date.class).setValue(context, new Date()); ValueExpression expr2 = factory.createValueExpression(context, "${current.time}", long.class); System.out.println("current.time = " + expr2.getValue(context));
然而以上只是针对top-level property以及简单的base.property进行求值。对于那些复杂的、级联很多的表达式(.号很多)如何进行求值呢?以下我们来看看JUEL的求值过程是怎样的。
JUEL求值过程
我们从ExpressionFactoryImpl
分析起。
首先:
需要给工厂制定一个类型转换器做强制类型转换校验。
JUEL定义了TypeConverterImpl
实现TypeConverter
接口。
如果需要增加更多的类型转换算法,只需要扩展TypeConverterImpl
即可。然后:
表达式工厂的主要职责就是创建表达式。UEL把表达式分为2种,ValueExpression
和MethodExpression
。这里,JUEL增加了一种叫做ObjectValueExpression
的表达式,它实际上是ValueExpression
的子类,直接封装一个Object为表达式进行求值,并不需要输入一个表达式字符串。
JUEL工厂创建3类表达式:@Override public final ObjectValueExpression createValueExpression(Object instance, Class<?> expectedType) { return new ObjectValueExpression(converter, instance, expectedType); } @Override public final TreeValueExpression createValueExpression(ELContext context, String expression, Class<?> expectedType) { return new TreeValueExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter, expression, expectedType); } @Override public final TreeMethodExpression createMethodExpression(ELContext context, String expression, Class<?> expectedReturnType, Class<?>[] expectedParamTypes) { return new TreeMethodExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter, expression, expectedReturnType, expectedParamTypes); }
除了
ObjectValueExpression
之外,可以看到工厂创建了TreeValueExpression
和TreeMethodExpression
。JUEL以树来组织表达式,以下进一步分析这两类树型表达式。
TreeValueExpression:
实现级连式的表达式(带有多个.的表达式)求值。求得的值是其子树表达式的值,这是一个递归过程,因为其子树表达式也可能是一个TreeValueExpression
。
看TreeValueExpression
的成员变量都有谁private final TreeBuilder builder; private final Bindings bindings; private final String expr; private final Class<?> type; private final boolean deferred; private transient ExpressionNode node; private String structure;
构造函数
public TreeValueExpression(TreeStore store, FunctionMapper functions, VariableMapper variables, TypeConverter converter, String expr, Class<?> type) { super(); Tree tree = store.get(expr); this.builder = store.getBuilder(); this.bindings = tree.bind(functions, variables, converter); this.expr = expr; this.type = type; this.node = tree.getRoot(); this.deferred = tree.isDeferred(); if (type == null) { throw new NullPointerException(LocalMessages.get("error.value.notype")); } }
求值过程有诸多工具类参与其中,因此我们有必要先来了解这些参与求值的工具类。
Bindings:
这个没有什么好说的,其本质上起到一个表达式求值时对变量和方法的调用映射到设计时在ELContext
里指定好的FunctionMapper
和VariablesMapper
里的响应方法和变量。表达式求值时如果没有找到方法和变量,则会通过resolver链路去决断。
ExpressionNode:
TreeStore:ExpressionFactoryImpl
在createValueExpression(...)
里new一个TreeValueExpression
对象,从其构造函数里可以看到对象的表达式串的语法tree实际上是从TreeStore
里取出来的。TreeStore
保持了TreeCache
和TreeBuilder
的引用。如果表达式串的语法tree已在TreeCache
中则从缓存取出语法tree,如不存在于TreeCache
中则通过TreeBuilder
构造一个对应于表达式串的语法tree并存入TreeCache
中留用。
Cache实现了一个简单的、线程安全的、二级的、LRU缓存。
TreeBuilder:
实现表达式串到语法树的构建。这是一个编译解释过程,涉及到词法分析和语法分析(语义分析)。涉及到两个重要的工具类:Scanner
和Parser
。Scanner: 表达式的词法分析器,把表达式拆解成一个一个的单词(Token)。
Scanner解析Token的流程分析,见下图:
未完,待续......
我的微信公众号: