大家好,我是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-name、firstName、FIRST_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.servlet → jakarta.servlet,javax.persistence → jakarta.persistence,javax.validation → jakarta.validation,javax.annotation → jakarta.annotation,javax.transaction → jakarta.transaction,javax.mail → jakarta.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 生成包含 type、title、status、detail、instance 字段的标准响应,机器可读、跨系统统一。
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。
如果你觉得这篇文章有帮助,点赞关注,点点赞~