Spring注解式依赖注入

在Spring框架中,依赖注入(Dependency Injection, DI)是核心功能之一,通过DI可以使得组件之间的依赖关系解耦,方便管理和维护。

依赖注入(DI)是一种软件设计模式,用于实现对象之间的松耦合。这种模式通过将对象的依赖(即被依赖的对象)注入到对象中,而不是在对象内部直接创建依赖,从而实现对象之间的解耦。DI是控制反转(Inversion of Control, IoC)的一种实现方式。

本文将详细介绍常用的注解:@Autowired@Resource@Qualifier@Inject@Lookup,并说明它们的使用场景和区别。


@Autowired

概述

@Autowired是Spring框架中用于自动注入依赖的注解。它可以标注在字段、构造方法、Setter方法上。

使用方式

  1. 字段注入

    字段式注入是项目中最常见的DI方式

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class MyService {
    @Autowired
    private MyRepository myRepository;

    // other methods
    }
  2. 构造方法注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
    }

    // other methods
    }
  3. Setter方法注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    public class MyService {
    private MyRepository myRepository;

    @Autowired
    public void setMyRepository(MyRepository myRepository) {
    this.myRepository = myRepository;
    }

    // other methods
    }

工作原理

@Autowired默认按类型(by type)注入,即Spring会在容器中查找匹配类型的Bean,并注入到目标属性中。如果有多个相同类型的Bean,可以配合@Qualifier来指定具体的Bean。

常见问题

  • NoUniqueBeanDefinitionException:当有多个同类型的Bean时,Spring不知道注入哪一个Bean,会抛出此异常。此时需要使用@Qualifier来指定具体的Bean。
  • NoSuchBeanDefinitionException:当找不到匹配的Bean时,会抛出此异常。可以通过@Autowired(required = false)来避免异常,但注入的字段可能为null。

@Resource

概述

@Resource是JSR-250规范(Java Specification Request,Java 规范提案)中的注解,也可以用于注入依赖。它由Java EE提供,在Spring中也可以使用。

使用方式

@Resource注解默认按名称(by name)注入,也可以通过name属性指定具体的Bean名称。

  1. 字段注入

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class MyService {
    @Resource
    private MyRepository myRepository;

    // other methods
    }
  2. 指定名称

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class MyService {
    @Resource(name = "specificRepository")
    private MyRepository myRepository;

    // other methods
    }

工作原理

@Resource首先按名称查找,如果没有找到匹配的Bean,再按类型查找。如果名称和类型都没有找到匹配的Bean,会抛出异常。

常见问题

  • NoSuchBeanDefinitionException:当指定的名称或类型找不到匹配的Bean时,会抛出此异常。
  • Overlapping:在使用@Resource@Autowired时,如果两个注解作用于同一个字段,可能会产生冲突,需要小心使用。

@Qualifier

概述

@Qualifier用于在自动注入时,指定具体的Bean。当有多个相同类型的Bean时,可以用@Qualifier来消除歧义。

使用方式

@Qualifier可以和@Autowired@Inject配合使用。

  1. 配合@Autowire使用

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class MyService {
    @Autowired
    @Qualifier("specificRepository")
    private MyRepository myRepository;

    // other methods
    }
  2. 配合构造方法使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(@Qualifier("specificRepository") MyRepository myRepository) {
    this.myRepository = myRepository;
    }

    // other methods
    }

工作原理

@Qualifier通过指定Bean的名称来消除自动注入时的歧义。它在注入时会查找与名称匹配的Bean并注入。

常见问题

  • NoSuchBeanDefinitionException:当指定的Bean名称找不到匹配的Bean时,会抛出此异常。

@Inject

概述

@Inject 是由 Java 标准规范 JSR-330 提供的注解,用于依赖注入。它和 Spring 特有的 @Autowired 非常相似,主要区别在于其来源。

使用方式

@Inject 可以标注在字段、构造函数和 Setter 方法上。

@Autowired区别

  1. 来源@Inject 是 JSR-330(Java Dependency Injection 标准)的一部分,而 @Autowired 是 Spring 特有的注解。

  2. 功能:在大多数情况下,@Inject@Autowired 的功能相同,都用于自动注入依赖。

  3. 扩展性@Autowired 有更多的功能,例如 required 属性,可以指定注入是否为必需的。@Inject 没有这个属性。

  4. 环境@Inject 更加通用,可以在任何依赖注入框架中使用,而 @Autowired 主要在 Spring 框架中使用。


@Lookup

概述

@Lookup 是 Spring 特有的注解,用于解决特殊的依赖注入场景。特别是当你需要在一个单例 Bean 中注入一个原型(prototype)作用域的 Bean 时,可以使用 @Lookup 来确保每次调用都返回不同的实例。

使用方式

@Lookup 注解应用于抽象方法上,Spring 会在运行时覆盖该方法,以返回指定的 Bean 实例。

1
2
3
4
5
6
7
8
9
10
@Component
public abstract class MyService {
public void performTask() {
MyRepository myRepository = getMyRepository();
// 使用 myRepository
}

@Lookup
protected abstract MyRepository getMyRepository();
}

和其他DI注解的区别

  1. 功能@Lookup 主要用于动态地获取 Bean 实例,特别适合于在单例 Bean 中注入原型 Bean。

  2. 注入时机@Lookup 在每次调用时都返回新的 Bean 实例,而 @Autowired@Inject 在 Spring 容器启动时进行依赖注入。

  3. 使用场景@Lookup 适用于需要在运行时获取新的 Bean 实例的场景,例如需要每次调用都返回不同的原型作用域 Bean。


总结:

  • @Autowired:Spring特有的注解,默认按类型注入,可以标注在字段、构造方法、Setter方法上。可以与@Qualifier配合使用来消除歧义。
  • @Resource:JSR-250规范中的注解,默认按名称注入,也可以按类型注入。主要用于字段注入。
  • @Qualifier:用于在多Bean情况下指定具体的Bean,与@Autowired@Inject配合使用。
  • @Inject:JSR-330规范中的注解,功能同(略少)@Autowired,但不局限于Spring框架
  • @Lookup:Spring特有的注解,用于运行时动态获取新的 Bean 实例的场景
  • 注解式依赖注入主要在Spring容器启动时进行。Spring容器会在启动时扫描、实例化和注入所有需要管理的Bean。

  • 运行时注入是特殊情况,通常用于需要动态获取Bean实例的场景,如使用@Lookup注解。


扩展:

构造函数注入

适用场景

构造函数注入是指通过类的构造函数将依赖项注入到类中。这种方式在创建对象时就完成了所有依赖项的注入。

  • 不可变对象:如果对象的依赖项在创建后不会再改变,构造函数注入是一种很好的选择。它确保所有的依赖在对象创建时就完全提供。
  • 强制依赖:通过构造函数注入,可以强制依赖项的存在,避免在运行时出现空指针异常。
  • 单一责任原则:构造函数注入有助于遵循单一责任原则(SRP),使类的设计更清晰。类的依赖项明确地通过构造函数传递,使得类的职责更加明确。

优缺点

  • 优点

    • 依赖项不可变,确保依赖项在对象创建时提供。
    • 代码更清晰,依赖关系更显式。
    • 更容易进行单元测试,可以使用Mockito等框架轻松进行依赖注入。
  • 缺点

    • 如果依赖项较多,构造函数参数列表可能会变得很长,影响代码的可读性。

Setter方法注入

适用场景

Setter方法注入是指通过类的Setter方法将依赖项注入到类中。这种方式允许在对象创建后再进行依赖项的设置。

  • 可选依赖项:如果某些依赖项是可选的,使用Setter方法注入可以在需要时提供这些依赖项。
  • 配置或初始化后设置依赖:在某些情况下,依赖项可能需要在对象创建后进行配置或初始化,然后再进行注入。

优缺点

  • 优点

    • 可以设置可选的依赖项,灵活性更高。
    • 适用于需要在对象创建后进行某些初始化操作的场景。
  • 缺点

    • 依赖项可以在对象创建后改变,可能会导致不一致的状态。
    • 如果依赖项没有被设置,可能会在运行时出现空指针异常。

比较与总结

  1. 构造函数注入

    • 强制性:确保所有依赖项在对象创建时提供,适用于强制依赖。
    • 不变性:一旦对象创建,依赖项不可改变,确保对象状态的一致性。
    • 代码清晰:依赖关系显式在构造函数中体现。
  2. Setter方法注入

    • 灵活性:允许在对象创建后设置或更改依赖项,适用于可选依赖。
    • 初始化后设置:适用于需要在对象创建后进行某些初始化操作的场景。
    • 潜在风险:可能导致对象状态不一致,需确保所有依赖项在使用前正确设置。