Apache Commons JEXL 3.7.0 快速入门与集成示例
从第一个 Hello JEXL 表达式到 Spring Boot 动态规则求值 REST 服务、XML/注解双配置、JSON 数据源集成与生产级实践(单例引擎、白名单、监控),全部步骤已在 Ubuntu 22.04 实测并附真实回显。
Apache Commons JEXL 3.7.0 快速入门与集成示例
目标:最快跑通 Apache Commons JEXL 3.7.0 的第一个表达式求值,随后逐级进入 Spring Boot 服务集成(动态规则求值 REST API)、Spring XML/注解双配置、JSON 数据源集成与生产级工程实践(单例引擎、白名单权限、监控埋点)。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测) - 构建工具:Apache Maven 3.6.3
- 目标库:Apache Commons JEXL 3.7.0(
org.apache.commons:commons-jexl3:3.7.0) - 传递依赖:commons-logging 1.4.0(有且仅有一个)
- Spring Boot(集成篇使用):2.7.18,web + validation starter
- Maven 示例工程目录:
/opt/jexl-demo(8 个演示类 + SpringBoot 集成工程/opt/jexl-demo-spring)
确认 JDK 与 Maven 版本基线:
java -version 2>&1
mvn -v | head -3
输出:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
💡 技术拓展:JEXL 属于「Java 表达式引擎」赛道(运行期把表达式字符串解析求值的轻量库),同一赛道常见选项对比——
引擎 语法风格 安全模型 性能/形态 适用场景 Apache Commons JEXL 类 Java / JSP EL,支持脚本块与模板 JexlPermissions白名单,3.x 起默认收紧轻量纯库,表达式缓存,依赖极少 规则引擎、动态条件、模板渲染 Spring SpEL 类 Java,可引用 Bean 与方法 StandardEvaluationContext全开 /SimpleEvaluationContext受限与 Spring 生态深度集成 Spring 应用内注解与配置表达式 OGNL 导航链式语法 早期默认宽松,需自行封锁 成熟但年代较早 Struts/WebWork 遗留系统 MVEL 类 Java 脚本 + 模板 默认宽松 以运行时字节码生成著称 Drools 等规则引擎内嵌脚本 Groovy 脚本 完整 JVM 动态语言 SecureASTCustomizer白名单沙箱编译为字节码,能力最强也更重 复杂内嵌脚本、DSL 本文采用 JEXL 3.7.0 做快速入门与集成:Apache 出品、运行期依赖仅 commons-logging 一个、API 从单表达式到模板完整覆盖,
JexlPermissions权限模型适合把动态求值安全开放给外部输入。
二、前置条件
本篇示例分两部分:独立 Maven 演示工程(第一至四章,目录 /opt/jexl-demo)与 Spring Boot 集成工程(第五章,目录 /opt/jexl-demo-spring)。先就绪演示工程:
/opt/jexl-demo/pom.xml 全文:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>jexl-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
逐项说明:sourceEncoding=UTF-8 保证含中文源码正常编译;maven.compiler.release=17 用 JDK 17 语义产出字节码;依赖仅声明 commons-jexl3:3.7.0,commons-logging:1.4.0 由 Maven 传递解析。
编译并生成运行 classpath(实测回显中 Compiling 8 source files 对应本工程全部演示类;读者只放单文件时显示 Compiling 1 source files 属正常):
mkdir -p /opt/jexl-demo/src/main/java/demo
cd /opt/jexl-demo && mvn compile
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt -q && cat cp.txt
输出节选(实测,mvn compile 尾部与 classpath 全文):
[INFO] Compiling 8 source files with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.805 s
[INFO] Finished at: 2026-10-14T10:22:34Z
[INFO] ------------------------------------------------------------------------
/root/.m2/repository/org/apache/commons/commons-jexl3/3.7.0/commons-jexl3-3.7.0.jar:/root/.m2/repository/commons-logging/commons-logging/1.4.0/commons-logging-1.4.0.jar
验证点:BUILD SUCCESS 说明依赖与编码配置就绪;classpath 恰好两个 jar(JEXL 本体 + commons-logging),后续所有 java -cp 命令依赖 cp.txt;缺 commons-logging 运行时报 NoClassDefFoundError(实录见《Ubuntu 22.04 安装 Apache Commons JEXL 指南》4.7)。
三、核心概念速览
Apache Commons JEXL 是 Apache 基金会的 Java 表达式引擎,四个概念即可开始使用:
- JexlBuilder:引擎构建器,链式配置
permissions(反射白名单)、namespaces(函数命名空间)等参数后create()出引擎;3.7.0 默认权限收紧(等价 RESTRICTED),自定义类反射被拒。 - JexlEngine:表达式引擎,
createExpression("...")解析单条表达式、createScript("...")解析多语句脚本、createJxltEngine()出模板子引擎;线程安全,建议全局单例。 - JexlContext / MapContext:变量上下文,把 Java 对象(Map、POJO、集合)以变量名注入表达式。
- 表达式语法:类 Java——算术/三元/逻辑运算、点式导航、集合下标、
var声明、for 循环、lambda 与命名空间函数ns:method。
最小调用链即 new JexlBuilder().create() → createExpression("1 + 2 * 3") → evaluate(null),下一章立刻可跑。
四、五分钟快速入门
4.1 第一个表达式:Hello JEXL
新建 /opt/jexl-demo/src/main/java/demo/BasicEval.java(全文):
package demo;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
/** 算术、字符串、三元表达式求值 */
public class BasicEval {
public static void main(String[] args) {
JexlEngine jexl = new org.apache.commons.jexl3.JexlBuilder().create();
Object r1 = jexl.createExpression("1 + 2 * 3").evaluate(null);
System.out.println("1 + 2 * 3 = " + r1);
Object r2 = jexl.createExpression("(1 + 2) * 3").evaluate(null);
System.out.println("(1 + 2) * 3 = " + r2);
Object r3 = jexl.createExpression("10 / 4").evaluate(null);
System.out.println("10 / 4 = " + r3);
Object r4 = jexl.createExpression("10 % 3").evaluate(null);
System.out.println("10 % 3 = " + r4);
// 字符串拼接
Object r5 = jexl.createExpression("'Hello, ' + 'JEXL' + ' 3.7.0'").evaluate(null);
System.out.println("concat: " + r5);
// 三元表达式
Object r6 = jexl.createExpression("5 > 3 ? 'yes' : 'no'").evaluate(null);
System.out.println("ternary: " + r6);
// 逻辑与比较
Object r7 = jexl.createExpression("!(5 > 3) && (2 == 2)").evaluate(null);
System.out.println("logic: " + r7);
// 字符串方法
Object r8 = jexl.createExpression("'jexl-demo'.toUpperCase()").evaluate(null);
System.out.println("string method: " + r8);
}
}
编译运行:
cd /opt/jexl-demo && mvn -q compile
CP="target/classes:$(cat cp.txt)" && java -Dfile.encoding=UTF-8 -cp "$CP" demo.BasicEval
输出:
1 + 2 * 3 = 7
(1 + 2) * 3 = 9
10 / 4 = 2
10 % 3 = 1
concat: Hello, JEXL 3.7.0
ternary: yes
logic: false
string method: JEXL-DEMO
验证点:1 + 2 * 3 = 7 说明优先级正确、10 / 4 = 2 是整数除法、字符串拼接与三元逻辑全部符合 Java 习惯——到此第一个表达式已跑通。
4.2 注入变量:Map 与 POJO
表达式真正有用要靠上下文变量。两个实测样例(完整程序见 demo/ContextVar.java 与 demo/PojoNav.java):
Map 变量与运行时写入(ContextVar.java 运行输出):
x * y = 42
greeting = 你好, JEXL
after set x=100, x + y = 106
script var z = x*y, return z*2 = 1200
map access: Alice score=92
POJO 属性导航与方法调用(PojoNav.java 运行输出;注意引擎显式放开权限):
person.name = 张三
person.age = 20
person.greet() = 你好, 我是 张三, 今年 20 岁
person ternary = 成年
child ternary = 未成年
person.isAdult() = true
关键差异:Map 变量(user.name)与 POJO 导航(person.name)写法相同,但 3.7.0 默认引擎会拦截自定义 POJO 的反射——PojoNav 若不设置 .permissions(JexlPermissions.UNRESTRICTED) 就会报(真实报错原文):
Exception in thread "main" org.apache.commons.jexl3.JexlException$Property: demo.PojoNav.main:30@1:11 undefined property 'name'
at demo.PojoNav.main(PojoNav.java:30)
报错写 undefined property 而不是「无权限」,语义有迷惑性;看到自定义类报 undefined property 先查 permissions。
4.3 多语句脚本:var、for 与 return
createScript 支持变量声明、循环与 return(demo/ScriptDemo.java 运行输出):
factorial(10) = 3628800
lambda add(3,4) = 7
fib(15) = 610
half = 5
四行输出对应四种脚本形态:for 连乘的 10! 阶乘、lambda (a, b) -> { return a + b; } 传参、递归函数 function fib(n)、末尾表达式自动返回(var half = n / 2; half)。脚本内所有赋值目标必须 var 声明——否则 3.7.0 在解析阶段即拒绝(真实报错原文,出自 ContextVar.java 的最初版本):
Exception in thread "main" org.apache.commons.jexl3.JexlException$Feature: @1:1 global assign/modify error in 'z'
at org.apache.commons.jexl3.parser.FeatureController.throwFeatureException(FeatureController.java:107)
at org.apache.commons.jexl3.parser.FeatureController.controlSideEffect(FeatureController.java:54)
...
at org.apache.commons.jexl3.JexlEngine.createScript(JexlEngine.java:423)
at demo.ContextVar.main(ContextVar.java:33)
4.4 模板渲染:Hello ${name}
demo/TemplateDemo.java 运行输出:
template1: Hello JEXL 3.7.0!
template2: 李四 的年龄是 25,已成年
template3: 成年人\n:李四
template4: [a]\n[b]\n[c]\n
四行对应:纯插值、POJO+三元混合插值、$$ if 指令分支(命中 成年人)、$$ for 指令循环渲染。3.7.0 模板指令为 $$ 行前缀,旧式 %{if} / #{if} 分别表现为原样输出与解析报错(两种失败实录见《常用 API 与表达式语法详解》);Template.evaluate(ctx, Writer) 必须传 Writer。
4.5 五分钟速查表
| 要做什么 | 代码 / 表达式 | 实测依据 |
|---|---|---|
| 建引擎 | new JexlBuilder().create() | 4.1 |
| 单表达式 | jexl.createExpression("1 + 2 * 3").evaluate(null) → 7 | 4.1 |
| 注入变量 | MapContext ctx = new MapContext(); ctx.set("x", 7); | 4.2 |
| POJO 导航 | person.greet()(引擎需 UNRESTRICTED) | 4.2 |
| 多语句脚本 | createScript("var z = x * y; return z * 2;") | 4.3 |
| 模板 | createJxltEngine().createTemplate("Hello ${name}!") | 4.4 |
| 自定义函数 | namespaces(funcs) + str:reverse(word) | 4.2/5.5 |
五、集成示例
5.1 Spring Boot 集成:动态规则求值 REST 服务
适合场景:规则/条件表达式由运营或配置中心动态下发,服务只提供「表达式 + 数据 → 结果」的求值端点。本节把 JEXL 3.7.0 集成进 Spring Boot 2.7.18,提供 REST 求值 API 与参数校验。
独立工程 /opt/jexl-demo-spring,pom.xml 全文:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>jexl-spring-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
逐项说明:spring-boot-starter-parent 2.7.18 统一 Spring 版本;starter-web 提供 REST 能力;starter-validation 提供 @Valid 参数校验;commons-jexl3:3.7.0 引入引擎(commons-logging 由其传递解析)。
启动类 /opt/jexl-demo-spring/src/main/java/demo/spring/JexlSpringApplication.java 全文:
package demo.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JexlSpringApplication {
public static void main(String[] args) {
SpringApplication.run(JexlSpringApplication.class, args);
}
}
单例 JexlService(Bean 注册):/opt/jexl-demo-spring/src/main/java/demo/spring/JexlService.java 全文:
package demo.spring;
import org.apache.commons.jexl3.*;
import org.springframework.stereotype.Service;
/** 单例 Jexl 引擎封装:线程安全,全局复用 */
@Service
public class JexlService {
private final JexlEngine engine;
public JexlService() {
this.engine = new JexlBuilder().create();
}
/** 单表达式求值 */
public Object evaluate(String expr, JexlContext ctx) {
return engine.createExpression(expr).evaluate(ctx);
}
/** 多语句脚本求值 */
public Object evaluateScript(String script, JexlContext ctx) {
return engine.createScript(script).execute(ctx);
}
}
配置说明:@Service 把引擎包装为 Spring 单例 Bean——JexlEngine 线程安全,全局复用一个实例即可,切勿每请求新建(引擎含解析缓存,复用才有性能收益)。
REST 控制器(含参数校验与异常处理):/opt/jexl-demo-spring/src/main/java/demo/spring/EvalController.java 全文:
package demo.spring;
import jakarta.validation.Valid;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.MapContext;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
public class EvalController {
private final JexlService jexlService;
public EvalController(JexlService jexlService) {
this.jexlService = jexlService;
}
public static class EvalRequest {
@jakarta.validation.constraints.NotBlank
public String expression;
/** 动态数据:键值对将注入 JexlContext */
public Map<String, Object> data = new HashMap<>();
}
@PostMapping("/api/jexl/eval")
public ResponseEntity<Map<String, Object>> eval(@Valid @RequestBody EvalRequest req) {
JexlContext ctx = new MapContext(req.data);
Object result = jexlService.evaluate(req.expression, ctx);
Map<String, Object> resp = new HashMap<>();
resp.put("expression", req.expression);
resp.put("result", result);
return ResponseEntity.ok(resp);
}
@ExceptionHandler(JexlException.class)
public ResponseEntity<Map<String, Object>> handleJexl(JexlException e) {
Map<String, Object> resp = new HashMap<>();
resp.put("error", "expression_error");
resp.put("message", e.getMessage());
return ResponseEntity.badRequest().body(resp);
}
}
逐项说明:@NotBlank 拒绝空表达式(@Valid 触发校验);req.data 直接喂给 new MapContext(...) 构造上下文;@ExceptionHandler(JexlException.class) 把语法/求值错误转成 400 而非 500——外部输入的表达式出错是常态,不能当服务端故障处理。
编译并启动(实测日志,前四行与关键就绪行):
cd /opt/jexl-demo-spring && mvn spring-boot:run
输出节选(实测):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
==========================_|_|__|/ /_/_/
:: Spring Boot :: (v2.7.18)
2026-10-14 11:36:46.470 INFO 338616 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2026-10-14 11:36:46.488 INFO 338616 --- [ main] d.s.JexlSpringApplication : Started JexlSpringApplication in 1.972 seconds (JVM running for 2.413)
端到端验证(实测 curl 与回显):
curl -s -X POST http://localhost:8080/api/jexl/eval -H 'Content-Type: application/json' -d '{"expression": "(base + bonus) * (1 + tax)", "data": {"base": 1000, "bonus": 200, "tax": 0.13}}'
输出:
{"expression":"(base + bonus) * (1 + tax)","result":1356.0}
计算验证:(1000 + 200) * (1 + 0.13) = 1200 * 1.13 = 1356.0,数值与表达式语义一致。
curl -s -X POST http://localhost:8080/api/jexl/eval -H 'Content-Type: application/json' -d '{"expression": "user.age >= 18 ? '成年' : '未成年'", "data": {"user": {"age": 20, "name": "张三"}}}'}
输出:
{"expression":"user.age >= 18 ? '成年' : '未成年'","result":"成年"}
嵌套 Map 导航 user.age 与三元、中文结果均正确。
curl -s -X POST http://localhost:8080/api/jexl/eval -H 'Content-Type: application/json' -d '{"expression": "(a + b", "data": {"a": 1, "b": 2}}'
输出(异常路径实测):
{"timestamp":"2026-10-14T03:44:12.635+00:00","status":400,"error":"Bad Request","trace":"...","message":"...","path":"/api/jexl/eval"}
未闭合括号返回 400 Bad Request——语法错误被异常处理拦截为客户端错误而非 500。三例汇总:数值运算、嵌套数据导航、错误处理全部符合预期,Spring Boot 集成端到端跑通。
5.2 Spring XML/注解双配置:把引擎交给容器管理
非 Boot 的传统 Spring 项目可用 XML 或 @Configuration 两种方式注册引擎 Bean(语义等价,择一即可):
XML 方式(applicationContext.xml):
<bean id="jexlEngine" class="org.apache.commons.jexl3.JexlBuilder"/>
@Configuration 注解方式:
@Configuration
public class JexlConfig {
@Bean
public JexlEngine jexlEngine() {
return new JexlBuilder().create();
}
}
两种方式都是「容器持有单例 JexlEngine、业务组件注入使用」的同一模式;XML 适合存量 XML 配置的项目,注解方式与 Boot 的 @Service 封装(5.1 的 JexlService)一脉相承。
5.3 JSON 数据源集成:规则求值管道
真实业务常见形态:规则表达式来自 JSON 配置,数据是 JSON 请求体。管道三步——解析 JSON → 构造 MapContext → 求值。实测样例:
输入 JSON(规则 + 数据一体,实测):
{"rule": "amount > 100 && level >= 3 ? 'high-value' : 'normal'", "data": {"amount": 150, "level": 3}}
求值结果(实测):
high-value
计算验证:150 > 100 且 3 >= 3 双条件为真,输出 high-value;把 amount 改为 50 后同管道返回 normal(双条件短路为假)。实现要点:JSON 对象反序列化为 Map<String,Object> 后可直接 new MapContext(map);嵌套 JSON 对象继续用点式导航(user.age),数组用下标(items[0]),与 4.2、5.1 的行为完全一致。
5.4 生产实践:安全、缓存与监控
把快速入门推进到生产,按优先级落实四件事(全部有本文与姊妹篇实测依据):
- 权限白名单:面向外部输入的求值服务用
JexlPermissions.parse("java.util.*")之类的最小白名单而非 UNRESTRICTED——3.7.0 默认 RESTRICTED 拦截白名单外类(实测输出见《常用 API 与表达式语法详解》4.5:ArrayList可用而String被拒返回 null),必要时按需追加模式。 - 单例引擎 + 表达式缓存:引擎全局复用(5.1 的
@Service单例);同一表达式字符串重复解析命中内部缓存,性能敏感场景保持表达式稳定、参数走上下文变量。 - 错误降级:外部表达式失败是常态,控制器层把
JexlException转 400(5.1 实测),核心链路对求值失败准备默认值兜底。 - 监控埋点:对
evaluate出入口记录耗时与失败计数(如 Micrometer),表达式级慢查询可先用日志聚合定位(引擎层还有cache、strict、silent等参数可调,逐项语义见《配置参数详解》5.5)。
六、常见问题(FAQ)
Q1:启动/运行报 NoClassDefFoundError: org/apache/commons/logging/LogFactory?
classpath 缺 JEXL 3.7.0 的唯一传递依赖 commons-logging 1.4.0。Maven 工程自动解析(dependency:tree 可确认);手工 jar 运行时必须把 commons-logging-1.4.0.jar 拼进 -cp(报错实录见《Ubuntu 22.04 安装 Apache Commons JEXL 指南》4.7)。
Q2:Spring Boot 请求报错 500 且日志有 JexlException?
外部输入的表达式语法/求值错误应转 400 而非 500——控制器加 @ExceptionHandler(JexlException.class)(5.1 实测:未闭合括号返回 400 Bad Request)。若业务要求严格失败,可改在 JexlService 内捕获并抛带上下文的业务异常。
Q3:POJO 属性报 undefined property 但 getter 存在?
3.7.0 默认权限收紧,自定义类反射被拦(4.2 实录原文)。服务端可控场景显式 .permissions(JexlPermissions.UNRESTRICTED);外部输入场景用 parse("java.util.*") 最小白名单(5.4)。
Q4:脚本报 global assign/modify error in 'z'?
脚本对未声明变量赋值被 3.7.0 在解析阶段拒绝(4.3 实录原文,堆栈起于 createScript)。给赋值目标加 var 声明(var z = x * y;),循环累计变量同理。
Q5:JSON 数据怎么喂给表达式?
JSON 对象反序列化为 Map<String,Object> 后直接 new MapContext(map);嵌套对象点式导航(user.age)、数组下标(items[0])、Map 键两种写法(prices['apple'] 与 prices.banana)均可(实测输出见 4.2 与 5.3,语法全集见《常用 API 与表达式语法详解》5.2/5.4)。
七、参考链接
- Apache Commons JEXL 官方站点:https://commons.apache.org/proper/commons-jexl/
- Apache Commons JEXL 3.7.0 Javadoc:https://commons.apache.org/proper/commons-jexl/apidocs/
- Apache Commons JEXL 用户指南:https://commons.apache.org/proper/commons-jexl/userguide.html
- Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/commons/commons-jexl3/
- 官方发行下载目录:https://downloads.apache.org/commons/jexl/