JUEL 学习笔记-(1)

本文介绍了JUEL,一个实现UEL通用表达式语言的框架。内容包括JUEL的基本框架,如表达式工厂和表达式上下文,以及求值过程。详细讲解了表达式工厂如何创建不同类型的表达式,特别是树形结构的值和方法表达式。同时,提到了表达式上下文在创建时和运行时的角色,以及如何通过resolver链路处理复杂的表达式求值。文章还涉及了JUEL的词法分析和语法树构建过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

表达式本质上类似于计算机编程语言的求值语句,它需要进行编译,词法/语法解析、变量/函数映射、求值、存储等一系列步骤串联起来进行求值。

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种,ValueExpressionMethodExpression。这里,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之外,可以看到工厂创建了TreeValueExpressionTreeMethodExpression。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里指定好的FunctionMapperVariablesMapper里的响应方法和变量。表达式求值时如果没有找到方法和变量,则会通过resolver链路去决断。 

ExpressionNode: 


TreeStore: 
ExpressionFactoryImplcreateValueExpression(...)里new一个TreeValueExpression对象,从其构造函数里可以看到对象的表达式串的语法tree实际上是从TreeStore里取出来的。TreeStore保持了TreeCacheTreeBuilder的引用。如果表达式串的语法tree已在TreeCache中则从缓存取出语法tree,如不存在于TreeCache中则通过TreeBuilder构造一个对应于表达式串的语法tree并存入TreeCache中留用。
Cache实现了一个简单的、线程安全的、二级的、LRU缓存。

TreeBuilder: 
实现表达式串到语法树的构建。这是一个编译解释过程,涉及到词法分析和语法分析(语义分析)。涉及到两个重要的工具类:ScannerParser

Scanner: 表达式的词法分析器,把表达式拆解成一个一个的单词(Token)。 
Scanner解析Token的流程分析,见下图: 

 

未完,待续......



 

我的微信公众号:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值