Skip to main content

Spring依赖注入有哪些常用注解?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

问题详解

1. @Autowired

最常见的依赖注入注解,它默认按先类型匹配,再按 beanName 匹配

几乎可以用在任何你能想到的地方:

  • 注解在属性上,即属性注入。
  • 注解在实例方法(通常是 Setter 方法)上,即 Setter 方法注入。
  • 注解在构造器上,即构造器注入。
public class Foo {

@Autowired // 属性注入
private Depend depnd1;
private Depend depnd2;
private Depend depnd3;

@Autowired // Setter 方法注入
public void setDepend2(Depend depnd2) {

}

// 构造器注入
@Autowired
public Foo(Depend depnd3) {
this.depnd3 = depnd3;
}

}

此外,@Autowired 也可以放到方法参数中,它等效于 Setter 方法注入和构造器注入。

1.1. 声明为可选项

在默认情况下,如果从 Spring 容器找不匹配的 Bean 会报错,你也可以将此处依赖其声明“非必填项”:

  • 设置 required 属性为 false
  • 将参数类型改为 Optional<T>
  • 在参数上加 @Nullable 注解。

这种情况下,如果没有对应的依赖将不会报错。不过构造器注入不在这个范围内,由于缺少依赖就无法实例化 Bean,因此当进行构造器注入时,依赖项总是必须存在的。

1.2. 批量注入

顺带一提,和其他注解一样,Spring 支持批量注入数组、Collection 集合或者使用 beanName 作为 Key 的 Map 集合(即 key 为 String 类型)。

public class Foo {

@Autowired
private Depend[] array;
@Autowired
private List<Depend> coll;
@Autowired
private Map<String, Depend> map;
}

关于集合类型的泛型是如何解析的,可以参照 Spring 源码中的 ResolvableType

1.3. 循环依赖

当进行依赖注入时,可能会出现 A -> B -> A 这种循环依赖的情况,甚至是 A -> A 这种自己注入自己的情况。虽然 Spring 并不推荐这么做,但是它通过三级缓存和懒加载很好的解决了这个问题。

2. @Qualifier

@Qualifier 注解用于配合 @Autowired 用于显式的指定要注入的依赖名称。

比如:

@Component("a")
public class A implements Letter {}
@Component("b")
public class B implements Letter {}
@Component("c")
public class C implements Letter {}

@Component
public class Example {

@Qualifier("a") // 指定注入 beanName 为 a 的 Letter 类型的 Bean
@Autowired
private Letter letter;
}

此外,JSR330 (即 javax.inject 包)也规定了一个 @Qualifier 注解,不过基本用不到,因此按下不表。

3. @Resource

@Resource 是 JSR250 规定的注解,它位于 javax.annotation 包,在 JDK11 以后被移到了 jakarta.annotation

@Autowried 相反,它是一个优先按名称注入的注解。

@Component
public class Example {

@Resource("a") // 指定注入 beanName 为 a 的 Letter 类型的 Bean
private Letter a;
}

当然,说它“按名称注入”实际上有失偏颇,由于它额外提供了 nametype 属性,因此我们可以同时显式的指定类型和名称

public class Example {

@Resource(
type = A.class,
name = "a"
)
private Letter letter;
}

比如上述配置中,我们从 Letter 类型的 Bean 中,寻找具体类型为 A、且 beanName 为“a”的 Bean。

关于 @Autowired@Resource 的区别,请参见:✅ @Autowired 和@Resource 有何区别?

4. @Inject & @Named

它们都是 JSR330 规定的注解,位于 javax.inject 包。其中,@Inject 注解约等于 @Autowried,而 @Named 注解在此时约等于 @Qualifier

举个例子:

@Component
public class Example {

@Named("a") // 指定注入 beanName 为 a 的 Letter 类型的 Bean
@Inject
private Letter letter;
}

5. @Value

@Value 注解可以使用 ${} 语法注入配置文件的某些配置,也可以使用 #{} 语法注入 Spring 容器中的 Bean。

比如,现在有如下配置文件:

example:
code: example-name
list:
- one
- two
- three

然后就可以通过 @Value 注入对应的配置文件值:

@ConfigurationProperties(prefix = "example")
@Data
public class ConfigProperties {

@Value("${example.code}")
private String code;

@Value("${example.list}")
private List<String> list;
}

5.1. 默认值

一般情况下,如果@Value 找不到对应的资源将会报错,不过我们也可以设置一个默认值避免这种情况:

@ConfigurationProperties(prefix = "example")
@Data
public class ConfigProperties {

@Value("${example.code:defaultCode}") // 没有时注入 defaultCode 字符串
private String code;

@Value("${example.list:}") // 没有时注入一个空集合
private List<String> list;
}

5.2. SpEL 支持

@Value 包含一个 SpEL 表达式时,该值将在运行时被动态计算出来,比如:

@Value("${example.code} == null ? $example.code} : '123456'")
private String value;

关于 SpEL 表达式,参见:什么是 SpEL 表达式?能用来做什么?

5.3. 访问 Spring 容器

虽然一般用于注入配置文件属性,但是实际上 @Value 是可以访问 Spring 容器中的 Bean 的,这意味着它也可以用于根据 beanName 注入 Bean,或者 Bean 的某个属性:

@Component("foo")
public class Foo {
private final String name = "abc";
}

@Component
public class Example {

@Value("#{foo}) // 注入 foo
private Foo foo;

@Value("#{foo.name}) // 注入 foo 的 name 属性
private String fooName;
}

5.4. 手动解析

@Value 注解在底层实际上是基于 StringValueResolvor 这个组件实现的,这意味着如果我们愿意,则可以在手动获取它后主动的解析某个表达式:

public class ValueResolveAssembleAnnotationHandler 
implements EmbeddedValueResolverAware { // 实现回调接口,获取表达式解析器

private StringValueResolver stringValueResolver;

@Override
public void setStringValueResolver(StringValueResolver stringValueResolver) {
this.stringValueResolver = stringValueResolver;
}

public void run() {
String exp = "${example.list}";
String value = stringValueResolver.resolveStringValue(exp);
}
}

这在造需要解析注解值的轮子时尤其好用,因为注解值本身的固定的,而通过引入表达式,我们就可以让它“动起来”,从而允许更灵活的面对各种场景。