誰更快?Spring Boot 七大表達(dá)式引擎,哪個(gè)更適合你
環(huán)境:SpringBoot3.4.2
1. 簡(jiǎn)介
在企業(yè)級(jí)應(yīng)用開發(fā)中,動(dòng)態(tài)表達(dá)式解析已成為實(shí)現(xiàn)靈活業(yè)務(wù)邏輯的關(guān)鍵技術(shù)。無論是規(guī)則引擎、動(dòng)態(tài)配置、公式計(jì)算,還是權(quán)限控制,表達(dá)式引擎都能大幅提升系統(tǒng)的可擴(kuò)展性和可維護(hù)性。
Spring Boot 作為 Java 生態(tài)中最流行的開發(fā)框架,支持多種表達(dá)式引擎,如 SpEL(Spring Expression Language)、MVEL、Aviator 等,每種引擎在性能、語法復(fù)雜度、適用場(chǎng)景上各有優(yōu)劣。例如:
- SpEL 深度集成 Spring,適合安全要求高的場(chǎng)景
- MVEL 支持完整腳本,適合復(fù)雜規(guī)則引擎
- Aviator 專注高性能計(jì)算,適合金融、大數(shù)據(jù)分析
- JEP 擅長(zhǎng)數(shù)學(xué)運(yùn)算,適用于科學(xué)計(jì)算
- OGNL 在 MyBatis 等框架中仍有應(yīng)用,但需注意安全風(fēng)險(xiǎn)
- JEXL 語法簡(jiǎn)單,適合輕量級(jí)動(dòng)態(tài)配置
- QLExpress 適合各種復(fù)雜規(guī)則解析運(yùn)算
本文將從性能、安全性、語法易用性等維度,結(jié)合完整代碼示例,深入對(duì)比六大引擎,幫助開發(fā)者選型并優(yōu)化業(yè)務(wù)邏輯! ??
2.實(shí)戰(zhàn)案例
2.1 SpEL表達(dá)式
SpEL(Spring Expression Language)是Spring框架的強(qiáng)大表達(dá)式語言,支持在運(yùn)行時(shí)查詢和操作對(duì)象圖。它語法簡(jiǎn)潔靈活,可進(jìn)行屬性訪問、方法調(diào)用、算術(shù)運(yùn)算、集合操作等,廣泛應(yīng)用于配置管理、條件注入等場(chǎng)景,提升開發(fā)效率。如下代碼示例:
@RestController
public class SpELController {
@GetMapping("/spel")
public String evaluate() {
ExpressionParser parser = new SpelExpressionParser();
// 基本運(yùn)算
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 結(jié)果:<br/>
基本運(yùn)算: 2*3+5 = %d<br/>
字符串操作: 'SPEL'.toLowerCase() = %s<br/>
集合過濾: {1,2,3,4}.?[#this > 2] = %s<br/>
""", result1, result2, result3);
}
}
運(yùn)行結(jié)果:
圖片
優(yōu)點(diǎn):
- 語法靈活,能靈活查詢操作對(duì)象圖,滿足多樣需求;
- 可解耦合,使邏輯與實(shí)現(xiàn)分離,提升代碼可維護(hù)性;
- 具備動(dòng)態(tài)性,運(yùn)行時(shí)動(dòng)態(tài)評(píng)估表達(dá)式,適應(yīng)不同條件;
- 還提供統(tǒng)一語法,降低學(xué)習(xí)成本,功能也豐富多樣。
缺點(diǎn):
- 學(xué)習(xí)有一定曲線,新手需要點(diǎn)時(shí)間掌握;
- 運(yùn)行時(shí)動(dòng)態(tài)計(jì)算可能帶來性能開銷,影響高性能應(yīng)用;
- 且若處理不當(dāng),存在安全風(fēng)險(xiǎn),如惡意表達(dá)式可能引發(fā)任意命令執(zhí)行等安全問題,使用時(shí)需謹(jǐn)慎。
2.2 MVEL
MVEL(MVFLEX Expression Language)是一種基于Java的輕量級(jí)表達(dá)式語言,語法簡(jiǎn)潔易讀,支持算術(shù)、邏輯、字符串操作等。它可與Java無縫集成,動(dòng)態(tài)解析執(zhí)行表達(dá)式,適用于規(guī)則引擎、模板引擎等場(chǎng)景,能提升開發(fā)效率與代碼靈活性。
引入依賴:
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.5.2.Final</version>
</dependency>
如下示例:
@RestController
public class MVELController {
@GetMapping("/mvel")
public String evaluate() {
// 編譯表達(dá)式提高性能
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);
// 復(fù)雜邏輯
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 表達(dá)式進(jìn)行過濾并求和
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 結(jié)果:<br/>
基本運(yùn)算: a*b+c = %d<br/>
條件邏輯: if (x>y) x*2 else y*3 = %d<br/>
集合操作: 偶數(shù)和 = %d
""", result1, result2, result3);
}
}
運(yùn)行結(jié)果:
圖片
優(yōu)點(diǎn):
語法簡(jiǎn)潔、集成方便、執(zhí)行效率較高,適合用于規(guī)則引擎、動(dòng)態(tài)配置等場(chǎng)景。它支持類型推斷、集合操作和部分函數(shù)式編程特性,與 Spring 等框架有良好集成。
缺點(diǎn):
它并不完全支持 Java 語法,特別是對(duì) Stream API 不兼容?,容易造成誤解和使用障礙。對(duì)復(fù)雜邏輯支持較弱,調(diào)試和錯(cuò)誤提示不夠友好。
2.3 Aviator
Aviator是一個(gè)高性能、輕量級(jí)的Java表達(dá)式求值引擎。它支持?jǐn)?shù)學(xué)運(yùn)算、邏輯運(yùn)算等,能將表達(dá)式編譯成Java字節(jié)碼執(zhí)行,性能優(yōu)越。還提供簡(jiǎn)潔API與豐富語法,支持自定義函數(shù),可靈活集成到Java項(xiàng)目中,滿足動(dòng)態(tài)計(jì)算等需求。
引入依賴:
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.4.3</version>
</dependency>
如下示例:
@RestController
public class AviatorController {
@GetMapping("/aviator")
public String evaluate() {
// 基本運(yùn)算
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);
// 大數(shù)運(yùn)算
String result3 = AviatorEvaluator.execute("999999999999999999 * 888888888888888888").toString();
// 自定義函數(shù)
AviatorEvaluator.addFunction(new CustomFunction());
Double result4 = (Double) AviatorEvaluator.execute("square_root(144)");
return String.format("""
Aviator 結(jié)果:<br/>
基本運(yùn)算: 2^10 + sqrt(100) = %s<br/>
變量使用: %s<br/>
大數(shù)運(yùn)算: 999... * 888... = %s<br/>
自定義函數(shù): 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));
}
}
}
運(yùn)行結(jié)果:
圖片
優(yōu)點(diǎn):
- 高性能,它直接將表達(dá)式編譯成Java字節(jié)碼交給JVM執(zhí)行
- 易用性佳,提供簡(jiǎn)潔API和豐富語法
- 可擴(kuò)展性強(qiáng),支持自定義函數(shù)和操作符
- 還具備運(yùn)行時(shí)安全性。
缺點(diǎn):
- 語法受限,不是完整語言,沒有if else、do while等語句和賦值語句
- 對(duì)日期類型支持不足,需將日期寫成特定格式字符串比較
- 且不支持八進(jìn)制數(shù)字字面量,僅支持十進(jìn)制和十六進(jìn)制數(shù)字字面量
2.4 JEP
JEP是Java Expression Parser(Java表達(dá)式分析器)的簡(jiǎn)稱,是用于轉(zhuǎn)換和計(jì)算數(shù)學(xué)表達(dá)式的Java庫,支持自定義變量、常量和函數(shù),能快速求值。
引入依賴:
<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();
// 基本數(shù)學(xué)運(yùn)算
jep.parseExpression("sin(pi/2) + log(100)") ;
double result1 = jep.getValue();
// 多變量計(jì)算
jep.addVariable("x", 3);
jep.addVariable("y", 4);
jep.parseExpression("x^2 + y^2");
double result2 = jep.getValue();
return String.format("""
JEP 結(jié)果:<br/>
三角函數(shù): sin(π/2) + log(100) = %.1f<br/>
多變量: x2 + y2 = %.1f
""", result1, result2);
}
}
運(yùn)行結(jié)果:
圖片
JEP非常使用數(shù)學(xué)運(yùn)算,詳細(xì)可以查看上面 addStandardFunctions 方法中添加的默認(rèn)函數(shù)功能。
2.5 Ognl
OGNL 是一種功能強(qiáng)大的表達(dá)式語言,用于訪問和操作 Java 對(duì)象圖。它支持屬性導(dǎo)航、方法調(diào)用、集合投影/過濾、Lambda 表達(dá)式等,廣泛應(yīng)用于 Struts2 等框架。通過簡(jiǎn)潔語法(如 obj.property 或 collection.{property}),可高效操作復(fù)雜對(duì)象結(jié)構(gòu),簡(jiǎn)化數(shù)據(jù)綁定與邏輯處理。
引入依賴:
<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"));
// 對(duì)象圖導(dǎo)航
String city = (String) Ognl.getValue("address.city", user);
// 方法調(diào)用
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表達(dá)式進(jìn)行投影操作,獲取所有人的年齡
String expression = "#users.{name}";
Object names = Ognl.getValue(expression, context, context.getRoot());
return String.format("""
OGNL 結(jié)果:<br/>
對(duì)象導(dǎo)航: address.city = %s<br/>
方法調(diào)用: name.toUpperCase() = %s<br/>
集合投影: users.{name} = %s
""", city, upperName, names);
}
}
運(yùn)行結(jié)果:
圖片
優(yōu)點(diǎn):
- 語法簡(jiǎn)潔靈活,能以少量代碼實(shí)現(xiàn)復(fù)雜操作,降低開發(fā)成本
- 支持對(duì)象方法調(diào)用、靜態(tài)方法調(diào)用和值訪問,還能操作集合對(duì)象,功能強(qiáng)大
- 可訪問OGNL上下文和ActionContext,方便數(shù)據(jù)交互
- 能對(duì)集合進(jìn)行過濾和投影等操作,提升數(shù)據(jù)處理能力
缺點(diǎn):
- 解析和執(zhí)行需消耗一定時(shí)間和資源,可能影響應(yīng)用性能
- 存在安全風(fēng)險(xiǎn),如注入攻擊、數(shù)據(jù)竊取等
- 在復(fù)雜表達(dá)式中,代碼可維護(hù)性會(huì)降低,且其表達(dá)式計(jì)算圍繞上下文進(jìn)行,使用不當(dāng)易出錯(cuò)
2.6 JEXL
JEXL 實(shí)現(xiàn)了一種基于 JSTL(JavaServer Pages Standard Tag Library)表達(dá)式語言擴(kuò)展的表達(dá)式語言,支持 shell 腳本或 ECMAScript 中常見的大部分結(jié)構(gòu)。其目標(biāo)是向在企業(yè)平臺(tái)上工作的技術(shù)人員或顧問暴露可用的腳本功能。在許多使用場(chǎng)景中,JEXL 允許應(yīng)用程序的最終用戶編寫自己的腳本或表達(dá)式,并確保這些腳本或表達(dá)式在受控的功能約束范圍內(nèi)執(zhí)行。
引入依賴:
<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();
// 簡(jiǎn)單表達(dá)式
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"
// 循環(huán)操作
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 結(jié)果:<br/>
字符串拼接: %s<br/>
條件判斷: %s<br/>
循環(huán)求和: %d
""", result1, result2, result3);
}
}
運(yùn)行結(jié)果:
圖片
優(yōu)點(diǎn):
- 語法簡(jiǎn)潔直觀,易于學(xué)習(xí)和使用,能降低開發(fā)門檻
- 與Java集成良好,可輕松與Java對(duì)象交互,利用反射機(jī)制訪問類和方法
- 靈活性高,支持自定義函數(shù)、變量和操作符,滿足多樣化需求
- 提供安全沙箱機(jī)制,限制腳本對(duì)系統(tǒng)資源的訪問,增強(qiáng)安全性
- 具有跨平臺(tái)特性,可在不同操作系統(tǒng)和硬件架構(gòu)上運(yùn)行
缺點(diǎn):
- 缺少規(guī)則管理功能,如規(guī)則編輯、版本控制等需自行實(shí)現(xiàn)或借助其他工具
- 執(zhí)行效率相對(duì)較低,雖可通過動(dòng)態(tài)編譯技術(shù)提升,但會(huì)增加額外轉(zhuǎn)換步驟