聊聊@Autowired注解的Field injection is not recommended提示问题

[复制链接]
发表于 2025-6-3 10:46:29 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1. 前言

在我接触过的大部分Java项目中,常常看到使用@Autowired注解举行字段注入:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class OrderService {
  5.     @Autowired
  6.     private PaymentService paymentService;
  7.     @Autowired
  8.     private InventoryService inventoryService;
  9. }
复制代码
在IDEA中,以上代码@Autowired注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,
翻译过来就是不发起使用字段注入。
关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用@Resource注解的,但这都不是该问题的本质。
该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项举行了检测并给以提示。
所以该提示背后的本诘责题是:为什么Spring官方推荐构造器注入而不是字段注入?
2. 推荐构造器注入的来由

相比字段注入,构造器注入有以下几个优点:

  • 支持不可变性
  • 依靠明确
  • 单元测试友爱
  • 循环依靠检测前置,提前暴露问题
2.1 支持不可变性

构造器注入答应将依靠字段声明为final,确保对象一旦创建,其依靠关系不再被修改。
字段注入无法使用final,依靠可能在对象生命周期中被意外修改,破坏状态一致性。
构造器注入示例:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class OrderService {
  5.     private final PaymentService paymentService;
  6.     private final InventoryService inventoryService;
  7.     @Autowired
  8.     public OrderService(PaymentService paymentService, InventoryService inventoryService) {
  9.         this.paymentService = paymentService;
  10.         this.inventoryService = inventoryService;
  11.     }
  12. }
复制代码
说明:如果Spring版本是4.3大概更高版本且只有一个构造器,构造器上的@Autowired注解可以省略。
2.2 依靠明确

构造器注入通过在类的构造函数中显式声明依靠,而且逼迫要求在创建对象时必须提供所有必须的依靠项,
通过构造函数参数,使用者对该类的依靠一目了然。
字段注入通过在类的字段上直接使用@Autowired注解注入依靠,依靠关系隐蔽在类的内部,使用者无法直接看到该类的依靠。
2.3 单元测试友爱

构造器注入答应直接通过new创建对象,无需依靠Spring容器或反射,降低了测试复杂度。
字段注入必要依靠Spring容器或反射,增加了测试复杂度。
2.4 循环依靠检测前置,提前暴露问题

构造器注入在应用启动时直接暴露循环依靠,逼迫开辟者通过计划解决问题。
字段注入在应用启动时不会暴露循环依靠,直到实际调用时才可能暴露问题,增加调试难度。
示例:
假设项目中有以下两个Service存在循环依靠:
  1. import org.springframework.stereotype.Service;
  2. @Service
  3. public class OrderService {
  4.     private final PaymentService paymentService;
  5.     public OrderService(PaymentService paymentService) {
  6.         this.paymentService = paymentService;
  7.     }
  8. }
复制代码
  1. import org.springframework.stereotype.Service;
  2. @Service
  3. public class PaymentService {
  4.     private final OrderService orderService;
  5.     public PaymentService(OrderService orderService) {
  6.         this.orderService = orderService;
  7.     }
  8. }
复制代码
此时启动项目会报错,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException异常,
大致的异常信息如下所示:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?
将以上两个Service修改为字段注入:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class OrderService {
  5.     @Autowired
  6.     private PaymentService paymentService;
  7. }
复制代码
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class PaymentService {
  5.     @Autowired
  6.     private OrderService orderService;
  7. }
复制代码
此时启动项目不会报错,可以启动乐成。
3. @RequiredArgsConstructor注解的使用及原理

为了避免样板化代码大概为了简化代码,有的项目中可能会使用@RequiredArgsConstructor注解来代替显式的构造方法:
  1. import lombok.RequiredArgsConstructor;
  2. import org.springframework.stereotype.Service;
  3. @RequiredArgsConstructor
  4. @Service
  5. public class OrderService {
  6.     private final PaymentService paymentService;
  7.     private final InventoryService inventoryService;
  8. }
复制代码
接下来简单解说下@RequiredArgsConstructor注解的原理。
@RequiredArgsConstructor注解用于在编译时自动生成包含特定字段的构造方法。
字段筛选逻辑如下所示:

  • 被final修饰的未显式初始化的非静态字段
  • 被@NonNull注解标记的未显式初始化的非静态字段
示例:
  1. import lombok.NonNull;
  2. import lombok.RequiredArgsConstructor;
  3. @RequiredArgsConstructor
  4. public class User {
  5.     private final String name;
  6.     @NonNull
  7.     private Integer age;
  8.     private final String address = "";
  9.     private String email;
  10.     private static String city;
  11.     @NonNull
  12.     private String sex = "男";
  13. }
复制代码
以上代码在编译时自动生成的构造方法如下所示:
  1. public User(String name, @NonNull Integer age) {
  2.     if (age == null) {
  3.         throw new NullPointerException("age is marked non-null but is null");
  4.     } else {
  5.         this.name = name;
  6.         this.age = age;
  7.     }
  8. }
复制代码
从生成的构造方法可以看出:
1)如果字段被lombok.NonNull注解标记,在生成的构造方法内会做null值检查。
2)address字段虽然被final修饰,但由于已初始化,所以未包含在构造方法中。
3)email字段既没被final修饰,也没被lombok.NonNull注解标记,所以未包含在构造方法中。
4)city字段是静态字段,所以未包含在构造方法中。
5)sex字段虽然被lombok.NonNull注解标记,但由于已初始化,所以未包含在构造方法中。
4. 总结

@Autowired注解在IDEA中提示:Field injection is not recommended,其背后的本诘责题是:
Spring官方推荐构造器注入而不是字段注入。
而Spring官方推荐构造器注入,是由于相比字段注入,构造器注入有以下几个优点:

  • 支持不可变性
  • 依靠明确
  • 单元测试友爱
  • 循环依靠检测前置,提前暴露问题
使用构造器注入时,为了避免样板化代码大概为了简化代码,可以使用@RequiredArgsConstructor注解来代替显式的构造方法,
由于@RequiredArgsConstructor注解可以在编译时自动生成包含特定字段的构造方法。
至于项目中要不要使用构造器注入,使用显式的构造方法还是使用@RequiredArgsConstructor注解来简化代码,可以根据个人喜好及
团队规范自行决定。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表