@Autowired与构造器注入区别,为什么spring推荐使用构造注入而不是Autowired?

目录

1.简介

2.了解两种注入方式的全过程

2.1 Autowired字段注入

2.2 构造函数注入

3.使用autowired注解注入有以下问题

3.1空指针异常

3.2测试不友好

4.使用Lombok去简化构造函数注入的臃肿代码

5.小结

5.1注解注入

5.2构造函数注入


1.简介

使用Spring开发时,我们通常有两种依赖注入的方式,基于注解@Autowired的依赖注入和基于构造函数的依赖注入。

用IDEA开发过程中,如果使用@Autowired注入,通常会有如下警告:

这真的是我们代码写错了吗?其实不然,只是用AutoWired注解去进行注入会产生一些隐形问题。

2.了解两种注入方式的全过程

在Spring框架中,依赖注入(DI)是一种核心功能,它允许对象通过构造函数、setter方法或字段直接定义其依赖关系。这里,我们专注于两种常见的注入方式:字段注入(通过@Autowired注解)和构造函数注入。理解这两种注入方式的全过程对于编写可维护、可测试和健壮的Spring应用至关重要。

2.1 Autowired字段注入

字段注入是Spring允许的依赖注入的简便方式,它直接在类的字段上使用@Autowired注解。这种方式的注入流程相对简单直接:

注入全过程

  1. 启动阶段:当Spring应用启动时,Spring容器开始创建并管理bean。它扫描项目中的类,查找带有@Component、@Service、@Controller等注解的类,并为这些类创建bean。
  2. 依赖查找:在字段上使用@Autowired时,Spring容器在运行时自动检测系统中可用的匹配该字段类型的bean。
  3. 自动注入:容器将找到的bean直接注入到被@Autowired标记的字段中。这一过程通常在bean的构造函数执行之后发生,意味着新创建的对象的字段将在稍后的时间点被Spring自动填充。
  4. 后处理:一旦所有的字段被注入后,bean才被认为是完全初始化的,并且随后会触发任何回调方法,如标记有@PostConstruct的方法。

优点与缺点
优点:

  • 简单易用:直接在字段上标注@Autowired,无需额外的构造函数或setter方法。
  • 代码简洁:减少了模板代码,特别是在依赖数量不多时。

缺点:

  • 不支持不可变性:由于字段是在对象创建后注入,不能声明为final。
  • 降低可测试性:不使用Spring容器时,例如在单元测试中,很难替换依赖项。
  • 违反了Spring推荐的最佳实践:构造函数注入是推荐的方式,因为它支持不可变性,并且依赖在使用前总是被初始化。

2.2 构造函数注入

构造函数注入是将依赖作为参数传递给类的构造函数。Spring容器使用这些参数来创建bean实例。

注入全过程

  1. 启动阶段:与字段注入相同,Spring首先创建所有的bean定义,并扫描标注了Spring注解的类。
  2. 依赖解析:对于构造函数注入,当创建类的实例时,Spring容器查看类的构造函数参数,确定需要注入哪些依赖。
  3. 依赖注入:Spring容器然后实例化这些依赖(如果它们尚未创建)并通过构造函数注入到正在创建的bean中。这确保了在对象完全构造之前所有必需的依赖都已经提供。
  4. 对象初始化:一旦所有构造函数参数被注入,对象被实例化,并且所有设置方法和回调都被调用。

优点与缺点
优点:

  • 支持不可变性:依赖可以被声明为final,确保了一旦构造对象后不会改变。
  • 促进了更好的软件设计:构造函数注入强制要求依赖在构造对象时必须存在,从而保证了bean的依赖不会是null。
  • 提高可测试性:容易在测试中通过构造函数替换依赖,特别是使用Mock对象时。

缺点:

  • 构造函数臃肿:如果一个类有多个依赖,构造函数可能会变得很长,这可能使代码更难阅读和维护。
  • 可能需要更多配置:特别是在存在多个构造函数或需要特定的配置来选择适当构造函数时。

3.使用autowired注解注入有以下问题

3.1空指针异常

我们来看一下一个简单例子

public class Car {

    @Autowired
    private Wheel wheel;

    public void run() {
        wheel.roll();
    }
}

假设测试代码如下,会出现以下异常:

Car car = new Car();
car.run();// -> NullPointerException

出现这种问题的关键在于,Car允许创建无状态的对象,也就是说在构建Car时允许Wheel为空。

我们来看看使用构造函数注入会不会出现这个问题

public class Car {

    private final Wheel wheel;

    public Car(Wheel wheel) {
        Assert.notNull(wheel,"Wheel must not be null");
        this.wheel = wheel;
    }

    public void run() {
        wheel.roll();
    }

}

这样我们就有如下优点:

 在创建Car对象的时候,强制依赖Wheel对象,确保创建Car对象时每个对象都是有效状态。

构造器中可以添加对象初始化的校验逻辑

可以清楚的区分对象是通过setter方法注入的(非final对象)还是通过强制依赖注入的(final对象)

构造注入代码变得臃肿?

或许有的读者可能会说,构造注入的话,如果依赖的对象很多,构造器参数就会很多,显得代码很臃肿。这种情况的话,就要考虑这个类是符合足单一职责原则了,将这个类拆分为多个类。

而且使用@Autowired的自动装配会让依赖对象变得很容易,随着项目的迭代,自动注入的对象可能会变得很多,但是使用构造注入,构造器就会变得很臃肿,提醒你代码里有bad smell了,需要拆分或重构代码了。

还有一个问题是@Autowired注入的对象无法使用final关键字,因为final对象必须在构造器中初始化。 

3.2测试不友好

当我们使用注解去注入时,确实会十分方便和节省许多代码的编写,但是这样的话我们的单元测试该怎么写呢?

        Wheel wheel = Mock(Wheel);
        Car car = new Car(wheel);
        car.run();

通过反射注入到Car对象里,我们的单元测试代码就会显得很繁琐,或者在Car对象里提供一个Wheel的setter方法?这样代码不是很优雅。

如果是构造注入,单元测试就会变成如下:

        Wheel wheel = Mock(Wheel);
        Car car = new Car(wheel);
        car.run();

单元测试代码就会变得很优雅,而且在后续的开发中,如果Car对象添加了强制依赖的Tank对象,单元测试也不会出现没有设置的强制依赖项。

Spring 的DI设计模式,是将依赖关系的创建和类本身分离,将依赖关系创建的职责交给了类注入器做,允许程序设计的松耦合,并遵循单一职责原则和依赖反转原则。因此使用@Autowired自动装配的字段在Spring容器之外无法使用(不包含通过反射设置对象的方式)。

构造注入可以在受影响的类中轻松表明对象的依赖关系,但是@Autowired的自动装配其实对外隐藏了这些依赖关系,需要到对应的类中查看代码才能明确依赖。

4.使用Lombok去简化构造函数注入的臃肿代码

Lombok的依赖如下:

       <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

简化之后代码如下:

5.小结

 最后我们来总结一下两者的优缺点

5.1注解注入

++ 写更少的代码
-- 代码变得不安全
-- 单元测试会比较复杂
-- 无法使用fianl对象
-- 违反单一职责原则变得很容易
-- 对受影响的类隐藏自己的依赖关系

5.2构造函数注入

++ 更安全的代码
++ 测试友好
++ 依赖添加代价较高,显式的表明代码的bad smell
++ 在受影响的类中显式的表明依赖关系
-- 需要写更多的业务代码(可以通过Lombok解决)

### @Autowired 注解构造器注入区别Spring 框架中,依赖注入可以通过多种方式进行配置,其中 `@Autowired` 注解构造器注入是最常见的两种方式。 #### 使用场景差异 对于 `@Autowired` 注解而言,通常用于字段、setter 方法以及构造函数参数上的自动装配。这种方式简单直观,在早期版本的 Spring 中被广泛采用[^1]。然而,随着框架的发展,官方更倾向于推荐使用构造器注入来处理对象间的依赖关系[^2]。 #### 构造器注入的优势 当应用构造器注入时,所有的必需组件都作为参数传递给类的构造方法,并立即初始化实例变量。这不仅使得 Bean 的创建过程更加清晰明了,而且有助于编写单元测试代码,因为可以在测试环境中轻松替换掉实际的服务实现[^3]。 ```java @RestController @RequestMapping("/test") public class TestController { private final AService aService; private final BService bService; // 显式声明依赖关系,便于理解维护 public TestController(AService aService, BService bService) { this.aService = aService; this.bService = bService; } } ``` 此外,利用 Lombok 库中的 `@RequiredArgsConstructor` 可进一步简化语法糖,自动生成带有 `@Autowired` 的构造函数: ```java import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/test") @RequiredArgsConstructor(onConstructor_ = {@Autowired}) public class TestController { private final AService aService; private final BService bService; } ``` #### 性能考量 尽管两者都能有效完成 DI 工作,但在性能方面可能存在细微差别。由于构造器注入会在对象构建初期即完成所有必要的资源分配工作,因此理论上可能会带来更好的启动速度表现;相反地,如果过度依赖于运行期间动态解析并注入服务,则可能增加一定的开销[^4]。 综上所述,虽然 `@Autowired` 提供了一种便捷的方式来定义 bean 之间的关联性,但从长远来看,遵循最佳实践指南——尽可能多地采纳构造器注入模式——可以显著改善应用程序的设计质量灵活性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值