誰更快?Spring Boot 七大表達式引擎,哪個更適合你
環境:SpringBoot3.4.2
1. 簡介
在企業級應用開發中,動態表達式解析已成為實現靈活業務邏輯的關鍵技術。無論是規則引擎、動態配置、公式計算,還是權限控制,表達式引擎都能大幅提升系統的可擴展性和可維護性。
Spring Boot 作為 Java 生態中最流行的開發框架,支持多種表達式引擎,如 SpEL(Spring Expression Language)、MVEL、Aviator 等,每種引擎在性能、語法復雜度、適用場景上各有優劣。例如:
- SpEL 深度集成 Spring,適合安全要求高的場景
- MVEL 支持完整腳本,適合復雜規則引擎
- Aviator 專注高性能計算,適合金融、大數據分析
- JEP 擅長數學運算,適用于科學計算
- OGNL 在 MyBatis 等框架中仍有應用,但需注意安全風險
- JEXL 語法簡單,適合輕量級動態配置
- QLExpress 適合各種復雜規則解析運算
本文將從性能、安全性、語法易用性等維度,結合完整代碼示例,深入對比六大引擎,幫助開發者選型并優化業務邏輯! ??
2.實戰案例
2.1 SpEL表達式
SpEL(Spring Expression Language)是Spring框架的強大表達式語言,支持在運行時查詢和操作對象圖。它語法簡潔靈活,可進行屬性訪問、方法調用、算術運算、集合操作等,廣泛應用于配置管理、條件注入等場景,提升開發效率。如下代碼示例:
@RestController
public class SpELController {
@GetMapping("/spel")
public String evaluate() {
ExpressionParser parser = new SpelExpressionParser();
// 基本運算
Expression exp1 = parser.parseExpression("2 * 3 + 5");
int result1 = exp1.getValue(Integer.class);
// 字符串操作
Expression exp2 = parser.parseExpression("'SPEL'.toLowerCase()");
String result2 = exp2.getValue(String.class);
// 集合操作
Expression exp3 = parser.parseExpression("{1,2,3,4}.?[#this > 2]");
Object result3 = exp3.getValue();
return String.format("""
SpEL 結果:<br/>
基本運算: 2*3+5 = %d<br/>
字符串操作: 'SPEL'.toLowerCase() = %s<br/>
集合過濾: {1,2,3,4}.?[#this > 2] = %s<br/>
""", result1, result2, result3);
}
}
運行結果:
圖片
優點:
- 語法靈活,能靈活查詢操作對象圖,滿足多樣需求;
- 可解耦合,使邏輯與實現分離,提升代碼可維護性;
- 具備動態性,運行時動態評估表達式,適應不同條件;
- 還提供統一語法,降低學習成本,功能也豐富多樣。
缺點:
- 學習有一定曲線,新手需要點時間掌握;
- 運行時動態計算可能帶來性能開銷,影響高性能應用;
- 且若處理不當,存在安全風險,如惡意表達式可能引發任意命令執行等安全問題,使用時需謹慎。
2.2 MVEL
MVEL(MVFLEX Expression Language)是一種基于Java的輕量級表達式語言,語法簡潔易讀,支持算術、邏輯、字符串操作等。它可與Java無縫集成,動態解析執行表達式,適用于規則引擎、模板引擎等場景,能提升開發效率與代碼靈活性。
引入依賴:
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.5.2.Final</version>
</dependency>
如下示例:
@RestController
public class MVELController {
@GetMapping("/mvel")
public String evaluate() {
// 編譯表達式提高性能
Serializable exp1 = MVEL.compileExpression("a * b + c");
Map<String, Object> vars = new HashMap<>();
vars.put("a", 5);
vars.put("b", 3);
vars.put("c", 2);
int result1 = MVEL.executeExpression(exp1, vars, Integer.class);
// 復雜邏輯
String exp2 = "if (x > y) { x * 2 } else { y * 3 }";
vars.put("x", 8);
vars.put("y", 10);
int result2 = MVEL.eval(exp2, vars, Integer.class);
// 集合操作
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Map<String, Object> vars1 = new HashMap<>();
vars1.put("list", list);
// 使用 MVEL 表達式進行過濾并求和
String exp = "sum = 0; for (n : list) { if (n % 2 == 0) sum += n }; sum";
Integer result3 = (Integer) MVEL.eval(exp, vars1);
return String.format("""
MVEL 結果:<br/>
基本運算: a*b+c = %d<br/>
條件邏輯: if (x>y) x*2 else y*3 = %d<br/>
集合操作: 偶數和 = %d
""", result1, result2, result3);
}
}
運行結果:
圖片
優點:
語法簡潔、集成方便、執行效率較高,適合用于規則引擎、動態配置等場景。它支持類型推斷、集合操作和部分函數式編程特性,與 Spring 等框架有良好集成。
缺點:
它并不完全支持 Java 語法,特別是對 Stream API 不兼容?,容易造成誤解和使用障礙。對復雜邏輯支持較弱,調試和錯誤提示不夠友好。
2.3 Aviator
Aviator是一個高性能、輕量級的Java表達式求值引擎。它支持數學運算、邏輯運算等,能將表達式編譯成Java字節碼執行,性能優越。還提供簡潔API與豐富語法,支持自定義函數,可靈活集成到Java項目中,滿足動態計算等需求。
引入依賴:
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.4.3</version>
</dependency>
如下示例:
@RestController
public class AviatorController {
@GetMapping("/aviator")
public String evaluate() {
// 基本運算
Double result1 = (Double) AviatorEvaluator.execute("2 ** 10 + math.sqrt(100)");
// 使用變量
Map<String, Object> env = new HashMap<>();
env.put("name", "Aviator");
env.put("score", 95.5);
String result2 = (String) AviatorEvaluator.execute("name + ' score: ' + score", env);
// 大數運算
String result3 = AviatorEvaluator.execute("999999999999999999 * 888888888888888888").toString();
// 自定義函數
AviatorEvaluator.addFunction(new CustomFunction());
Double result4 = (Double) AviatorEvaluator.execute("square_root(144)");
return String.format("""
Aviator 結果:<br/>
基本運算: 2^10 + sqrt(100) = %s<br/>
變量使用: %s<br/>
大數運算: 999... * 888... = %s<br/>
自定義函數: square_root(144) = %s
""", result1, result2, result3, result4);
}
static class CustomFunction extends com.googlecode.aviator.runtime.function.AbstractFunction {
private static final long serialVersionUID = 1L;
@Override
public String getName() {
return "square_root";
}
@Override
public com.googlecode.aviator.runtime.type.AviatorObject call(Map<String, Object> env,
com.googlecode.aviator.runtime.type.AviatorObject arg1) {
Long num = (Long) arg1.getValue(env);
return new com.googlecode.aviator.runtime.type.AviatorDouble(Math.sqrt(num));
}
}
}
運行結果:
圖片
優點:
- 高性能,它直接將表達式編譯成Java字節碼交給JVM執行
- 易用性佳,提供簡潔API和豐富語法
- 可擴展性強,支持自定義函數和操作符
- 還具備運行時安全性。
缺點:
- 語法受限,不是完整語言,沒有if else、do while等語句和賦值語句
- 對日期類型支持不足,需將日期寫成特定格式字符串比較
- 且不支持八進制數字字面量,僅支持十進制和十六進制數字字面量
2.4 JEP
JEP是Java Expression Parser(Java表達式分析器)的簡稱,是用于轉換和計算數學表達式的Java庫,支持自定義變量、常量和函數,能快速求值。
引入依賴:
<dependency>
<groupId>org.scijava</groupId>
<artifactId>jep</artifactId>
<version>2.4.2</version>
</dependency>
代碼示例:
@RestController
public class JEPController {
@GetMapping("/jep")
public String evaluate() {
JEP jep = new JEP() ;
jep.addStandardFunctions();
jep.addStandardConstants();
// 基本數學運算
jep.parseExpression("sin(pi/2) + log(100)") ;
double result1 = jep.getValue();
// 多變量計算
jep.addVariable("x", 3);
jep.addVariable("y", 4);
jep.parseExpression("x^2 + y^2");
double result2 = jep.getValue();
return String.format("""
JEP 結果:<br/>
三角函數: sin(π/2) + log(100) = %.1f<br/>
多變量: x2 + y2 = %.1f
""", result1, result2);
}
}
運行結果:
圖片
JEP非常使用數學運算,詳細可以查看上面 addStandardFunctions 方法中添加的默認函數功能。
2.5 Ognl
OGNL 是一種功能強大的表達式語言,用于訪問和操作 Java 對象圖。它支持屬性導航、方法調用、集合投影/過濾、Lambda 表達式等,廣泛應用于 Struts2 等框架。通過簡潔語法(如 obj.property 或 collection.{property}),可高效操作復雜對象結構,簡化數據綁定與邏輯處理。
引入依賴:
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.4.7</version>
</dependency>
示例代碼:
@RestController
public class OGNLController {
public static class User {
public String name;
public Address address;
public User(String name, Address address) {
this.name = name;
this.address = address;
}
}
public static class Address {
public String city;
public String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
}
@GetMapping("/ognl")
public String evaluate() throws OgnlException {
User user = new User("Tom", new Address("Beijing", "Chang'an Street"));
// 對象圖導航
String city = (String) Ognl.getValue("address.city", user);
// 方法調用
String upperName = (String) Ognl.getValue("name.toUpperCase()", user);
// 集合投影
List<User> users = List.of(
new User("Alice", new Address("Shanghai", "Nanjing Road")),
new User("Bob", new Address("Guangzhou", "Zhongshan Road"))
);
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(users);
context.put("users", users);
// 使用OGNL表達式進行投影操作,獲取所有人的年齡
String expression = "#users.{name}";
Object names = Ognl.getValue(expression, context, context.getRoot());
return String.format("""
OGNL 結果:<br/>
對象導航: address.city = %s<br/>
方法調用: name.toUpperCase() = %s<br/>
集合投影: users.{name} = %s
""", city, upperName, names);
}
}
運行結果:
圖片
優點:
- 語法簡潔靈活,能以少量代碼實現復雜操作,降低開發成本
- 支持對象方法調用、靜態方法調用和值訪問,還能操作集合對象,功能強大
- 可訪問OGNL上下文和ActionContext,方便數據交互
- 能對集合進行過濾和投影等操作,提升數據處理能力
缺點:
- 解析和執行需消耗一定時間和資源,可能影響應用性能
- 存在安全風險,如注入攻擊、數據竊取等
- 在復雜表達式中,代碼可維護性會降低,且其表達式計算圍繞上下文進行,使用不當易出錯
2.6 JEXL
JEXL 實現了一種基于 JSTL(JavaServer Pages Standard Tag Library)表達式語言擴展的表達式語言,支持 shell 腳本或 ECMAScript 中常見的大部分結構。其目標是向在企業平臺上工作的技術人員或顧問暴露可用的腳本功能。在許多使用場景中,JEXL 允許應用程序的最終用戶編寫自己的腳本或表達式,并確保這些腳本或表達式在受控的功能約束范圍內執行。
引入依賴:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.5.0</version>
</dependency>
示例代碼:
@RestController
public class JEXLController {
@GetMapping("/jexl")
public String evaluate() {
JexlEngine jexl = new JexlBuilder().create();
// 簡單表達式
JexlExpression exp1 = jexl.createExpression("'Hello ' + name");
JexlContext context1 = new MapContext();
context1.set("name", "JEXL");
String result1 = exp1.evaluate(context1).toString(); // "Hello JEXL"
// 條件判斷
JexlExpression exp2 = jexl.createExpression("age >= 18 ? 'Adult' : 'Minor'");
JexlContext context2 = new MapContext();
context2.set("age", 20);
String result2 = exp2.evaluate(context2).toString(); // "Adult"
// 循環操作
JexlScript script = jexl.createScript("""
total = 0;
for (n : numbers) {
total += n
}
return total
""");
JexlContext context3 = new MapContext();
context3.set("numbers", new int[] { 1, 2, 3, 4, 5 });
Object result3 = script.execute(context3); // 15
return String.format("""
JEXL 結果:<br/>
字符串拼接: %s<br/>
條件判斷: %s<br/>
循環求和: %d
""", result1, result2, result3);
}
}
運行結果:
圖片
優點:
- 語法簡潔直觀,易于學習和使用,能降低開發門檻
- 與Java集成良好,可輕松與Java對象交互,利用反射機制訪問類和方法
- 靈活性高,支持自定義函數、變量和操作符,滿足多樣化需求
- 提供安全沙箱機制,限制腳本對系統資源的訪問,增強安全性
- 具有跨平臺特性,可在不同操作系統和硬件架構上運行
缺點:
- 缺少規則管理功能,如規則編輯、版本控制等需自行實現或借助其他工具
- 執行效率相對較低,雖可通過動態編譯技術提升,但會增加額外轉換步驟