目錄
一、SpEL是什么?為什么需要它?
核心價值:
典型應用場景:
二、基礎語法快速入門
1. 表達式解析基礎
2. 字面量表示
3. 屬性訪問
三、SpEL核心特性詳解
1. 集合操作
2. 方法調用
3. 運算符大全
4. 類型操作
四、Spring集成實戰
1. XML配置中的SpEL
2. 注解配置中的SpEL
3. Spring Data中的SpEL
五、高級技巧與最佳實踐
1. 自定義函數擴展
2. 模板表達式
3. 安全沙箱限制
六、性能優化指南
1. 表達式編譯
2. 緩存策略
3. 避免性能陷阱
七、SpEL在Spring生態系統中的應用
1. Spring Security
2. Spring Integration
3. Spring Batch
八、總結:SpEL最佳實踐
一、SpEL是什么?為什么需要它?
Spring Expression Language?(SpEL) 是Spring框架3.0引入的動態表達式引擎,它允許在運行時查詢和操作對象圖。與OGNL和MVEL類似,但深度集成Spring生態。
核心價值:
典型應用場景:
Spring XML/注解配置中的動態值
Spring Security的權限表達式
Spring Data的查詢方法
Thymeleaf模板引擎
@Value注解注入
二、基礎語法快速入門
1. 表達式解析基礎
ExpressionParser parser = new SpelExpressionParser();// 數學運算
Expression exp = parser.parseExpression("100 * 2 + 50");
Integer result = (Integer) exp.getValue(); // 250// 字符串操作
exp = parser.parseExpression("'Hello ' + 'World'");
String str = exp.getValue(String.class); // "Hello World"
2. 字面量表示
類型 | 示例 |
---|---|
整型 | 42 ,?0x2A |
浮點型 | 3.14 ,?6.022e23 |
布爾型 | true ,?false |
字符串 | 'Single Quote' |
Null | null |
3. 屬性訪問
public class User {private String name;private Address address;// getters
}// 鏈式屬性訪問
exp = parser.parseExpression("address.city");
String city = exp.getValue(user, String.class); // 獲取user.address.city
三、SpEL核心特性詳解
1. 集合操作
列表/數組訪問:
List<String> list = Arrays.asList("a","b","c");// 索引訪問
exp = parser.parseExpression("[1]");
String elem = exp.getValue(list, String.class); // "b"// 集合投影(返回新集合)
exp = parser.parseExpression("![toUpperCase()]");
List<String> upper = exp.getValue(list, List.class); // ["A","B","C"]
Map訪問:
Map<String, Integer> scores = Map.of("Tom", 90, "Jerry", 85);// Key訪問
exp = parser.parseExpression("['Tom']");
Integer score = exp.getValue(scores, Integer.class); // 90
2. 方法調用
// 調用String方法
exp = parser.parseExpression("'abc'.substring(1, 2)");
String substr = exp.getValue(String.class); // "b"// 調用Bean方法
exp = parser.parseExpression("calculateDiscount(0.1)");
Double discount = exp.getValue(orderService, Double.class);
3. 運算符大全
類別 | 運算符 | 示例 |
---|---|---|
算術 | + ,?- ,?* ,?/ ,?% ,?^ | 2^3 ?→ 8 |
關系 | < ,?> ,?== ,?<= ,?>= ,?!= | price > 100 ? 'high' : 'low' |
邏輯 | and ,?or ,?not | isVip and age > 18 |
正則 | matches | email matches '[a-z]+@domain\\.com' |
三元 | ?: | status ?: 'default' |
Elvis | ?: | name ?: 'Unknown' |
Safe導航 | ?. | user?.address?.city |
4. 類型操作
// 類型判斷
exp = parser.parseExpression("'abc' instanceof T(String)");
Boolean isString = exp.getValue(Boolean.class); // true// 靜態方法調用
exp = parser.parseExpression("T(java.lang.Math).random()");
Double random = exp.getValue(Double.class);// 構造函數
exp = parser.parseExpression("new com.example.User('Alice')");
User user = exp.getValue(User.class);
四、Spring集成實戰
1. XML配置中的SpEL
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"><property name="jdbcUrl" value="#{systemProperties['db.url'] ?: 'jdbc:mysql://localhost:3306/default'}"/><property name="maximumPoolSize" value="#{T(java.lang.Runtime).getRuntime().availableProcessors() * 2}"/>
</bean>
2. 注解配置中的SpEL
@Value("#{systemEnvironment['APP_HOME']}")
private String appHome;@Scheduled(fixedDelay = #{@configService.getInterval() * 1000})
public void scheduledTask() {// 定時任務
}@PreAuthorize("hasRole('ADMIN') or #user.id == authentication.principal.id")
public void updateUser(User user) {// 方法安全控制
}
3. Spring Data中的SpEL
// 查詢方法中的SpEL
public interface UserRepository extends JpaRepository<User, Long> {@Query("SELECT u FROM User u WHERE u.status = :#{#status ?: 'ACTIVE'}")List<User> findByStatus(@Param("status") String status);
}// 實體中的默認值
@Entity
public class Article {@Id@GeneratedValueprivate Long id;@Columnprivate Date publishDate;@Transient@Value("#{T(java.time.LocalDate).now()}")private LocalDate currentDate;
}
五、高級技巧與最佳實踐
1. 自定義函數擴展
public class StringUtils {public static String reverse(String input) {return new StringBuilder(input).reverse().toString();}
}// 注冊自定義函數
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverse", StringUtils.class.getDeclaredMethod("reverse", String.class));// 使用自定義函數
Expression exp = parser.parseExpression("#reverse('hello')");
String result = exp.getValue(context, String.class); // "olleh"
2. 模板表達式
ParserContext templateContext = new TemplateParserContext();// 解析模板
String template = "用戶#{#user.name}的余額是#{#account.balance}";
Expression exp = parser.parseExpression(template, templateContext);// 設置上下文變量
context.setVariable("user", currentUser);
context.setVariable("account", userAccount);String message = exp.getValue(context, String.class);
3. 安全沙箱限制
// 創建安全上下文
SecurityManager securityManager = new SecurityManager();
securityManager.setRestrict(true);
securityManager.setAllowedTypes(Collections.singleton(String.class));StandardEvaluationContext context = new StandardEvaluationContext();
context.setSecurityManager(securityManager);// 嘗試執行危險操作(將被阻止)
try {Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('rm -rf /')");exp.getValue(context); // 拋出SecurityException
} catch (EvaluationException e) {System.err.println("危險操作被阻止!");
}
六、性能優化指南
1. 表達式編譯
// 解析并編譯表達式(提升10倍性能)
SpelCompiler compiler = SpelCompiler.getCompiler();
Expression compiledExp = compiler.compile(parser.parseExpression("amount * taxRate"));// 重復使用編譯后的表達式
for (Order order : orders) {Double tax = compiledExp.getValue(order, Double.class);
}
2. 緩存策略
// 使用ConcurrentHashMap緩存編譯后表達式
private static final Map<String, Expression> EXPR_CACHE = new ConcurrentHashMap<>();public Object evaluate(String exprStr, Object root) {Expression expr = EXPR_CACHE.computeIfAbsent(exprStr, key -> parser.parseExpression(key));return expr.getValue(root);
}
3. 避免性能陷阱
// 錯誤示例:每次解析新表達式
@Scheduled(fixedRate = 5000)
public void process() {Expression exp = parser.parseExpression(ruleEngine.getRule()); // 頻繁解析exp.getValue(context);
}// 正確方案:預編譯+緩存
private final Map<String, Expression> ruleCache = new ConcurrentHashMap<>();public void process() {Expression exp = ruleCache.computeIfAbsent(ruleEngine.getRule(), key -> parser.parseExpression(key));exp.getValue(context);
}
七、SpEL在Spring生態系統中的應用
1. Spring Security
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {// 方法級安全控制
}public interface BankService {@PreAuthorize("hasPermission(#accountId, 'ACCOUNT', 'WRITE')")void withdraw(Long accountId, BigDecimal amount);
}
2. Spring Integration
<int:router input-channel="input" expression="headers['type']"><int:mapping value="order" channel="orderChannel"/><int:mapping value="payment" channel="paymentChannel"/>
</int:router>
3. Spring Batch
<batch:job id="importJob"><batch:step id="processFile" next="#{jobParameters['skipStep'] ? 'skipStep' : 'nextStep'}"><!-- 步驟配置 --></batch:step>
</batch:job>
八、總結:SpEL最佳實踐
-
適用場景:
-
? 動態配置值
-
? 條件化Bean創建
-
? 安全表達式
-
? 簡單業務規則
-
? 復雜業務邏輯(應使用Java代碼)
-
-
性能黃金法則:
安全建議:
-
永遠不要執行不受信任的表達式
-
生產環境啟用SecurityManager
-
限制可訪問的類和包
-
終極提示:在Spring Boot中,可通過
spring.expression.compiler.mode
設置編譯器模式(IMMEDIATE, MIXED, OFF)