为什么在IDEA使用@Autowired会报黄?

其实就两个:

  • 一个final关键字;
  • 一个@RequiredArgsConstructor注解

够了,就这两个,你看看下面的:

复制代码
@RequiredArgsConstructor
@Service
@Slf4j
public class OrderApplicationService {
    private final OrderDomainService orderDomainService;
    private final TransactionTemplate transactionTemplate;
}

具体的,我下面会说明。我们需要先看看为啥不推荐使用@Autowired了。

你的IDEA里给一个字段加上@Autowired,会看到一条黄色波浪线。鼠标悬停上去,提示信息是:Field injection is not recommended。

这不是IDEA的问题。IntelliJ这条检查规则,依据的是Spring官方说明。Spring团队在官方文档里明确写了,推荐使用构造器注入。

那@Autowired字段注入有啥问题?

先看一段典型的字段注入代码:

复制代码
@Service
public class OrderService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private OrderRepository orderRepository;

}

这种写法在老项目里到处都是。每个依赖一行注解加一行声明,看着还行。但是问题在两个地方。

依赖没法用final修饰

字段注入的工作原理是:Spring先创建对象实例,再通过反射把依赖设置到字段上。赋值发生在对象构造之后,所以这些字段不能声明为final

不能用final,意味着依赖可以在运行时被修改:

复制代码
@Service
public class OrderService {

    @Autowired
    private UserRepository userRepository;

    public void someMethod() {
        // 编译不报错,运行时也不会立刻出问题
        this.userRepository = null;
    }
}

下次调用userRepository的方法时,直接空指针异常。

换成构造器注入,字段可以声明为final

复制代码
@Service
public class OrderService {

    private final UserRepository userRepository;

    public OrderService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void someMethod() {
        // 编译直接报错:Cannot assign a value to final variable
        this.userRepository = null;
    }
}

依赖一旦注入就不可更改,编译器直接拦住。

脱离容器单元测试不好搞

字段注入的类,所有依赖都通过Spring容器的反射机制设置。如果不启动容器,直接new一个对象:

复制代码
// 所有依赖都是null
OrderService service = new OrderService();

想让测试跑起来,要么用@SpringBootTest启动整个容器,要么用反射工具手动设值。

构造器注入的类可以直接实例化:

复制代码
OrderService service = new OrderService(
    mockUserRepository,
    mockOrderRepository,
    mockNotificationService
);

不依赖框架,不启动容器,直接传Mock对象进去测业务逻辑。

我们来看Spring官方是怎么说的

Spring Framework官方文档里有一段专门讨论这个问题,原文如下:

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state.

这段话出自Spring Framework 6.0的官方文档,是框架团队的明确立场,翻译一下关键信息:

  • Spring团队主张使用构造器注入
  • 构造器注入让组件成为不可变对象
  • 确保必需的依赖不会为null
  • 组件返回给调用方时,一定处于完全初始化的状态

Spring从4.3版本开始还做了一个改动:如果一个类只有一个构造器,@Autowired注解可以省略,Spring会自动用这个构造器进行注入。这个设计本身就是在引导开发者往构造器注入迁移。

另外,Spring官方文档里也提到了这一点:大量的构造器参数是一个代码坏味道,说明这个类承担了太多职责,应该重构。

构造器注入的写法

手写构造器

最基础的方式,不依赖额外工具:

复制代码
@Service
public class OpenAppService {

    private final OpenAppMapper openAppMapper;

    public OpenAppService(OpenAppMapper openAppMapper) {
        this.openAppMapper = openAppMapper;
    }
}

类里只有一个构造器,Spring自动识别并注入,不需要加@Autowired。字段用final修饰,保证不可变。

这种写法在依赖少的时候足够用。一旦依赖超过三四个,手写构造器和赋值语句会显得比较啰嗦,「代码颜值也不高」。

该@RequiredArgsConstructor + final出场了

目前主流项目的标准做法:

复制代码
@RequiredArgsConstructor
@Service
@Slf4j
public class OrderApplicationService {
    private final OrderDomainService orderDomainService;
    private final TransactionTemplate transactionTemplate;
}

@RequiredArgsConstructorLombok提供的注解,编译期自动生成一个包含所有final字段的构造器。效果和手写完全一样,但省掉了模板代码。

新加一个依赖,只需要加一行private final字段声明。Lombok自动把它加到构造器参数里,不需要手动维护构造器。

整个项目统一这种写法,代码风格干净一致。

对比速查表

维度 @Autowired字段注入 构造器注入
不可变性 不支持final 支持final
单元测试 需要反射或启动容器 直接new,传Mock对象
依赖可见性 分散在各个字段上 集中在构造器参数
空指针风险 运行时可能为null 容器启动时校验
Spring官方态度 不推荐 明确推荐

小结

适当的约束比完全的自由更有价值。 final是约束,构造器参数太长时的不适感也是约束。这些约束会推着你在更早的阶段发现设计问题。

如果你在维护一个老项目,里面到处都是@Autowired字段注入,不用急着一次性改完。新代码统一用构造器注入,老代码改动时顺手切过来。

希望这篇内容可以帮到你。

相关推荐
米糕闯编程2 小时前
IDEA新建springboot项目
spring boot·后端·intellij-idea
我登哥MVP2 小时前
【Spring6笔记】 - 15 - Spring中的八大设计模式
java·spring boot·笔记·spring·设计模式·intellij-idea
蚰蜒螟2 小时前
深入剖析 Tomcat 9.0.53 源码:Web 资源管理与类加载机制
java·前端·tomcat
黑不溜秋的2 小时前
C++ vscode 常用插件
ide·vscode·编辑器
m0_475064502 小时前
Spring AI文档切片
java·人工智能·spring
我登哥MVP2 小时前
【SpringMVC笔记】 - 1 - SpringMVC入门
java·spring boot·spring·tomcat·maven·intellij-idea·springmvc
Arva .2 小时前
Spring 事务传播机制 速记
java·数据库·spring
0xDevNull2 小时前
Spring Boot 2.0动态多数据源切换实战教程
java·后端
语戚2 小时前
力扣 2463. 最小移动总距离 —— 动态规划 & 贪心排序全解(Java 实现)
java·算法·leetcode·贪心算法·动态规划·力扣·dp