JEXL是一个旨在促进应用程序和框架中动态和脚本功能实现的库。
Example:
在最简单的形式中,JEXL 在评估表达式时将JexlExpression 与 JexlContext 合并。使用JexlEngine.createExpression(java.lang.String)传递包含有效JEXL语法的String 创建表达式 。可以使用MapContext实例创建简单的JexlContext ; 可以通过其构造函数选择提供内部包装的变量映射。以下示例采用名为car的变量,并在属性engine上调用checkStatus()方法
// Create a JexlEngine (could reuse one instead)
JexlEngine jexl = new JexlBuilder().create();
// Create an expression object equivalent to 'car.getEngine().checkStatus()':
String jexlExp = "car.engine.checkStatus()";
Expression e = jexl.createExpression( jexlExp );
// The car we have to handle coming as an argument...
Car car = theCarThatWeHandle;
// Create a context and add data
JexlContext jc = new MapContext();
jc.set("car", car );
// Now evaluate the expression, getting the result
Object o = e.evaluate(jc);
使用JEXL
API由三个级别组成,以满足不同的功能需求:
- 动态调用setter,getters,方法和构造函数
- 脚本表达式称为JEXL表达式
- JSP / JSF之类的表达式称为JXLT表达式
重要的提示:
公共API类位于2个包中:
org.apache.commons.jexl3
org.apache.commons.jexl3.introspection
以下软件包遵循“按您自己的维护成本使用”政策; 这些仅用于扩展JEXL。他们的类和方法不保证在后续版本中保持兼容。如果您认为需要直接使用他们的某些功能或方法,最好先通过邮件列表与社区联系。
org.apache.commons.jexl3.parser
org.apache.commons.jexl3.scripting
org.apache.commons.jexl3.internal
org.apache.commons.jexl3.internal.introspection
动态调用
这些功能与BeanUtils中的核心级实用程序非常接近 。对于基本的动态属性操作和方法调用,您可以使用以下方法集:
- JexlEngine.newInstance(java.lang.Class<? extends T>, java.lang.Object...)
- JexlEngine.setProperty(java.lang.Object, java.lang.String, java.lang.Object)
- JexlEngine.getProperty(java.lang.Object, java.lang.String)
- JexlEngine.invokeMethod(java.lang.Object, java.lang.String, java.lang.Object...)
以下示例说明了它们的用法:
// test outer class
public static class Froboz {
int value;
public Froboz(int v) { value = v; }
public void setValue(int v) { value = v; }
public int getValue() { return value; }
}
// test inner class
public static class Quux {
String str;
Froboz froboz;
public Quux(String str, int fro) {
this.str = str;
froboz = new Froboz(fro);
}
public Froboz getFroboz() { return froboz; }
public void setFroboz(Froboz froboz) { this.froboz = froboz; }
public String getStr() { return str; }
public void setStr(String str) { this.str = str; }
}
// test API
JexlEngine jexl = new JexlBuilder().create();
Quux quux = jexl.newInstance(Quux.class, "xuuq", 100);
jexl.setProperty(quux, "froboz.value", Integer.valueOf(100));
Object o = jexl.getProperty(quux, "froboz.value");
assertEquals("Result is not 100", new Integer(100), o);
jexl.setProperty(quux, "['froboz'].value", Integer.valueOf(1000));
o = jexl.getProperty(quux, "['froboz']['value']");
assertEquals("Result is not 1000", new Integer(1000), o);
表达式和脚本
如果您的需求需要简单的表达式评估功能,核心JEXL功能很可能适合。主要方法是:
- JexlEngine.createScript(org.apache.commons.jexl3.JexlInfo, java.lang.String, java.lang.String[])
- JexlScript.execute(org.apache.commons.jexl3.JexlContext)
- JexlEngine.createExpression(org.apache.commons.jexl3.JexlInfo, java.lang.String)
- JexlExpression.evaluate(org.apache.commons.jexl3.JexlContext)
以下示例说明了它们的用法:
JexlEngine jexl = new JexlBuilder().create();
JexlContext jc = new MapContext();
jc.set("quuxClass", quux.class);
JexlExpression create = jexl.createExpression("quux = new(quuxClass, 'xuuq', 100)");
JelxExpression assign = jexl.createExpression("quux.froboz.value = 10");
JexlExpression check = jexl.createExpression("quux[\"froboz\"].value");
Quux quux = (Quux) create.evaluate(jc);
Object o = assign.evaluate(jc);
assertEquals("Result is not 10", new Integer(10), o);
o = check.evaluate(jc);
assertEquals("Result is not 10", new Integer(10), o);
统一表达式和模板
如果您正在寻找类似JSP-EL和基本模板功能,可以使用JxltEngine中的Expression。
主要方法是:
- JxltEngine.createExpression(java.lang.String)
- JxltEngine.Expression.prepare(org.apache.commons.jexl3.JexlContext)
- JxltEngine.Expression.evaluate(org.apache.commons.jexl3.JexlContext)
- JxltEngine.createTemplate(org.apache.commons.jexl3.JexlInfo, java.lang.String, java.io.Reader, java.lang.String...)
- JxltEngine.Template.prepare(org.apache.commons.jexl3.JexlContext)
- JxltEngine.Template.evaluate(org.apache.commons.jexl3.JexlContext, java.io.Writer)
以下示例说明了它们的用法:
JexlEngine jexl = new JexlBuilder().create();
JxltEngine jxlt = jexl.createJxltEngine();
JxltEngine.Expression expr = jxlt.createExpression("Hello ${user}");
String hello = expr.evaluate(context).toString();
JexlExpression,JexlScript,表达式和模板:摘要
JexlExpression
这些是JexlEngine表达式的最基本形式,只允许执行单个命令并返回其结果。如果您尝试使用多个命令,它会忽略第一个分号后的所有内容,只返回第一个命令的结果。
还要注意表达式不是语句(这是脚本的组成),并且不允许使用流控制(if,while,for),变量或lambdas语法元素。
JexlScript
这些允许您使用多个语句,您可以使用变量赋值,循环,计算等。或多或少可以在Shell或JavaScript的基本级别实现。从脚本返回最后一个命令的结果。
JxltEngine.Expression
这些是生成“单行”文本的理想选择,就像toString()一样。要获得计算,请使用类似EL的语法,如$ {someVariable}。括号之间的表达式行为类似于JexlScript,而不是表达式。您可以使用分号来执行多个命令,并从脚本返回最后一个命令的结果。您还可以使用#{someScript}语法使用2-pass评估。
JxltEngine.Template
这些产生了文本文档。以'$$'开头的每一行(作为默认值)被视为JEXL代码,所有其他行被视为JxltEngine.Expression。可以将它们视为简单的Velocity模板。重写的MudStore初始Velocity示例如下所示:
<html>
<body>
Hello ${customer.name}!
<table>
$$ for(var mud : mudsOnSpecial ) {
$$ if (customer.hasPurchased(mud) ) {
<tr>
<td>
${flogger.getPromo( mud )}
</td>
</tr>
$$ }
$$ }
</table>
</body>
</html>
JEXL配置
JexlEngine可以通过一些参数进行配置,这些参数将驱动它在出现错误时的反应方式。这些配置方法是通过一个嵌入的JexlBuilder。
静态和共享配置
JexlEngine和JxltEngine都是线程安全的,他们的大部分内部领域都是最终的; 可以在不同线程之间共享相同的实例,并在关键区域(内省缓存)中实施适当的同步。
特别重要的是JexlBuilder.loader,它指示JexlEngine正在构建哪个类加载器用于解决类名; 这直接影响JexlEngine.newInstance和'new'脚本方法的运行方式。
在您依赖JEXL动态加载和调用应用程序插件的情况下,这也非常有用。为了避免在插件实现更改时重新启动服务器,您可以调用 JexlEngine.setClassLoader(java.lang.ClassLoader),通过此引擎实例创建的所有脚本将自动指向新加载的类。
您可以通过NoJexl 完全屏蔽JEXL内省的类和方法的注释来说明可以通过脚本操作的内容。限制JEXL的另一种可配置方式是使用一个 JexlSandbox允许更好地控制暴露内容的方法; 沙箱可以通过JexlBuilder.sandbox。
JexlBuilder.namespaces 通过将您自己的类注册为名称空间来扩展JEXL脚本,允许您自己的函数随意公开。
这可以用作:
public static MyMath {
public double cos(double x) {
return Math.cos(x);
}
}
Map<String, Object> funcs = new HashMap<String, Object>();
funcs.put("math", new MyMath());
JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();
JexlContext jc = new MapContext();
jc.set("pi", Math.PI);
JexlExpression e = JEXL.createExpression("math:cos(pi)");
o = e.evaluate(jc);
assertEquals(Double.valueOf(-1),o);
如果命名空间是一个Class,并且该类声明了一个带有JexlContext(或扩展JexlContext的类)的构造函数,则在表达式中首次使用时会创建一个命名空间实例; 此实例生命周期仅限于表达式评估。
也可以配置JexlEngine和JxltEngine表达式缓存。如果您打算在应用程序中重复使用JEXL,那么这些值得配置,因为表达式解析非常繁重。请注意,JEXL创建的所有缓存都是通过SoftReference保存的; 在高内存压力下,GC将能够回收这些缓存,如果需要,JEXL将重建它们。默认情况下,JexlEngine会为“小”表达式创建缓存,而JxltEngine会为Expression创建一个缓存。
JexlBuilder.cache将设置JEXL引擎可以同时缓存多少个表达式。JxltEngine允许通过其构造函数定义缓存大小。
JexlBuilder.debug 使得JExlException携带的堆栈跟踪更有意义; 特别是,这些跟踪将携带表达式创建的确切调用者位置。
动态配置
这些配置选项可以在评估期间通过实现JexlContext 实现JexlEngine.Options带有评估选项的实现来覆盖 。测试包中存在这样的类的示例。
JexlBuilder.strict或者JexlEngine.Options.isStrict() 在JEXL在各种情况下将'null'视为错误时进行配置; 当面对一个不可引用的变量时,使用null作为算术运算符的参数或者无法调用方法或构造函数。宽松模式接近JEXL-1.1行为。
JexlBuilder.silent或JexlEngine.Options.isSilent() 配置JEXL如何对错误做出反应; 如果是静默的,引擎将不会抛出异常但会通过记录器发出警告并在出现错误时返回null。请注意,当非静默时,JEXL会抛出JexlException,这是未经检查的异常。
JexlContext.NamespaceResolver通过JexlContext 实现- 以 JexlEvalContext 为例 - 允许覆盖命名空间解析和通过定义的默认命名空间映射JexlBuilder.namespaces。
JEXL定制
这是JexlContext,JexlBuilder并且 JexlEngine.Options是您希望实现自定义的最可能的接口。由于它们暴露变量和选项,因此它们是主要目标。在此之前,请查看测试目录中的JexlEvalContext,ObjectContext这可能已经涵盖了您的一些需求。
JexlArithmetic 如果您需要更改运算符的行为方式或添加运算类型,则派生类。有3个入口点可以自定义创建的对象类型:
- array literals: JexlArithmetic.arrayBuilder(int)
- map literals: JexlArithmetic.mapBuilder(int)
- set literals: JexlArithmetic.setBuilder(int)
- range objects: JexlArithmetic.createRange(java.lang.Object, java.lang.Object)
您还可以重载运算符方法; 按照惯例,每个运算符都有一个与之关联的方法名称。如果在JexlArithmetic派生实现中重载某些方法,则当参数与方法签名匹配时,将调用这些方法。例如,如果你想要'+'来操作数组,就会出现这种情况; 你需要派生JexlArithmetic并实现'public Object add(Set x,Set y)'方法。但请注意,您无法更改运算符优先级。运算符/方法匹配列表如下:
Operator | Method Name | Example |
---|---|---|
+ | add | add(x, y) |
- | subtract | subtract(x, y) |
* | multiply | multiply(x, y) |
/ | divide | divide(x, y) |
% | mod | mod(x, y) |
& | bitwiseAnd | bitwiseAnd(x, y) |
I | bitwiseOr | bitwiseOr(x, y) |
^ | bitwiseXor | bitwiseXor(x, y) |
! | logicalNot | logicalNot(x) |
- | bitwiseComplement | bitiwiseComplement(x) |
== | equals | equals(x, y) |
< | lessThan | lessThan(x, y) |
<= | lessThanOrEqual | lessThanOrEqual(x, y) |
> | greaterThan | greaterThan(x, y) |
>= | greaterThanOrEqual | greaterThanOrEqual(x, y) |
- | negate | negate(x) |
size | size | size(x) |
empty | empty | empty(x) |
您还可以添加方法来重载属性getter和setter操作符行为。名为propertyGet / propertySet / arrayGet / arraySet的JexlArithmetic实例的公共方法是在适当时调用的潜在替代。下表概述了语法形式与调用方法之间的关系,其中V是属性值类,O是对象类,P是属性标识符类(通常是String或Integer)。
Expression | Method Template |
---|---|
foo.property | public V propertyGet(O obj, P property); |
foo.property = value | public V propertySet(O obj, P property, V value); |
foo[property] | public V arrayGet(O obj, P property, V value); |
foo[property] = value | public V arraySet(O obj, P property, V value); |
您还可以覆盖基本运算符方法,其参数为Object的方法可以为您提供完全控制。
扩展JEXL
如果您需要使JEXL以特殊方式处理某些对象或调整它对某些设置的反应,您可以获得其大部分内部工作。这些类和方法很少是私有的或最终的 - 只有当内部合同真正需要它时。但是,使用受保护的方法和内部包类意味着在发布新的JEXL版本时,可能必须重新调整代码。
Engine可以扩展为允许您捕获自己的配置默认值wrt缓存大小和各种标志。实现自己的缓存 - 而不是基于LinkedHashMap的基本缓存 - 将是另一种可能的扩展。
Interpreter 如果您需要向评估本身添加更多功能,那么该类是派生的类; 例如,您希望变量的前置和后置解析器或变量上下文的嵌套作用域。
Uberspect 如果需要为某些对象添加内省或反射功能,则可以派生类,例如将基于工厂的支持添加到“new”运算符。代码已经将公共字段反映为Java-beans约定之上的属性。