这些Spring Boot写法已经过时了!

大家好,我是HLAIA光子。

我把 Spring Boot 从 2.x 到 3.x 再到 4.x 的演进过程中,那些曾经是"标准写法"、现在被官方明确不推荐甚至直接移除的用法,做了一个系统梳理。读完之后,你应该能快速排查自己的项目里有没有这些"技术债",并且知道每一条该怎么改。

说实话,做这份调研的时候我自己也挺震惊的。有些是教科书级别的写法,现在官方直接告诉你别用了,Spring 团队在加速推进现代化,该废弃的废弃,该移除的移除。

代码层

@Autowired 字段注入

这可能是覆盖面最广的一条。

java 复制代码
// 旧写法------不推荐
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepo;

    @Autowired
    private PaymentGateway paymentGateway;
}

当年学 Spring 的时候,几乎所有教程都这么教。但 Spring 官方现在只认可两种注入方式:构造器注入Setter 注入。字段注入已经不作为正式的 DI 选项了。

推荐写法:

java 复制代码
@Service
public class OrderService {
    private final OrderRepository orderRepo;
    private final PaymentGateway paymentGateway;

    // Spring 4.3 起,单一构造器无需 @Autowired
    public OrderService(OrderRepository orderRepo, PaymentGateway paymentGateway) {
        this.orderRepo = orderRepo;
        this.paymentGateway = paymentGateway;
    }
}

配合 Lombok 更简洁:

java 复制代码
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepo;
    private final PaymentGateway paymentGateway;
}

字段注入有六个硬伤,

  • 空指针风险 :可以 new OrderService() 创建实例,依赖全是 null,运行时才爆炸。

  • 无法不可变 :字段不能用 final,依赖随时可被篡改。

  • 隐藏依赖:从 API 签名看不出类需要什么,依赖关系不透明。

  • 违反单一职责 :加 @Autowired 太容易了,类膨胀你根本没感觉,换成构造器注入,参数多了你自然会想去重构。

  • 测试困难:必须依赖反射,注入失败时可能静默通过。

  • 框架耦合:依赖Spring容器。

说白了,字段注入的唯一优点就是省事,但在项目变大之后会变成你的债务。

@Value

@Value 适合注入单个简单值,但拿来管理一组相关配置就很痛苦。

java 复制代码
@Value("${my.service.host}")
private String host;

@Value("${my.service.port}")
private int port;

每个字段单独声明,散落各处,拼错属性名只在运行时才炸。推荐用 @ConfigurationProperties

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "my.service")
@Validated
public class MyServiceProperties {
    @NotBlank
    private String host;

    @Min(1) @Max(65535)
    private int port;

    @NotNull
    private Duration timeout;
}

@ConfigurationProperties 支持类型安全绑定**、Bean Validation 校验 、**宽松绑定,first-namefirstNameFIRST_NAME 全部映射到同一字段,,还能自动生成元数据给 IDE 做自动补全。@Value 这些全做不到,肺雾

@Transactional

@Transactional 是 Spring 里用得最多也最容易用错的注解之一。这里有六个很容易踩的坑。

坑一:只读方法不加 readOnly。 不加的话,即使只是查数据,也会开完整事务、加写锁、占连接池。这放到高并发场景下你不就炸了吗。

坑二:同类内部调用。 Spring 基于代理的 AOP 拦截不了同一个类内部的方法调用,你在一个方法里调同一个类的另一个 @Transactional 方法,事务注解会被静默忽略。这事特别隐蔽,表面看不出任何异常。

坑三:标在 private 方法上。 代理无法拦截 private 方法,注解完全无效。只应用于 public 或 protected 方法。

坑四:吞掉异常不回滚。 在事务方法里 catch 了异常但不抛出,事务正常提交,数据写入了但逻辑是错的。

坑五:忘记 rollbackFor。 默认只对 RuntimeException 回滚,受检异常不会触发回滚。大多数场景应该写 @Transactional(rollbackFor = Exception.class)

坑六:滥用 REQUIRES_NEW。 会挂起外层事务开新事务,影响性能还可能导致部分提交。大多数场景 REQUIRED 就够了。

System.out.println

这个我是真没想到,因为以前初次接触java,用java做算法题的时候,一直在System.out.println,leetcode里面打输出做调试;acm模式的竞赛,比如牛客,就用它来写输出,已经成习惯了。

它没有日志级别、不可配置、同步阻塞 I/O、没有时间戳和线程信息、完全脱离 Spring Boot 的日志基础设施。

用 SLF4J + Lombok:

java 复制代码
@Slf4j
public class MyService {
    public void login(Long userId, String ip) {
        log.info("User {} logged in from {}", userId, ip);
    }
}

注意用参数化日志log.info("User {} logged in", userId))而不是字符串拼接(log.info("User " + userId + " logged in"))。即使 INFO 级别被过滤掉,字符串拼接照样执行,白白浪费性能。

不过写题用 System.out.println 我觉得还是没有任何问题,因为写题不涉及工程;况且OJ也不会给你提供slf4j这些依赖。

框架层

WebSecurityConfigurerAdapter

这是 Spring Security 最大的 API 变更。Spring Security 5.7 废弃,6.0 直接移除 。如果你从 Spring Boot 2.x 升级到 3.x,代码里还继承 WebSecurityConfigurerAdapter,编译都过不了。

旧写法(已不可用):

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated();
    }
}

新写法:

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
        );
        return http.build();
    }
}

注意配套的 API 也全换了:antMatchers()requestMatchers()authorizeRequests()authorizeHttpRequests()。这些是跟着 WebSecurityConfigurerAdapter 一起被清理的。

RestTemplate

Spring 官方博客 2025 年 9 月发布了 HTTP 客户端的现状说明,时间线很明确:

  • Spring Framework 7.0(2025.11):宣布即将废弃 RestTemplate
  • Spring Framework 7.1(2026.11) :正式 @Deprecate 并标记移除
  • Spring Framework 8.0:完全移除

这不是"可能发生的事",这是已经公布的计划。

推荐替代方案是 RestClient(Spring 6.1+):

java 复制代码
RestClient restClient = RestClient.create();
User user = restClient.get()
    .uri("https://api.example.com/users/1")
    .retrieve()
    .body(User.class);

API 风格和 RestTemplate 类似,但更现代。Spring Boot 4.0 已经有专用 starter:spring-boot-starter-restclient

如果你需要响应式场景,用 WebClient。RestTemplate 只应该在遗留项目里出现了。

WebMvcConfigurerAdapter

WebMvcConfigurerAdapter 从 Spring 5.0 就废弃了,原因是 Java 8 的接口默认方法让 Adapter 类不再必要。直接 implements WebMvcConfigurer 就行。

但这里有个陷阱:有些人从 WebMvcConfigurerAdapter 迁移到了 WebMvcConfigurationSupport,这会完全禁用 Spring Boot 的 MVC 自动配置,静态资源、默认消息转换器、格式化器全部丢失。正确做法是 implements WebMvcConfigurer

同样,不要在 Spring Boot 项目上加 @EnableWebMvc,它也会禁用自动配置。这两个陷阱新手特别容易踩。

生态层

javax → jakarta

Spring Boot 3.0 把所有 javax.* 包迁移到 jakarta.*。这个必须改,不改就编译不通过。

受影响的包包括:javax.servletjakarta.servletjavax.persistencejakarta.persistencejavax.validationjakarta.validationjavax.annotationjakarta.annotationjavax.transactionjakarta.transactionjavax.mailjakarta.mail

迁移手段很简单,IDE 全局搜索替换 import 语句就行。如果项目大,可以用 OpenRewrite 做自动化重构。但要特别注意,第三方依赖也必须升级到 Jakarta EE 9+ 兼容版本,否则会类冲突。

JUnit 4

旧写法 @RunWith(SpringRunner.class) 是 JUnit 4 时代的。现在 JUnit 5 中,@SpringBootTest 已经内置注册了 SpringExtension,单独一个注解。

java 复制代码
// 旧
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest { }

// 新
@SpringBootTest
class UserServiceTest { }

其他

spring.factories → AutoConfiguration.imports。 Spring Boot 2.7 起,自动配置注册从 META-INF/spring.factories 迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。如果你写过自定义 starter,这个要注意。

异常处理标准化。 Spring 6 / Boot 3 引入了 ProblemDetail ,提供 RFC 7807 标准化的错误响应格式。以前全局异常处理返回 Map<String, String>,现在可以用 ProblemDetail 生成包含 typetitlestatusdetailinstance 字段的标准响应,机器可读、跨系统统一。

JPA 的 N+1 陷阱。 @OneToMany(fetch = FetchType.LAZY) 配合遍历访问关联对象时,每个父记录会触发一次 SELECT。解决方案有三种:JPQL 的 JOIN FETCH、声明式的 @EntityGraph、Hibernate 特有的 @BatchSize。还有 N+1 删除问题:deleteByStatus("xxx") 会对每行执行 SELECT + DELETE,应该用 @Modifying + @Query 做批量删除。

虚拟线程

Spring Boot 3.2+ 支持虚拟线程,一行配置开启:

properties 复制代码
spring.threads.virtual.enabled=true

这让传统的"每请求一线程"模型也能处理数千并发,不需要引入 WebFlux 的复杂性。有了虚拟线程,Spring MVC 仍然是默认推荐。WebFlux 只在流式负载和极高并发场景有明确优势。不要为了"潮流"引入响应式的复杂性。

写在最后

这篇报告梳理下来,我最深的感受是:Spring 生态的现代化速度在加快,该废弃的废弃,该移除的移除,不再向后兼容那些有更好替代的旧模式。

如果你正在维护一个两三年前的 Spring Boot 项目,想升级到 Spring Boot 3.x 甚至 4.x 的时候,就会出现这些兼容性问题。

其实迁移不迁移 还是以公司的代码规范为准,有些组就是用的旧规范,设计的时候可能就没想过以后要迁移,估计是遵循"能跑就不要去动"这个原则hhh。

如果你觉得这篇文章有帮助,点赞关注,点点赞~

相关推荐
i220818 Faiz Ul1 小时前
宠物猫之猫咖管理系统|基于java + vue宠物猫之猫咖管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·宠物猫之猫咖管理系统
alwaysrun1 小时前
Zig实现Windows下进程监控
后端·编程语言
Nyarlathotep01132 小时前
定时线程池:ScheduledThreadPoolExecutor
java·后端
i220818 Faiz Ul2 小时前
二手交易系统|基于springboot + vue二手交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·二手交易系统
逍遥德2 小时前
SpringBoot自带TaskScheduler 接口实现定时任务的动态增、删、启、停。
java·spring boot·后端·中间件
jieyucx3 小时前
Go 语言核心关键字:defer 深度解析与实战避坑
开发语言·后端·golang·defer
南囝coding3 小时前
Anthropic 内部数百个 Claude Code Skills,他们总结的这套方法值得看
前端·后端
Rust研习社4 小时前
Ubuntu 全面拥抱 Rust 后,我意识到 Rust 社区要变了
linux·服务器·开发语言·后端·ubuntu·rust
小江的记录本4 小时前
【AI大模型选型指南】《2026年5月(最新版)国内外主流AI大模型选型指南》(个人版)
前端·人工智能·后端·ai·aigc·ai编程·ai写作