一、简介

JUEL是统一表达式语言EL(Unified Expression Language)的实现,是JSP 2.1标准(JSR-245)的一部分,已在JEE5中引入。它有以下特性:

  • 高性能

解析表达式是明确的性能瓶颈,JUEL使用手工编码的解析器,比之前使用的(javacc)生成的解析器快10倍。

  • 可插拔式缓存(Pluggable Cache)

即使JUEL的解析器速度很快,但解析表达式的成本也相对较高;因此,最好只解析一个表达式字符串一次。JUEL提供了默认缓存机制,在大多数情况下应该足够,同时,JUEL也允许轻松插入自定义的缓存。

  • 占用空间小

JUEL经过精心设计,以最大限度地减少内存使用和代码大小。

  • 支持方法调用

JUEL支持类似${foo.matches('[0-9]+')}的方法调用;使用EL的解析器机制解析和调用方法。从JUEL2.2开始,默认启用方法调用。

  • 可变参数调用

JUEL支持Java 5的可变参数的函数和方法调用。例如:${format('Hey %s','Joe')}(String.format(String, String...))。从JUEL2.2开始,默认启用VarArgs

  • 可插拔

使用JUEL不需要应用程序显式引用任何JUEL特定的实现类。

二、快速入门

1、JAR

JUEL相关JAR:

  • juel-api-x.y.z.jar

包含javax.elAPI。

  • juel-impl-x.y.z.jar

包含de.odysseus.el实现类。

  • juel-spi-x.y.z.jar

包含javax.el.E​​xpressionFactory服务提供者资源,当类路径上有多个表达式语言实现并希望JUEL的实现是ExpressionFactory.newInstance()时需要。

2、样例

下载juel-api-2.2.7.jarjuel-impl-2.2.7.jar或使用Maven配置:

<dependency>
    <groupId>de.odysseus.juel</groupId>
    <artifactId>juel-api</artifactId>
    <version>2.2.7</version>
</dependency>

<dependency>
    <groupId>de.odysseus.juel</groupId>
    <artifactId>juel-impl</artifactId>
    <version>2.2.7</version>
</dependency>

代码:

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;

import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;

public class Example {

	public static void main(String[] args) throws Exception {
		//工厂和上下文
		ExpressionFactory factory = new ExpressionFactoryImpl();
		SimpleContext context = new SimpleContext();
		
		//将function math:max(int, int)映射到java.lang.Math.max(int, int)
		context.setFunction("math", "max", Math.class.getMethod("max", int.class, int.class));
		//将变量foo映射到0
		context.setVariable("foo", factory.createValueExpression(0, int.class));
		
		ValueExpression expression = factory.createValueExpression(context, "${math:max(foo,bar)}", int.class);
		//设置顶级属性bar的值为1
		factory.createValueExpression(context, "${bar}", int.class).setValue(context, 1);
		//获取表达式的值
		System.out.println(expression.getValue(context));
	}
}

运行程序,输出0和1的最大值为:1。

三、实用程序类

在创建和计算表达式时会用到一些比较重要的类,例如:在创建和计算时需要使用javax.el.E​​LContext,它包含访问函数映射器(FunctionMapper)、变量映射器(VariableMapper)和解析器(ELResolver)的方法。

  • 在创建(creation)时,上下文的函数映射器和变量映射器将函数调用绑定到静态方法、将标识符(变量)绑定到值表达式,此时不使用上下文的解析器。

  • 在计算(evaluation)时,上下文的解析器用于属性和未绑定的标识符(顶级属性)的解析,此时不使用上下文的映射器

JUEL提供了这些类的简单实现,使得可以开箱即用。

1、简单上下文

de.odysseus.el.util.SimpleContext是一个简单的上下文实现类,它可以在创建时和计算时使用。

在创建时可以使用以下方法:

  • setFunction(String prefix, String name, Method method)

将方法定义为给定前缀和名称的函数;没有命名空间的函数必须传入空字符串作为prefix。

  • setVariable(String name, ValueExpression expression)

将值表达式定义为给定名称的变量。

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 expression = factory.createValueExpression(context, "${math:sin(pi/2)}", double.class);
System.out.println("math:sin(pi/2) = " + expression.getValue(context));//math:sin(pi/2) = 1.0

在计算(求值)时,需要使用javax.el.E​​LResolver进行属性解析和解析未绑定到变量的标识符。

解析器可以在构造上下文时传递给SimpleContext,如果使用默认构造函数,调用getELResolver()时将延迟创建de.odysseus.el.util.SimpleResolver的实例。

2、简单解析器

SimpleContext.java:

public SimpleContext(ELResolver resolver) {
	this.resolver = resolver;
}


@Override
public ELResolver getELResolver() {
	if (resolver == null) {
		resolver = new SimpleResolver();
	}
	return resolver;
}

SimpleResolver.java:

/**
 * Create a resolver capable of resolving top-level identifiers. Everything else is passed to
 * the supplied delegate.
 */
public SimpleResolver(ELResolver resolver, boolean readOnly) {
	delegate = new CompositeELResolver();
	delegate.add(root = new RootPropertyResolver(readOnly));
	delegate.add(resolver);
}

de.odysseus.el.util.SimpleResolver是一个简单的解析器实现类,适用于解析顶级标识符并委托给在构造时提供的另一个解析器。

如果未提供解析器委托,则将使用复合解析器(CompositeELResolver)作为默认值,能够解析bean属性、数组值、列表值、资源值和映射值。

解析器用于解析属性,它操作一对称为baseproperty的对象;JUEL的简单解析器维护了一个映射来直接解析顶级属性(即base==null),解析base!=nullbase/property对(pairs)时将被委拖。

ExpressionFactory factory = new ExpressionFactoryImpl();
SimpleContext context = new SimpleContext(new SimpleResolver());
factory.createValueExpression(context, "#{pi}", double.class).setValue(context, Math.PI);
ValueExpression expression = factory.createValueExpression(context, "${pi/2}", double.class);
System.out.println("pi/2 = " + expression.getValue(context));

factory.createValueExpression(context, "#{current}", Date.class).setValue(context, new Date());
expression = factory.createValueExpression(context, "${current.time}", long.class);
System.out.println("current.time = " + expression.getValue(context));

四、表达式工厂

表达式工厂javax.el.E​​xpressionFactory用于创建各种类型的表达式,JUEL的表达式工厂实现类是de.odysseus.el.E​​xpressionFactoryImpl;创建表达式工厂实例的最简单方法是:

ExpressionFactory factory = new ExpressionFactoryImpl();

表达式工厂是线程安全的,可以创建无限数量的表达式。表达式工厂提供以下操作:

1、表达式缓存

每个工厂实例使用自己的表达式缓存;JUEL提供了一个缓存接口,允许应用程序使用自己的缓存机制。大多数情况下,JUEL的默认实现(基于ConcurrentHashMap和ConcurrentLinkedQueue实现)就可以满足需求。

默认构造函数使用的最大缓存大小为1000,可以通过javax.el.cacheSize属性来指定不同的值。

Properties properties = new Properties();
properties.put("javax.el.cacheSize", "5000");
ExpressionFactory factory = new ExpressionFactoryImpl(properties);

2、类型转换

在表达式求值(evaluating expressions)时,有多处类型转换:

  • 执行算术或逻辑运算时强转操作数

  • 值表达式结果被强转为创建时指定的预期类型

  • 对于文字方法表达式,文本被强转为创建时指定的类型

  • 对于非文字方法表达式,最后一个属性被强转为方法名称

  • 复合表达式在连接它们之前将它们的子表达式强转为字符串

ExpressionFactoryImpl类中的转换方法如下:

public final Object coerceToType(Object obj, Class<?> targetType)

此方法根据EL类型转换规则将对象强转为特定类型,如果应用转换规则导致错误,则抛出ELException。

五、值表达式

值表达式对应的类为javax.el.ValueExpression,它定义了getValue(ELContext)setValue(ELContext, Object)isReadOnly(ELContext)getType(ELContext)等抽象方法。

如果值表达式是由单个标识符或非文字前缀(函数、标识符或嵌套表达式)接着(followed by)一系列属性运算符(.[])组成的eval表达式(#{...}或${...}),则称为左值表达式,例如:${customer.age}${customer.address["street"];其他所有的值表达式都称为非左值表达式,例如:${customer.age + 20}${55}

对于非左值表达式:

  • getType(ELContext)方法将始终返回null。

  • isReadOnly(ELContext)方法将始终返回true。

  • setValue(ELContext, Object)方法总是会抛出异常。

1、树值表达式

树值表达式对应的类为de.odysseus.el.TreeValueExpression,它是javax.el.ValueExpression的子类。

树值表达式的创建包括:

  • 解析表达式字符串并构建抽象语法树

  • 使用上下文提供的映射器绑定函数和变量

表达式创建后,可以使用getValue(ELContext)方法计算表达式的值,结果会自动强转为创建时指定的预期类型。

树值表达式通过ExpressionFactoryImpl.createValueExpression(ELContext, String, Class<?>)方法创建,除父类的方法外,它还提供了:

  • void dump(java.io.PrintWriter writer):转储解析树。

  • boolean isDeferred():如果表达式被延迟(包含eval表达式#{...}或${...}),则为true。

  • boolean isLeftValue():如果表达式是左值表达式,则为true。

ExpressionFactoryImpl factory = new ExpressionFactoryImpl();
SimpleContext context = new SimpleContext();
ObjectValueExpression pi = factory.createValueExpression(Math.PI, Double.class);
context.setVariable("pi", pi);

TreeValueExpression expression = factory.createValueExpression(context, "#{pi/2}", Object.class);
PrintWriter writer = new PrintWriter(System.out);
expression.dump(writer);
writer.flush();
System.out.println(expression.isDeferred());
System.out.println(expression.isLeftValue());
System.out.println(expression.getValue(context));

输出:

+- #{...}
   |
   +- '/'
      |
      +- pi
      |
      +- 2
true
false
1.5707963267948966

2、对象值表达式

对象值表达式对应的类为de.odysseus.el.ObjectValueExpression,它是javax.el.ValueExpression的子类。

一个对象值表达式简单地包装一个对象,它用于定义变量,表达式创建后,可以使用getValue(ELContext)方法计算表达式的值,该方法仅返回包装的对象,强转为创建时提供的期望类型。

对象值表达式通过ExpressionFactoryImpl.createValueExpression(Object, Class<?>)方法创建:

ExpressionFactoryImpl factory = new ExpressionFactoryImpl();
SimpleContext context = new SimpleContext();
ObjectValueExpression expression = factory.createValueExpression(Math.PI, Double.class);

六、方法表达式

方法表达式javax.el.MethodExpression可以通过invoke(ELContext, Object<?>[])被调用。

如果方法表达式的底层表达式是文本(即isLiteralText()方法返回true),则该方法表达式称为文字方法表达式。其他所有方法表达式都称为非文字方法表达式,非文字方法表达式与左值表达式共享相同的语法。

对于文字方法表达式:

  • invoke(ELContext, Object<?>[])方法只返回表达式字符串,可以选择强转为创建时指定的预期返回类型。

  • getMethodInfo(ELContext)方法总是返回null。

对于非文字方法表达式:

  • invoke(ELContext, Object<?>[])方法将表达式计算为java.lang.reflext.Method并调用此方法。

  • 找到的方法必须匹配预期的返回类型和创建时给出的参数类型,否则抛出异常。

1、树方法表达式

树方法表达式对应的类为de.odysseus.el.TreeMethodExpression,它是javax.el.MethodExpression的子类。

树方法表达式通过ExpressionFactoryImpl.createMethodExpression(ELContext context, String context, Class<?> expectedReturnType, Class<?>[] expectedParamTypes)方法创建,除父类的方法外,它还提供了:

  • void dump(java.io.PrintWriter writer):转储解析树。

  • boolean isDeferred():如果表达式被延迟(包含eval表达式#{...}或${...}),则为true。

ExpressionFactoryImpl factory = new ExpressionFactoryImpl();
SimpleContext context = new SimpleContext();
context.setVariable("str", factory.createValueExpression("He", String.class));
TreeMethodExpression expression = factory.createMethodExpression(context, "#{str.concat}", String.class, new Class[]{String.class});
PrintWriter writer = new PrintWriter(System.out);
expression.dump(writer);
writer.flush();
System.out.println(expression.isDeferred());
Object result = expression.invoke(context, new Object[]{"llo"});
System.out.println(result);

输出:

+- #{...}
   |
   +- . concat
      |
      +- str
true
Hello
参考资料

Java Unified Expression Language :: Guide