Spring(Spring Boot/Spring MVC)项目普遍基于 JDK 8+,天然支持 Lambda 表达式。Lambda 依托函数式接口实现简化代码、精简匿名内部类、配合 Stream 流、框架函数式编程等能力,是日常开发、框架用法里的高频语法。

本文结合Spring 实际业务场景讲解,从基础语法 → 框架常用场景 → 踩坑避坑,全程配套可运行代码。


一、前置基础(Lambda 必备知识点)

1. 函数式接口(Lambda 的前提)

函数式接口:有且仅有一个抽象方法的接口,可使用 @FunctionalInterface 显式标记(编译校验)。Lambda 表达式本质就是函数式接口的简写实现

​
// 标准函数式接口
@FunctionalInterface
public interface MyFunc {
    // 唯一抽象方法
    void sayHello(String name);
}

​

2. Lambda 基础语法

完整格式:

(参数列表) -> { 方法体 }
简写规则(开发常用)
  1. 参数只有一个:可省略括号 ()
  2. 参数类型可省略(编译器自动推断)
  3. 方法体只有一行代码:可省略大括号 {} + return(有返回值时)

示例对比:

​
// 1. 完整写法
MyFunc func1 = (String name) -> {
    System.out.println("Hello: " + name);
};

// 2. 简写(省略参数类型、大括号)
MyFunc func2 = name -> System.out.println("Hello: " + name);

// 调用
func2.sayHello("Spring");

​

3. 方法引用(Lambda 进阶简写)

配合 Lambda 进一步简化代码,Spring 项目中高频使用:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法
  • 构造方法引用 类::new

二、Spring 项目高频实战场景(按使用频率排序)

场景 1:集合 + Stream + Lambda(业务层最常用)

Service/Controller 中对 List/Set/Map过滤、排序、分组、去重、统计、转 Map,替代传统 for 循环,代码更简洁。

1. 先定义业务实体(Spring 常规实体)
​
import lombok.Data;
import java.util.Date;

@Data
public class User {
    private Long id;
    private String username;
    private Integer age;
    private String gender;
}

​
2. 常用 Stream + Lambda 操作示例
​
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserService {

    // 模拟数据库查询结果
    private List<User> getUserList() {
        List<User> list = new ArrayList<>();
        User u1 = new User(); u1.setId(1L); u1.setUsername("张三"); u1.setAge(20); u1.setGender("男");
        User u2 = new User(); u2.setId(2L); u2.setUsername("李四"); u2.setAge(25); u2.setGender("女");
        User u3 = new User(); u3.setId(3L); u3.setUsername("王五"); u3.setAge(20); u3.setGender("男");
        list.add(u1); list.add(u2); list.add(u3);
        return list;
    }

    public void streamTest() {
        List<User> userList = getUserList();

        // 1. 过滤:筛选 年龄>=22 的用户
        List<User> filterList = userList.stream()
                .filter(user -> user.getAge() >= 22) // Lambda 过滤条件
                .collect(Collectors.toList());

        // 2. 排序:按年龄升序,年龄相同按id降序
        List<User> sortList = userList.stream()
                .sorted((u1, u2) -> {
                    if (!u1.getAge().equals(u2.getAge())) {
                        return u1.getAge() - u2.getAge();
                    }
                    return Long.compare(u2.getId(), u1.getId());
                })
                .collect(Collectors.toList());

        // 3. 分组:按性别分组
        Map<String, List<User>> groupMap = userList.stream()
                .collect(Collectors.groupingBy(user -> user.getGender()));

        // 4. 提取字段:只收集用户名
        List<String> nameList = userList.stream()
                .map(user -> user.getUsername())
                .collect(Collectors.toList());

        // 5. 转Map:id 作为 key,用户对象作为 value(注意key重复问题)
        Map<Long, User> userMap = userList.stream()
                .collect(Collectors.toMap(user -> user.getId(), user -> user));

        // 6. 遍历(替代for循环)
        userList.forEach(user -> System.out.println(user.getUsername()));
    }
}

​

场景 2:MyBatis-Plus Lambda 条件构造器(持久层核心用法)

Spring 项目搭配 MyBatis-Plus 时,LambdaQueryWrapper / LambdaUpdateWrapper 是 Lambda 最经典场景:

  • 优势:避免硬编码字符串字段名,防止手写字段名出错,重构代码自动关联。
    ​
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    @Service
    public class UserService {
    
        @Resource
        private UserMapper userMapper; // MyBatis-Plus Mapper
    
        // 1. Lambda 查询(推荐,无硬编码字段)
        public User getUserById(Long id) {
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            // Lambda 写法:User::getId 方法引用,等价于 user -> user.getId()
            queryWrapper.eq(User::getId, id)
                        .eq(User::getGender, "男");
            return userMapper.selectOne(queryWrapper);
        }
    
        // 2. Lambda 更新
        public void updateUserAge(Long id, Integer newAge) {
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(User::getId, id)
                         .set(User::getAge, newAge);
            userMapper.update(null, updateWrapper);
        }
    }
    
    ​

对比传统 QueryWrappereq("id", id) 字符串写死字段,重构时不会自动修改,极易出错。


场景 3:Spring 内置四大核心函数接口(通用回调 / 工具封装)

JDK 8 自带 4 个核心函数式接口,Spring 大量底层、工具方法都依赖它们,日常用来封装通用逻辑。

接口抽象方法作用
Consumer<T>void accept(T t)消费型:入参无返回值(执行动作)
Function<T,R>R apply(T t)函数型:入参 T,返回 R(类型转换)
Predicate<T>boolean test(T t)断言型:入参,返回布尔(判断 / 过滤)
Supplier<T>T get()供给型:无入参,返回 T(获取对象)
代码示例(Spring Service 中使用)

场景 4:项目启动执行 CommandLineRunner / ApplicationRunner

Spring Boot 项目启动完成后执行任务,传统写法需要新建类,Lambda 一行简化

在启动类中直接使用:

​
import org.springframework.stereotype.Service;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

@Service
public class FuncService {

    public void test() {
        User user = new User();
        user.setUsername("测试用户");

        // 1. Consumer 消费:处理对象,无返回
        Consumer<User> consumer = u -> System.out.println("用户名:" + u.getUsername());
        consumer.accept(user);

        // 2. Function 转换:User → 用户名字符串
        Function<User, String> func = u -> u.getUsername();
        String name = func.apply(user);

        // 3. Predicate 判断:判断年龄是否成年
        Predicate<Integer> predicate = age -> age >= 18;
        boolean isAdult = predicate.test(20);

        // 4. Supplier 生成对象:无参,返回User
        Supplier<User> supplier = () -> new User();
        User newUser = supplier.get();
    }
}

​

​
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    // Lambda 写法:项目启动后执行
    public CommandLineRunner runTask() {
        return args -> {
            System.out.println("Spring 项目启动完成,执行初始化任务...");
            // 执行数据初始化、定时任务预热、外部接口拉取等
        };
    }
}

​

场景 5:Web 组件:过滤器、Servlet 函数式注册

Spring Boot 中使用 FilterRegistrationBean 注册过滤器,可用 Lambda 替代传统 Filter 类,精简配置。

​
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Configuration
public class WebConfig {

    // Lambda 实现过滤器,无需单独写 Filter 类
    @Bean
    public FilterRegistrationBean<Filter> loginFilter() {
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        // Lambda 实现 doFilter 方法
        bean.setFilter((request, response, chain) -> {
            HttpServletRequest req = (HttpServletRequest) request;
            System.out.println("拦截请求:" + req.getRequestURI());
            // 放行
            chain.doFilter(request, response);
        });
        bean.addUrlPatterns("/*");
        return bean;
    }
}

​

场景 6:Spring 事件监听(函数式简化监听)

Spring 事件机制基于 ApplicationListener(函数式接口),可用 Lambda 简化事件监听代码。

​
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EventConfig {

    // Lambda 实现事件监听
    @Bean
    public ApplicationListener<ApplicationEvent> eventListener() {
        return event -> {
            System.out.println("监听到Spring事件:" + event.getClass().getSimpleName());
        };
    }
}

​

场景 7:线程池 & 异步任务(多线程 Lambda)

Spring 内置线程池、@Async 异步场景,使用 Lambda 快速定义线程任务。

​
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.Executor;

@Service
public class AsyncService {

    @Resource
    private Executor taskExecutor; // Spring 线程池

    public void asyncTask() {
        // Lambda 提交多线程任务
        taskExecutor.execute(() -> System.out.println("异步任务1执行"));
        taskExecutor.execute(() -> System.out.println("异步任务2执行"));
    }

    // @Async 异步方法内部使用Lambda
    @Async
    public void asyncWork() {
        new Thread(() -> System.out.println("子线程任务")).start();
    }
}

​

三、Spring 环境下 Lambda 必踩坑 & 注意事项

1. 外部变量必须是「有效 final」

Lambda 内部不能修改外部局部变量,变量默认被隐式修饰为 final

​
public void test() {
    int num = 10;
    // 正确:只读
    new Thread(() -> System.out.println(num)).start();

    // 错误:尝试修改 num,编译报错
    // new Thread(() -> num = 20).start();
}

​

2. this 指向问题

  • 匿名内部类的 this:指向内部类实例
  • Lambda 的 this:指向当前所在的 Spring Bean 实例(和外部类一致)

3. 请求上下文丢失(Spring 最大坑)

如果 Lambda 运行在新线程 / 异步线程中,会丢失 RequestContextHolder(请求对象、Session、Token 等):

  • 原因:请求上下文绑定在当前主线程,子线程无法共享
  • 解决方案:提前把 Request/Token 取出,传入 Lambda

4. 事务失效

Lambda 内部新开子线程执行数据库操作,Spring 事务无法传播,事务会失效。

原则:事务逻辑尽量放在主线程,不要在 Lambda 子线程中写事务代码。

5. MyBatis-Plus Lambda 序列化要求

MyBatis-Plus 的 Lambda 构造器依赖序列化,实体类建议实现 Serializable 接口,避免分布式 / 代理场景报错。

6. 并行流 parallelStream 线程安全

stream().parallel() 并行流会启用多线程,操作非线程安全集合(如 ArrayList)会引发并发异常。


四、Lambda VS 匿名内部类 选型建议

  1. 优先用 Lambda
    • 简单回调、单方法接口、集合遍历、框架内置函数接口;
    • 代码简短、逻辑单一的场景。
  2. 用匿名内部类
    • 接口有多个方法(非函数式接口);
    • 需要重写多个方法、逻辑复杂、需要额外成员变量;
    • 需要区分 this 指向的场景。

五、总结

  1. Lambda 基于函数式接口,是 JDK8 语法,Spring Boot 项目全覆盖;
  2. 项目中最高频场景Stream 集合操作 + MyBatis-Plus Lambda 条件构造器
  3. Spring 框架大量底层接口(事件、过滤器、启动器、线程池)都设计为函数式接口,Lambda 可大幅精简代码;
  4. 线上开发重点规避:有效 final、请求上下文丢失、事务失效三大问题。

更多推荐