Spring表达式详解

Spring表达式详解:SpEL从入门到实战

前言

SpEL(Spring Expression Language,Spring表达式语言)是Spring框架提供的强大表达式语言,支持在运行时查询和操作对象图。SpEL广泛应用于Spring的各个模块中,如Spring Security、Spring Data、Spring Cache等。本文将深入讲解SpEL的核心概念、语法特性和实战应用。

一、SpEL概述

1.1 什么是SpEL

SpEL是一种功能强大的表达式语言,它支持:

  • 字面量表达式
  • 属性访问
  • 方法调用
  • 集合操作
  • 逻辑运算
  • 正则表达式
  • 自定义函数
kotlin 复制代码
SpEL在Spring中的应用
┌─────────────────────────────────────────┐
│  Spring框架                              │
│  ┌───────────────────────────────────┐ │
│  │ @Value("${...}")                  │ │
│  │ @Cacheable(key = "#id")           │ │
│  │ @PreAuthorize("hasRole('ADMIN')")│ │
│  │ @EventListener(condition = "...")│ │
│  │ XML配置:<property value="#{...}"/>│ │
│  └───────────────────────────────────┘ │
│              ↓                           │
│  ┌───────────────────────────────────┐ │
│  │     SpEL解析引擎                  │ │
│  │  - 词法分析                       │ │
│  │  - 语法分析                       │ │
│  │  - 表达式求值                     │ │
│  └───────────────────────────────────┘ │
└─────────────────────────────────────────┘

1.2 SpEL的优势

复制代码
SpEL vs 其他表达式语言
┌──────────────┬─────────────┬──────────────┐
│  特性         │  SpEL       │  EL/OGNL     │
├──────────────┼─────────────┼──────────────┤
│  Spring集成  │  原生支持    │  需要适配     │
│  类型安全     │  支持        │  有限支持     │
│  方法调用     │  全面支持    │  有限支持     │
│  集合操作     │  强大        │  基础         │
│  自定义函数   │  支持        │  有限         │
│  安全性       │  可控        │  风险较高     │
└──────────────┴─────────────┴──────────────┘

二、SpEL基础语法

2.1 字面量表达式

java 复制代码
/**
 * 字面量表达式示例
 */
public class LiteralExpressionsDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 字符串字面量
        Expression exp1 = parser.parseExpression("'Hello World'");
        String str = (String) exp1.getValue();
        System.out.println("字符串: " + str);  // Hello World

        // 数字字面量
        Expression exp2 = parser.parseExpression("100");
        Integer num = (Integer) exp2.getValue();
        System.out.println("数字: " + num);  // 100

        // 浮点数字面量
        Expression exp3 = parser.parseExpression("3.14159");
        Double pi = (Double) exp3.getValue();
        System.out.println("浮点数: " + pi);  // 3.14159

        // 布尔字面量
        Expression exp4 = parser.parseExpression("true");
        Boolean bool = (Boolean) exp4.getValue();
        System.out.println("布尔值: " + bool);  // true

        // null字面量
        Expression exp5 = parser.parseExpression("null");
        Object nullVal = exp5.getValue();
        System.out.println("null值: " + nullVal);  // null
    }
}

2.2 属性访问

java 复制代码
/**
 * 属性访问示例
 */
public class PropertyAccessDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 创建上下文对象
        User user = new User("张三", 25, "zhangsan@example.com");

        // 访问属性
        Expression exp1 = parser.parseExpression("name");
        String name = exp1.getValue(user, String.class);
        System.out.println("姓名: " + name);  // 张三

        // 访问嵌套属性
        user.setAddress(new Address("北京市", "朝阳区"));
        Expression exp2 = parser.parseExpression("address.city");
        String city = exp2.getValue(user, String.class);
        System.out.println("城市: " + city);  // 北京市

        // 安全导航(避免空指针)
        user.setAddress(null);
        Expression exp3 = parser.parseExpression("address?.city");
        String safeCity = exp3.getValue(user, String.class);
        System.out.println("安全访问: " + safeCity);  // null

        // 数组/列表访问
        user.setHobbies(new String[]{"篮球", "游泳", "阅读"});
        Expression exp4 = parser.parseExpression("hobbies[0]");
        String hobby = exp4.getValue(user, String.class);
        System.out.println("第一个爱好: " + hobby);  // 篮球

        // Map访问
        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");
        Expression exp5 = parser.parseExpression("['key1']");
        String value = exp5.getValue(map, String.class);
        System.out.println("Map值: " + value);  // value1
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    private String name;
    private Integer age;
    private String email;
    private Address address;
    private String[] hobbies;

    public User(String name, Integer age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

@Data
@AllArgsConstructor
class Address {
    private String city;
    private String district;
}

2.3 方法调用

java 复制代码
/**
 * 方法调用示例
 */
public class MethodCallDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 字符串方法调用
        Expression exp1 = parser.parseExpression("'Hello'.concat(' World')");
        String result1 = exp1.getValue(String.class);
        System.out.println("字符串拼接: " + result1);  // Hello World

        // 对象方法调用
        User user = new User("zhangsan", 25, "zhangsan@example.com");
        Expression exp2 = parser.parseExpression("name.toUpperCase()");
        String result2 = exp2.getValue(user, String.class);
        System.out.println("大写: " + result2);  // ZHANGSAN

        // 链式调用
        Expression exp3 = parser.parseExpression("email.substring(0, 8).toUpperCase()");
        String result3 = exp3.getValue(user, String.class);
        System.out.println("链式调用: " + result3);  // ZHANGSAN

        // 静态方法调用
        Expression exp4 = parser.parseExpression(
            "T(java.lang.Math).random() * 100");
        Double random = exp4.getValue(Double.class);
        System.out.println("随机数: " + random);

        // 调用自定义方法
        Expression exp5 = parser.parseExpression(
            "T(com.example.StringUtils).isEmpty(name)");
        Boolean isEmpty = exp5.getValue(user, Boolean.class);
        System.out.println("是否为空: " + isEmpty);
    }
}

/**
 * 工具类
 */
class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }
}

2.4 运算符

java 复制代码
/**
 * 运算符示例
 */
public class OperatorDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 算术运算符
        System.out.println("加法: " + parser.parseExpression("1 + 2").getValue());  // 3
        System.out.println("减法: " + parser.parseExpression("5 - 2").getValue());  // 3
        System.out.println("乘法: " + parser.parseExpression("3 * 4").getValue());  // 12
        System.out.println("除法: " + parser.parseExpression("10 / 2").getValue()); // 5
        System.out.println("取模: " + parser.parseExpression("10 % 3").getValue()); // 1

        // 关系运算符
        System.out.println("等于: " + parser.parseExpression("5 == 5").getValue());   // true
        System.out.println("不等于: " + parser.parseExpression("5 != 3").getValue()); // true
        System.out.println("大于: " + parser.parseExpression("5 > 3").getValue());    // true
        System.out.println("小于: " + parser.parseExpression("3 < 5").getValue());    // true
        System.out.println("大于等于: " + parser.parseExpression("5 >= 5").getValue());  // true

        // 逻辑运算符
        System.out.println("与: " + parser.parseExpression("true and false").getValue()); // false
        System.out.println("或: " + parser.parseExpression("true or false").getValue());  // true
        System.out.println("非: " + parser.parseExpression("!true").getValue());         // false

        // 三元运算符
        System.out.println("三元: " + parser.parseExpression("5 > 3 ? 'yes' : 'no'").getValue());  // yes

        // 字符串连接
        System.out.println("拼接: " + parser.parseExpression("'Hello' + ' ' + 'World'").getValue());

        // Elvis运算符(简化三元)
        User user = new User();
        user.setName(null);
        Expression exp = parser.parseExpression("name?:'匿名用户'");
        System.out.println("Elvis: " + exp.getValue(user, String.class));  // 匿名用户
    }
}

2.5 集合操作

java 复制代码
/**
 * 集合操作示例
 */
public class CollectionDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 创建列表
        Expression exp1 = parser.parseExpression("{1,2,3,4,5}");
        List<Integer> list = (List<Integer>) exp1.getValue();
        System.out.println("列表: " + list);  // [1, 2, 3, 4, 5]

        // 创建Map
        Expression exp2 = parser.parseExpression("{name:'张三',age:25}");
        Map<String, Object> map = (Map<String, Object>) exp2.getValue();
        System.out.println("Map: " + map);  // {name=张三, age=25}

        // 集合选择(筛选)
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("numbers", numbers);

        // 选择偶数
        Expression exp3 = parser.parseExpression("#numbers.?[#this % 2 == 0]");
        List<Integer> evenNumbers = (List<Integer>) exp3.getValue(context);
        System.out.println("偶数: " + evenNumbers);  // [2, 4, 6, 8, 10]

        // 集合投影(映射)
        List<User> users = Arrays.asList(
            new User("张三", 25, "zhangsan@example.com"),
            new User("李四", 30, "lisi@example.com"),
            new User("王五", 35, "wangwu@example.com")
        );
        context.setVariable("users", users);

        // 提取所有用户名
        Expression exp4 = parser.parseExpression("#users.![name]");
        List<String> names = (List<String>) exp4.getValue(context);
        System.out.println("用户名列表: " + names);  // [张三, 李四, 王五]

        // 查找第一个匹配
        Expression exp5 = parser.parseExpression("#users.^[age > 25]");
        User firstMatch = (User) exp5.getValue(context);
        System.out.println("第一个年龄>25: " + firstMatch.getName());  // 李四

        // 查找最后一个匹配
        Expression exp6 = parser.parseExpression("#users.$[age > 25]");
        User lastMatch = (User) exp6.getValue(context);
        System.out.println("最后一个年龄>25: " + lastMatch.getName());  // 王五
    }
}

2.6 正则表达式

java 复制代码
/**
 * 正则表达式示例
 */
public class RegexDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 邮箱验证
        Expression exp1 = parser.parseExpression(
            "'zhangsan@example.com' matches '^[A-Za-z0-9+_.-]+@(.+)$'");
        Boolean isEmail = exp1.getValue(Boolean.class);
        System.out.println("是否为邮箱: " + isEmail);  // true

        // 手机号验证
        Expression exp2 = parser.parseExpression(
            "'13800138000' matches '^1[3-9]\\d{9}$'");
        Boolean isPhone = exp2.getValue(Boolean.class);
        System.out.println("是否为手机号: " + isPhone);  // true

        // 在对象上使用
        User user = new User("张三", 25, "invalid-email");
        Expression exp3 = parser.parseExpression(
            "email matches '^[A-Za-z0-9+_.-]+@(.+)$'");
        Boolean validEmail = exp3.getValue(user, Boolean.class);
        System.out.println("邮箱格式正确: " + validEmail);  // false
    }
}

三、Spring中的SpEL应用

3.1 @Value注解

java 复制代码
/**
 * @Value注解使用SpEL
 */
@Component
public class ValueDemo {

    // 1. 字面量
    @Value("#{100}")
    private Integer literalValue;

    // 2. 系统属性
    @Value("#{systemProperties['user.home']}")
    private String userHome;

    // 3. 环境变量
    @Value("#{systemEnvironment['PATH']}")
    private String path;

    // 4. 配置文件属性
    @Value("${server.port:8080}")
    private Integer serverPort;

    // 5. Bean属性
    @Value("#{userService.userCount}")
    private Integer userCount;

    // 6. 表达式计算
    @Value("#{T(java.lang.Math).random() * 100}")
    private Double randomValue;

    // 7. 列表注入
    @Value("#{'${user.roles}'.split(',')}")
    private List<String> roles;

    // 8. 三元表达式
    @Value("#{${user.enabled:true} ? '启用' : '禁用'}")
    private String userStatus;

    // 9. Elvis运算符
    @Value("#{config.name ?: 'default'}")
    private String configName;

    public void printValues() {
        System.out.println("字面量: " + literalValue);
        System.out.println("用户主目录: " + userHome);
        System.out.println("随机值: " + randomValue);
        System.out.println("用户角色: " + roles);
        System.out.println("用户状态: " + userStatus);
    }
}

3.2 Spring Cache

java 复制代码
/**
 * Spring Cache中使用SpEL
 */
@Service
public class ProductService {

    /**
     * 缓存Key使用SpEL
     */
    @Cacheable(
        value = "products",
        key = "#id",                    // 简单参数
        condition = "#id > 0",          // 缓存条件
        unless = "#result == null"     // 排除条件
    )
    public Product findById(Long id) {
        System.out.println("从数据库查询: " + id);
        return new Product(id, "商品" + id, new BigDecimal("99.99"));
    }

    /**
     * 复杂Key生成
     */
    @Cacheable(
        value = "productList",
        key = "#root.methodName + ':' + #category + ':' + #page + ':' + #size"
    )
    public List<Product> findByCategory(String category, int page, int size) {
        System.out.println("查询分类: " + category);
        return Arrays.asList(new Product(1L, "商品1", new BigDecimal("99.99")));
    }

    /**
     * 使用对象属性作为Key
     */
    @Cacheable(
        value = "searchResults",
        key = "#query.keyword + ':' + #query.page"
    )
    public List<Product> search(SearchQuery query) {
        System.out.println("搜索: " + query.getKeyword());
        return new ArrayList<>();
    }

    /**
     * 缓存删除
     */
    @CacheEvict(
        value = "products",
        key = "#product.id",
        condition = "#product.id != null"
    )
    public void updateProduct(Product product) {
        System.out.println("更新商品: " + product.getId());
    }

    /**
     * 批量删除缓存
     */
    @CacheEvict(value = "products", allEntries = true)
    public void clearAllCache() {
        System.out.println("清空所有缓存");
    }
}

@Data
@AllArgsConstructor
class Product {
    private Long id;
    private String name;
    private BigDecimal price;
}

@Data
class SearchQuery {
    private String keyword;
    private int page;
    private int size;
}

3.3 Spring Security

java 复制代码
/**
 * Spring Security中使用SpEL
 */
@RestController
@RequestMapping("/api/users")
public class UserController {

    /**
     * 方法级权限控制
     */
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping
    public List<User> getAllUsers() {
        return new ArrayList<>();
    }

    /**
     * 基于参数的权限控制
     */
    @PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return new User();
    }

    /**
     * 复杂权限表达式
     */
    @PreAuthorize("hasRole('ADMIN') and #user.department == authentication.principal.department")
    @PostMapping
    public User createUser(@RequestBody User user) {
        return user;
    }

    /**
     * 方法返回后检查
     */
    @PostAuthorize("returnObject.owner == authentication.principal.username")
    @GetMapping("/{id}/details")
    public UserDetails getUserDetails(@PathVariable Long id) {
        return new UserDetails();
    }

    /**
     * 过滤集合
     */
    @PostFilter("filterObject.owner == authentication.principal.username")
    @GetMapping("/my-items")
    public List<Item> getMyItems() {
        return new ArrayList<>();
    }
}

@Data
class UserDetails {
    private Long id;
    private String owner;
}

@Data
class Item {
    private Long id;
    private String owner;
}

3.4 Spring事件监听

java 复制代码
/**
 * 事件监听中使用SpEL
 */
@Component
public class OrderEventListener {

    /**
     * 条件事件监听
     */
    @EventListener(condition = "#event.order.amount > 1000")
    public void handleLargeOrder(OrderCreatedEvent event) {
        System.out.println("处理大额订单: " + event.getOrder().getAmount());
    }

    /**
     * 复杂条件
     */
    @EventListener(condition = "#event.order.status == 'PAID' and #event.order.amount > 500")
    public void handlePaidOrder(OrderCreatedEvent event) {
        System.out.println("处理已支付订单");
    }

    /**
     * 使用SpEL调用方法
     */
    @EventListener(condition = "#event.isVipOrder()")
    public void handleVipOrder(OrderCreatedEvent event) {
        System.out.println("处理VIP订单");
    }
}

class OrderCreatedEvent extends ApplicationEvent {
    private Order order;

    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder() {
        return order;
    }

    public boolean isVipOrder() {
        return order.getAmount().compareTo(new BigDecimal("5000")) > 0;
    }
}

@Data
class Order {
    private Long id;
    private String status;
    private BigDecimal amount;
}

四、高级特性

4.1 自定义函数

java 复制代码
/**
 * 自定义函数示例
 */
public class CustomFunctionDemo {

    public static void main(String[] args) throws NoSuchMethodException {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 注册自定义函数
        Method method = StringUtils.class.getDeclaredMethod("reverse", String.class);
        context.registerFunction("reverse", method);

        // 使用自定义函数
        Expression exp = parser.parseExpression("#reverse('Hello')");
        String result = exp.getValue(context, String.class);
        System.out.println("反转: " + result);  // olleH
    }

    static class StringUtils {
        public static String reverse(String str) {
            return new StringBuilder(str).reverse().toString();
        }
    }
}

4.2 变量定义

java 复制代码
/**
 * 变量定义示例
 */
public class VariableDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 定义变量
        context.setVariable("name", "张三");
        context.setVariable("age", 25);
        context.setVariable("hobbies", Arrays.asList("篮球", "游泳", "阅读"));

        // 使用变量
        Expression exp1 = parser.parseExpression("#name");
        System.out.println("姓名: " + exp1.getValue(context));  // 张三

        // 在表达式中计算
        Expression exp2 = parser.parseExpression("#age > 18 ? '成年' : '未成年'");
        System.out.println("年龄状态: " + exp2.getValue(context));  // 成年

        // 集合操作
        Expression exp3 = parser.parseExpression("#hobbies.size()");
        System.out.println("爱好数量: " + exp3.getValue(context));  // 3

        // #this和#root
        User user = new User("李四", 30, "lisi@example.com");
        context.setRootObject(user);

        Expression exp4 = parser.parseExpression("#root.name");
        System.out.println("#root: " + exp4.getValue(context));  // 李四

        Expression exp5 = parser.parseExpression("name");
        System.out.println("直接访问: " + exp5.getValue(context));  // 李四
    }
}

4.3 Bean引用

java 复制代码
/**
 * Bean引用示例
 */
@Configuration
public class BeanReferenceDemo {

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }

    @Component
    public class ConfigService {

        // 引用Bean
        @Value("#{@dataSource.jdbcUrl}")
        private String jdbcUrl;

        // 调用Bean方法
        @Value("#{@userService.getUserCount()}")
        private Integer userCount;

        // Bean属性访问
        @Value("#{@environment['spring.application.name']}")
        private String appName;
    }

    @Service
    public class UserService {

        public Integer getUserCount() {
            return 100;
        }

        @Cacheable(value = "users", key = "@userService.generateKey(#id)")
        public User findById(Long id) {
            return new User();
        }

        public String generateKey(Long id) {
            return "user:" + id;
        }
    }
}

4.4 类型转换

java 复制代码
/**
 * 类型转换示例
 */
public class TypeConversionDemo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        // 自动类型转换
        Expression exp1 = parser.parseExpression("'100'");
        Integer num = exp1.getValue(Integer.class);
        System.out.println("字符串转整数: " + num);  // 100

        // 显式类型转换
        Expression exp2 = parser.parseExpression("T(Integer).parseInt('123')");
        Integer parsed = exp2.getValue(Integer.class);
        System.out.println("解析整数: " + parsed);  // 123

        // 日期转换
        Expression exp3 = parser.parseExpression(
            "T(java.time.LocalDate).parse('2024-01-01')");
        LocalDate date = exp3.getValue(LocalDate.class);
        System.out.println("日期: " + date);

        // 自定义类型转换
        StandardEvaluationContext context = new StandardEvaluationContext();
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToUserConverter());
        context.setTypeConverter(new StandardTypeConverter(conversionService));

        Expression exp4 = parser.parseExpression("'张三,25,zhangsan@example.com'");
        User user = exp4.getValue(context, User.class);
        System.out.println("自定义转换: " + user.getName());
    }

    static class StringToUserConverter implements Converter<String, User> {
        @Override
        public User convert(String source) {
            String[] parts = source.split(",");
            return new User(parts[0], Integer.parseInt(parts[1]), parts[2]);
        }
    }
}

五、实战案例

5.1 案例1:动态权限校验

java 复制代码
/**
 * 动态权限校验系统
 */
@Service
public class PermissionService {

    @Autowired
    private ExpressionParser parser;

    private final Map<String, String> resourcePermissions = new HashMap<>();

    public PermissionService() {
        // 初始化资源权限表达式
        resourcePermissions.put("/api/users", "hasRole('ADMIN')");
        resourcePermissions.put("/api/users/{id}",
            "hasRole('ADMIN') or #id == principal.id");
        resourcePermissions.put("/api/orders",
            "hasRole('USER') and principal.department == 'SALES'");
    }

    /**
     * 检查权限
     */
    public boolean checkPermission(String resource, Map<String, Object> variables) {
        String expression = resourcePermissions.get(resource);
        if (expression == null) {
            return true;  // 无限制
        }

        StandardEvaluationContext context = new StandardEvaluationContext();

        // 设置变量
        variables.forEach(context::setVariable);

        // 注册自定义函数
        registerFunctions(context);

        Expression exp = parser.parseExpression(expression);
        return Boolean.TRUE.equals(exp.getValue(context, Boolean.class));
    }

    private void registerFunctions(StandardEvaluationContext context) {
        // 注册hasRole函数
        context.setVariable("hasRole", (Function<String, Boolean>) role -> {
            // 模拟从SecurityContext获取角色
            Set<String> roles = getCurrentUserRoles();
            return roles.contains(role);
        });

        // 设置principal
        context.setVariable("principal", getCurrentUser());
    }

    private Set<String> getCurrentUserRoles() {
        return new HashSet<>(Arrays.asList("USER", "ADMIN"));
    }

    private UserPrincipal getCurrentUser() {
        return new UserPrincipal(1L, "zhangsan", "SALES");
    }
}

@Data
@AllArgsConstructor
class UserPrincipal {
    private Long id;
    private String username;
    private String department;
}

/**
 * 使用示例
 */
public class PermissionDemo {
    public static void main(String[] args) {
        PermissionService service = new PermissionService();
        service.parser = new SpelExpressionParser();

        // 检查访问/api/users权限
        boolean canAccess = service.checkPermission("/api/users", new HashMap<>());
        System.out.println("可以访问用户列表: " + canAccess);

        // 检查访问特定用户权限
        Map<String, Object> vars = new HashMap<>();
        vars.put("id", 1L);
        boolean canAccessUser = service.checkPermission("/api/users/{id}", vars);
        System.out.println("可以访问用户1: " + canAccessUser);
    }
}

5.2 案例2:动态查询构建器

java 复制代码
/**
 * 动态查询构建器
 */
@Service
public class DynamicQueryService {

    @Autowired
    private ExpressionParser parser;

    /**
     * 根据条件表达式构建查询
     */
    public List<User> query(String condition, Map<String, Object> params) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        params.forEach(context::setVariable);

        // 模拟数据
        List<User> allUsers = Arrays.asList(
            new User("张三", 25, "zhangsan@example.com"),
            new User("李四", 30, "lisi@example.com"),
            new User("王五", 35, "wangwu@example.com"),
            new User("赵六", 28, "zhaoliu@example.com")
        );

        context.setVariable("users", allUsers);

        // 使用SpEL过滤
        String expression = "#users.?[" + condition + "]";
        Expression exp = parser.parseExpression(expression);

        return (List<User>) exp.getValue(context);
    }

    /**
     * 动态排序
     */
    public List<User> sort(List<User> users, String sortExpression) {
        users.sort((u1, u2) -> {
            StandardEvaluationContext context1 = new StandardEvaluationContext(u1);
            StandardEvaluationContext context2 = new StandardEvaluationContext(u2);

            Expression exp = parser.parseExpression(sortExpression);
            Comparable v1 = exp.getValue(context1, Comparable.class);
            Comparable v2 = exp.getValue(context2, Comparable.class);

            return v1.compareTo(v2);
        });

        return users;
    }
}

/**
 * 使用示例
 */
@SpringBootTest
public class DynamicQueryDemo {

    @Autowired
    private DynamicQueryService queryService;

    @Test
    public void testDynamicQuery() {
        // 查询年龄大于25的用户
        Map<String, Object> params1 = new HashMap<>();
        params1.put("minAge", 25);
        List<User> result1 = queryService.query("#this.age > #minAge", params1);
        System.out.println("年龄>25: " + result1.size());

        // 查询邮箱包含特定域名的用户
        Map<String, Object> params2 = new HashMap<>();
        params2.put("domain", "example.com");
        List<User> result2 = queryService.query(
            "#this.email.contains(#domain)", params2);
        System.out.println("邮箱包含example.com: " + result2.size());

        // 复合条件
        Map<String, Object> params3 = new HashMap<>();
        params3.put("minAge", 25);
        params3.put("maxAge", 32);
        List<User> result3 = queryService.query(
            "#this.age >= #minAge and #this.age <= #maxAge", params3);
        System.out.println("年龄25-32: " + result3.size());

        // 动态排序
        List<User> sorted = queryService.sort(result3, "age");
        System.out.println("排序后: " + sorted);
    }
}

5.3 案例3:配置表达式引擎

java 复制代码
/**
 * 配置表达式引擎
 */
@Service
public class ConfigExpressionService {

    @Autowired
    private Environment environment;

    @Autowired
    private ExpressionParser parser;

    /**
     * 解析配置表达式
     */
    public <T> T evaluateConfig(String expression, Class<T> type) {
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 添加环境变量
        context.setVariable("env", environment);

        // 添加系统属性
        context.setVariable("sys", System.getProperties());

        // 添加自定义函数
        registerFunctions(context);

        Expression exp = parser.parseExpression(expression);
        return exp.getValue(context, type);
    }

    private void registerFunctions(StandardEvaluationContext context) {
        try {
            // 注册环境相关函数
            context.registerFunction("isProd",
                ConfigExpressionService.class.getMethod("isProdEnvironment"));
            context.registerFunction("isWindows",
                ConfigExpressionService.class.getMethod("isWindows"));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public static boolean isProdEnvironment() {
        String profile = System.getProperty("spring.profiles.active", "");
        return "prod".equals(profile);
    }

    public static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }
}

/**
 * 配置类
 */
@Configuration
public class DynamicConfig {

    @Autowired
    private ConfigExpressionService configService;

    /**
     * 动态线程池配置
     */
    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        // 根据环境动态配置
        int coreSize = configService.evaluateConfig(
            "#isProd() ? 20 : 5", Integer.class);

        int maxSize = configService.evaluateConfig(
            "#isProd() ? 50 : 10", Integer.class);

        int queueSize = configService.evaluateConfig(
            "#isProd() ? 1000 : 100", Integer.class);

        return new ThreadPoolExecutor(
            coreSize, maxSize, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(queueSize),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }

    /**
     * 动态数据源配置
     */
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();

        // 根据系统动态选择驱动
        String driver = configService.evaluateConfig(
            "#isWindows() ? 'com.mysql.cj.jdbc.Driver' : 'org.postgresql.Driver'",
            String.class);

        config.setDriverClassName(driver);

        return new HikariDataSource(config);
    }
}

5.4 案例4:规则引擎

java 复制代码
/**
 * 简单规则引擎
 */
@Service
public class RuleEngine {

    @Autowired
    private ExpressionParser parser;

    private final List<Rule> rules = new ArrayList<>();

    public RuleEngine() {
        initRules();
    }

    private void initRules() {
        // 订单金额折扣规则
        rules.add(new Rule(
            "大额订单折扣",
            "order.amount > 1000",
            "order.discount = 0.9"
        ));

        rules.add(new Rule(
            "VIP用户折扣",
            "user.vip == true",
            "order.discount = order.discount * 0.95"
        ));

        rules.add(new Rule(
            "首单优惠",
            "user.orderCount == 0",
            "order.discount = order.discount * 0.85"
        ));

        rules.add(new Rule(
            "节假日优惠",
            "T(java.time.LocalDate).now().dayOfWeek.value >= 6",
            "order.discount = order.discount * 0.98"
        ));
    }

    /**
     * 执行规则
     */
    public void execute(OrderContext context) {
        StandardEvaluationContext evalContext = new StandardEvaluationContext(context);

        for (Rule rule : rules) {
            // 检查条件
            Expression conditionExp = parser.parseExpression(rule.getCondition());
            Boolean matched = conditionExp.getValue(evalContext, Boolean.class);

            if (Boolean.TRUE.equals(matched)) {
                System.out.println("规则匹配: " + rule.getName());

                // 执行动作
                Expression actionExp = parser.parseExpression(rule.getAction());
                actionExp.getValue(evalContext);
            }
        }
    }
}

@Data
@AllArgsConstructor
class Rule {
    private String name;
    private String condition;
    private String action;
}

@Data
class OrderContext {
    private OrderInfo order;
    private UserInfo user;

    public OrderContext(OrderInfo order, UserInfo user) {
        this.order = order;
        this.user = user;
        this.order.setDiscount(1.0);  // 初始折扣
    }
}

@Data
class OrderInfo {
    private BigDecimal amount;
    private Double discount;

    public BigDecimal getFinalAmount() {
        return amount.multiply(BigDecimal.valueOf(discount));
    }
}

@Data
class UserInfo {
    private boolean vip;
    private int orderCount;
}

/**
 * 使用示例
 */
@SpringBootTest
public class RuleEngineDemo {

    @Autowired
    private RuleEngine ruleEngine;

    @Test
    public void testRuleEngine() {
        // 场景1:大额订单 + VIP用户
        OrderInfo order1 = new OrderInfo();
        order1.setAmount(new BigDecimal("1500"));

        UserInfo user1 = new UserInfo();
        user1.setVip(true);
        user1.setOrderCount(5);

        OrderContext context1 = new OrderContext(order1, user1);
        ruleEngine.execute(context1);

        System.out.println("原价: " + order1.getAmount());
        System.out.println("折扣: " + order1.getDiscount());
        System.out.println("实付: " + order1.getFinalAmount());
        System.out.println();

        // 场景2:首单用户
        OrderInfo order2 = new OrderInfo();
        order2.setAmount(new BigDecimal("500"));

        UserInfo user2 = new UserInfo();
        user2.setVip(false);
        user2.setOrderCount(0);

        OrderContext context2 = new OrderContext(order2, user2);
        ruleEngine.execute(context2);

        System.out.println("原价: " + order2.getAmount());
        System.out.println("折扣: " + order2.getDiscount());
        System.out.println("实付: " + order2.getFinalAmount());
    }
}

六、性能优化

6.1 表达式缓存

java 复制代码
/**
 * 表达式缓存
 */
@Service
public class CachedExpressionService {

    private final ExpressionParser parser = new SpelExpressionParser();
    private final Map<String, Expression> cache = new ConcurrentHashMap<>();

    /**
     * 获取表达式(带缓存)
     */
    public Expression getExpression(String expressionString) {
        return cache.computeIfAbsent(expressionString, parser::parseExpression);
    }

    /**
     * 评估表达式
     */
    public <T> T evaluate(String expressionString, Object rootObject, Class<T> type) {
        Expression exp = getExpression(expressionString);
        return exp.getValue(rootObject, type);
    }

    /**
     * 性能测试
     */
    public void performanceTest() {
        String expr = "name.toUpperCase() + ' - ' + age";
        User user = new User("张三", 25, "zhangsan@example.com");

        // 不使用缓存
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            parser.parseExpression(expr).getValue(user, String.class);
        }
        long time1 = System.currentTimeMillis() - start1;

        // 使用缓存
        long start2 = System.currentTimeMillis();
        Expression cached = getExpression(expr);
        for (int i = 0; i < 10000; i++) {
            cached.getValue(user, String.class);
        }
        long time2 = System.currentTimeMillis() - start2;

        System.out.println("不使用缓存: " + time1 + "ms");
        System.out.println("使用缓存: " + time2 + "ms");
        System.out.println("性能提升: " + (time1 - time2) + "ms");
    }
}

6.2 编译模式

java 复制代码
/**
 * 编译模式优化
 */
public class CompilationDemo {

    public static void main(String[] args) {
        SpelExpressionParser parser = new SpelExpressionParser();
        SpelParserConfiguration config = new SpelParserConfiguration(
            SpelCompilerMode.IMMEDIATE,  // 立即编译
            null
        );
        SpelExpressionParser compilingParser = new SpelExpressionParser(config);

        String expr = "#this * 2 + 1";
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 普通模式
        long start1 = System.currentTimeMillis();
        Expression exp1 = parser.parseExpression(expr);
        StandardEvaluationContext context1 = new StandardEvaluationContext();
        for (int i = 0; i < 100000; i++) {
            for (Integer num : numbers) {
                context1.setRootObject(num);
                exp1.getValue(context1);
            }
        }
        long time1 = System.currentTimeMillis() - start1;

        // 编译模式
        long start2 = System.currentTimeMillis();
        Expression exp2 = compilingParser.parseExpression(expr);
        StandardEvaluationContext context2 = new StandardEvaluationContext();
        for (int i = 0; i < 100000; i++) {
            for (Integer num : numbers) {
                context2.setRootObject(num);
                exp2.getValue(context2);
            }
        }
        long time2 = System.currentTimeMillis() - start2;

        System.out.println("普通模式: " + time1 + "ms");
        System.out.println("编译模式: " + time2 + "ms");
        System.out.println("性能提升: " + ((time1 - time2) * 100.0 / time1) + "%");
    }
}

七、最佳实践

7.1 安全性

java 复制代码
/**
 * SpEL安全性最佳实践
 */
public class SecurityDemo {

    /**
     * ❌ 不安全:直接使用用户输入
     */
    public void unsafeEvaluation(String userInput) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(userInput);
        // 危险!用户可以执行任意代码
        // 如输入:T(java.lang.Runtime).getRuntime().exec('rm -rf /')
        exp.getValue();
    }

    /**
     * ✓ 安全:使用SimpleEvaluationContext
     */
    public void safeEvaluation(String userInput) {
        ExpressionParser parser = new SpelExpressionParser();

        // 创建受限的上下文
        SimpleEvaluationContext context = SimpleEvaluationContext
            .forReadOnlyDataBinding()  // 只读绑定
            .build();

        Expression exp = parser.parseExpression(userInput);
        // 只能访问有限的功能
        exp.getValue(context);
    }

    /**
     * ✓ 安全:白名单验证
     */
    public void validatedEvaluation(String userInput) {
        // 白名单验证
        if (!isValidExpression(userInput)) {
            throw new IllegalArgumentException("非法表达式");
        }

        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(userInput);
        exp.getValue();
    }

    private boolean isValidExpression(String expr) {
        // 只允许简单的属性访问和运算符
        return expr.matches("^[a-zA-Z0-9.+\\-*/()\\s]+$");
    }
}

7.2 错误处理

java 复制代码
/**
 * 错误处理最佳实践
 */
public class ErrorHandlingDemo {

    private final ExpressionParser parser = new SpelExpressionParser();

    /**
     * 安全的表达式求值
     */
    public <T> Optional<T> safeEvaluate(String expr, Object root, Class<T> type) {
        try {
            Expression expression = parser.parseExpression(expr);
            T value = expression.getValue(root, type);
            return Optional.ofNullable(value);

        } catch (ParseException e) {
            // 表达式语法错误
            System.err.println("表达式解析失败: " + e.getMessage());
            return Optional.empty();

        } catch (EvaluationException e) {
            // 表达式求值错误
            System.err.println("表达式求值失败: " + e.getMessage());
            return Optional.empty();

        } catch (Exception e) {
            // 其他错误
            System.err.println("未知错误: " + e.getMessage());
            return Optional.empty();
        }
    }

    /**
     * 带默认值的求值
     */
    public <T> T evaluateWithDefault(String expr, Object root,
                                     Class<T> type, T defaultValue) {
        return safeEvaluate(expr, root, type).orElse(defaultValue);
    }
}

八、总结

核心知识点回顾

scss 复制代码
Spring表达式语言(SpEL)核心要点
│
├── 基础语法
│   ├── 字面量表达式
│   ├── 属性访问(含安全导航)
│   ├── 方法调用
│   ├── 运算符
│   ├── 集合操作
│   └── 正则表达式
│
├── Spring集成
│   ├── @Value注解
│   ├── Spring Cache
│   ├── Spring Security
│   └── 事件监听
│
├── 高级特性
│   ├── 自定义函数
│   ├── 变量定义
│   ├── Bean引用
│   └── 类型转换
│
├── 实战应用
│   ├── 动态权限校验
│   ├── 动态查询构建
│   ├── 配置表达式引擎
│   └── 规则引擎
│
└── 最佳实践
    ├── 表达式缓存
    ├── 编译模式
    ├── 安全性控制
    └── 错误处理

SpEL是Spring框架的重要组成部分,它提供了强大而灵活的表达式计算能力。掌握SpEL不仅能帮助我们更好地使用Spring的各种特性,还能在实际项目中构建动态、可配置的系统。在使用SpEL时,要特别注意安全性问题,避免执行不受信任的表达式。

相关推荐
n***63271 小时前
SpringCloud 微服务框架
spring·spring cloud·微服务
切糕师学AI2 小时前
Spring 中的 @Service 注解
java·spring
想不明白的过度思考者2 小时前
Spring Web MVC从入门到实战
java·前端·spring·mvc
p***95003 小时前
spring Profile
java·数据库·spring
艺杯羹3 小时前
从Spring到SpringBoot3的演进:缺陷、优化与最新实践要求
java·spring boot·spring
极光代码工作室3 小时前
基于SpringBoot的校园招聘信息管理系统的设计与实现
java·前端·spring
krafft4 小时前
从零入门 Spring AI,详细拆解 ChatClient 调用流程和 Advisor 底层原理,小白可入!
java·spring·ai
j***82704 小时前
Spring 中集成Hibernate
java·spring·hibernate
h***34634 小时前
spring session、spring security和redis整合的简单使用
redis·spring·bootstrap