Spring依赖注入有哪些常用注解?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个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 并不推荐这么做,但是它通过三级缓存和懒加载很好的解决了这个问题。
- 关于循环依赖与三级缓存,请参见:✅ 什么是三级缓存?为什么需要?
- 关于懒加载和延迟注入,请参见:✅ Spring 的懒加载和延迟注入是什么?两者有何区别?
- 关于
@Autowired
与@Resource
的区别,请参见:✅ @Autowired 和@Resource 有何区别?
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;
}
当然,说它“按名称注入”实际上有失偏颇,由于它额外提供了 name
和 type
属性,因此我们可以同时显式的指定类型和名称:
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);
}
}
这在造需要解析注解值的轮子时尤其好用,因为注解值本身的固定的,而通过引入表达式,我们就可以让它“动起来”,从而允许更灵活的面对各种场景。