唐新国 發表於 2025-6-3 09:28:00

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

<h2 id="1-前言">1. 前言</h2>
<p>在我接触过的大部分Java项目中,经常看到使用<code>@Autowired</code>注解进行字段注入:</p>
<pre><code class="language-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;
}
</code></pre>
<p>在IDEA中,以上代码<code>@Autowired</code>注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,</p>
<p>翻译过来就是不建议使用字段注入。</p>
<p>关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用<code>@Resource</code>注解的,但这都不是该问题的本质。</p>
<p>该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项进行了检测并给以提示。</p>
<p>所以该提示背后的本质问题是:为什么Spring官方推荐构造器注入而不是字段注入?</p>
<h2 id="2-推荐构造器注入的理由">2. 推荐构造器注入的理由</h2>
<p>相比字段注入,构造器注入有以下几个优点:</p>
<ol>
<li>支持不可变性</li>
<li>依赖明确</li>
<li>单元测试友好</li>
<li>循环依赖检测前置,提前暴露问题</li>
</ol>
<h3 id="21-支持不可变性">2.1 支持不可变性</h3>
<p><strong>构造器注入</strong>允许将依赖字段声明为<code>final</code>,确保对象一旦创建,其依赖关系不再被修改。</p>
<p><strong>字段注入</strong>无法使用final,依赖可能在对象生命周期中被意外修改,破坏状态一致性。</p>
<p>构造器注入示例:</p>
<pre><code class="language-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;
    }
}
</code></pre>
<blockquote>
<p>说明:如果Spring版本是4.3或者更高版本且只有一个构造器,构造器上的<code>@Autowired</code>注解可以省略。</p>
</blockquote>
<h3 id="22-依赖明确">2.2 依赖明确</h3>
<p><strong>构造器注入</strong>通过在类的构造函数中显式声明依赖,并且强制要求在创建对象时必须提供所有必须的依赖项,</p>
<p>通过构造函数参数,使用者对该类的依赖一目了然。</p>
<p><strong>字段注入</strong>通过在类的字段上直接使用<code>@Autowired</code>注解注入依赖,依赖关系隐藏在类的内部,使用者无法直接看到该类的依赖。</p>
<h3 id="23-单元测试友好">2.3 单元测试友好</h3>
<p><strong>构造器注入</strong>允许直接通过new创建对象,无需依赖Spring容器或反射,降低了测试复杂度。</p>
<p><strong>字段注入</strong>需要依赖Spring容器或反射,增加了测试复杂度。</p>
<h3 id="24-循环依赖检测前置提前暴露问题">2.4 循环依赖检测前置,提前暴露问题</h3>
<p><strong>构造器注入</strong>在应用启动时直接暴露循环依赖,强制开发者通过设计解决问题。</p>
<p><strong>字段注入</strong>在应用启动时不会暴露循环依赖,直到实际调用时才可能暴露问题,增加调试难度。</p>
<p>示例:</p>
<p>假设项目中有以下两个Service存在循环依赖:</p>
<pre><code class="language-java">import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
      this.paymentService = paymentService;
    }
}
</code></pre>
<pre><code class="language-java">import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    private final OrderService orderService;

    public PaymentService(OrderService orderService) {
      this.orderService = orderService;
    }
}
</code></pre>
<p>此时启动项目会报错,抛出<code>org.springframework.beans.factory.BeanCurrentlyInCreationException</code>异常,</p>
<p>大致的异常信息如下所示:</p>
<blockquote>
<p>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?</p>
</blockquote>
<p>将以上两个Service修改为字段注入:</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}
</code></pre>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;
}
</code></pre>
<p>此时启动项目不会报错,可以启动成功。</p>
<h2 id="3-requiredargsconstructor注解的使用及原理">3. @RequiredArgsConstructor注解的使用及原理</h2>
<p>为了避免样板化代码或者为了简化代码,有的项目中可能会使用<code>@RequiredArgsConstructor</code>注解来代替显式的构造方法:</p>
<pre><code class="language-java">import lombok.RequiredArgsConstructor;

import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class OrderService {
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
}
</code></pre>
<p>接下来简单讲解下<code>@RequiredArgsConstructor</code>注解的原理。</p>
<p><code>@RequiredArgsConstructor</code>注解用于在编译时自动生成包含特定字段的构造方法。</p>
<p>字段筛选逻辑如下所示:</p>
<ol>
<li>被<code>final</code>修饰的未显式初始化的非静态字段</li>
<li>被<code>@NonNull</code>注解标记的未显式初始化的非静态字段</li>
</ol>
<p>示例:</p>
<pre><code class="language-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 = "男";
}
</code></pre>
<p>以上代码在编译时自动生成的构造方法如下所示:</p>
<pre><code class="language-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;
    }
}
</code></pre>
<p>从生成的构造方法可以看出:</p>
<p>1)如果字段被<code>lombok.NonNull</code>注解标记,在生成的构造方法内会做null值检查。</p>
<p>2)address字段虽然被<code>final</code>修饰,但因为已初始化,所以未包含在构造方法中。</p>
<p>3)email字段既没被<code>final</code>修饰,也没被<code>lombok.NonNull</code>注解标记,所以未包含在构造方法中。</p>
<p>4)city字段是静态字段,所以未包含在构造方法中。</p>
<p>5)sex字段虽然被<code>lombok.NonNull</code>注解标记,但因为已初始化,所以未包含在构造方法中。</p>
<h2 id="4-总结">4. 总结</h2>
<p><code>@Autowired</code>注解在IDEA中提示:Field injection is not recommended,其背后的本质问题是:</p>
<p>Spring官方推荐构造器注入而不是字段注入。</p>
<p>而Spring官方推荐构造器注入,是因为相比字段注入,构造器注入有以下几个优点:</p>
<ol>
<li>支持不可变性</li>
<li>依赖明确</li>
<li>单元测试友好</li>
<li>循环依赖检测前置,提前暴露问题</li>
</ol>
<p>使用构造器注入时,为了避免样板化代码或者为了简化代码,可以使用<code>@RequiredArgsConstructor</code>注解来代替显式的构造方法,</p>
<p>因为<code>@RequiredArgsConstructor</code>注解可以在编译时自动生成包含特定字段的构造方法。</p>
<p>至于项目中要不要使用构造器注入,使用显式的构造方法还是使用<code>@RequiredArgsConstructor</code>注解来简化代码,可以根据个人喜好及</p>
<p>团队规范自行决定。</p>
<blockquote>
<p>文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/zwwhnly/p/18907966
頁: [1]
查看完整版本: 聊聊@Autowired注解的Field injection is not recommended提示问题