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

1. 前言

在我接触过的大部分Java项目中,经常看到使用@Autowired注解进行字段注入:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;

    @Autowired
    private InventoryService inventoryService;
}

在IDEA中,以上代码@Autowired注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,

翻译过来就是不建议使用字段注入。

关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用@Resource注解的,但这都不是该问题的本质。

该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项进行了检测并给以提示。

所以该提示背后的本质问题是:为什么Spring官方推荐构造器注入而不是字段注入?

2. 推荐构造器注入的理由

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

  1. 支持不可变性
  2. 依赖明确
  3. 单元测试友好
  4. 循环依赖检测前置,提前暴露问题

2.1 支持不可变性

构造器注入 允许将依赖字段声明为final,确保对象一旦创建,其依赖关系不再被修改。

字段注入无法使用final,依赖可能在对象生命周期中被意外修改,破坏状态一致性。

构造器注入示例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    @Autowired
    public OrderService(PaymentService paymentService, InventoryService inventoryService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
}

说明:如果Spring版本是4.3或者更高版本且只有一个构造器,构造器上的@Autowired注解可以省略。

2.2 依赖明确

构造器注入通过在类的构造函数中显式声明依赖,并且强制要求在创建对象时必须提供所有必须的依赖项,

通过构造函数参数,使用者对该类的依赖一目了然。

字段注入 通过在类的字段上直接使用@Autowired注解注入依赖,依赖关系隐藏在类的内部,使用者无法直接看到该类的依赖。

2.3 单元测试友好

构造器注入允许直接通过new创建对象,无需依赖Spring容器或反射,降低了测试复杂度。

字段注入需要依赖Spring容器或反射,增加了测试复杂度。

2.4 循环依赖检测前置,提前暴露问题

构造器注入在应用启动时直接暴露循环依赖,强制开发者通过设计解决问题。

字段注入在应用启动时不会暴露循环依赖,直到实际调用时才可能暴露问题,增加调试难度。

示例:

假设项目中有以下两个Service存在循环依赖:

java 复制代码
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}
java 复制代码
import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    private final OrderService orderService;

    public PaymentService(OrderService orderService) {
        this.orderService = orderService;
    }
}

此时启动项目会报错,抛出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修改为字段注入:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;
}

此时启动项目不会报错,可以启动成功。

3. @RequiredArgsConstructor注解的使用及原理

为了避免样板化代码或者为了简化代码,有的项目中可能会使用@RequiredArgsConstructor注解来代替显式的构造方法:

java 复制代码
import lombok.RequiredArgsConstructor;

import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class OrderService {
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
}

接下来简单讲解下@RequiredArgsConstructor注解的原理。

@RequiredArgsConstructor注解用于在编译时自动生成包含特定字段的构造方法。

字段筛选逻辑如下所示:

  1. 被final修饰的未显式初始化的非静态字段
  2. 被@NonNull注解标记的未显式初始化的非静态字段

示例:

java 复制代码
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class User {
    private final String name;

    @NonNull
    private Integer age;

    private final String address = "";

    private String email;

    private static String city;

    @NonNull
    private String sex = "男";
}

以上代码在编译时自动生成的构造方法如下所示:

java 复制代码
public User(String name, @NonNull Integer age) {
    if (age == null) {
        throw new NullPointerException("age is marked non-null but is null");
    } else {
        this.name = name;
        this.age = age;
    }
}

从生成的构造方法可以看出:

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官方推荐构造器注入,是因为相比字段注入,构造器注入有以下几个优点:

  1. 支持不可变性
  2. 依赖明确
  3. 单元测试友好
  4. 循环依赖检测前置,提前暴露问题

使用构造器注入时,为了避免样板化代码或者为了简化代码,可以使用@RequiredArgsConstructor注解来代替显式的构造方法,

因为@RequiredArgsConstructor注解可以在编译时自动生成包含特定字段的构造方法。

至于项目中要不要使用构造器注入,使用显式的构造方法还是使用@RequiredArgsConstructor注解来简化代码,可以根据个人喜好及

团队规范自行决定。

文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

相关推荐
\光辉岁月/12 分钟前
3.包装类
java·开发语言·包装类
BeiGuTing_code13 分钟前
Java动态数组(一)
java·数据结构·windows
我命由我1234523 分钟前
软件工程概念 - 可插拔性
java·后端·学习·java-ee·系统架构·软件工程·学习方法
木易 士心1 小时前
Java 后端驱动远程手机自动化:ADB + Python + 内网穿透实战全解
java·后端·智能手机·自动化
十年Java程序媛1 小时前
JVM 内存区域与对象内存布局|new 对象底层流程,对象头 MarkWord 详解
java·jvm·spring boot
用户094248568031 小时前
第2章:Windows、WSL2 与 Docker 环境搭建Redis
java·jvm
小物学长1 小时前
HJ212-2017 对接与环境在线监测平台设计:报文、CRC、应答、预警和传输有效率
java
171320330计算机毕设编程1 小时前
计算机毕业设计 基于springboot+vue的在线观鸟视频播放网站 LW PPT
java·大数据·vue.js·spring boot·算法·课程设计
IT枫斗者枫哥1 小时前
JDBC事务实测:操作日志写入失败时,让客户修改一起回滚
java
OxYGC1 小时前
Spring 面经系列 · 第 1 篇:Bean 生命周期、单例语义、依赖注入与线程安全
java·安全·spring