目录
一、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)