图源:
Spring 通过注解实现 DI(依赖注入),本文详细讨论这些注解。
@Autowired
@Autowired
是 Spring 定义的注解,属于包org.springframework.beans.factory.annotation
。
@Autowired
匹配 bean 的顺序是:
-
按类型(type)匹配
-
按限定符(qualifier)匹配
-
按名称(name)匹配
@Autowired
是最常用的注解,用它可以通过属性注入、Setter注入、构造器注入来实现DI。在中讨论过这些内容,这里不再重复。
筛选备选项
一般来说,用@Autowired
注入时必须存在类型匹配的唯一的 bean,没有匹配到和匹配到一个以上的 bean 都会报错:
public abstract class Carrier {
protected final String maker;
protected final String model;
protected Carrier(String maker, String model) {
this.maker = maker;
this.model = model;
}
}
callSuper = true)
(callSuper = true)
(public class Car extends Carrier {
public Car(final String maker, final String model) {
super(maker, model);
}
}
callSuper = true)
(callSuper = true)
(public class Motorcycle extends Carrier {
public Motorcycle(final String maker, final String model) {
super(maker, model);
}
}
public class CarrierService {
private Carrier defaultCarrier;
public Carrier getDefaultCarrier() {
return defaultCarrier;
}
}
public class WebConfig {
private CarrierService carrierService;
Car defaultCar(){
return new Car("长安汽车","X5");
}
Motorcycle defaultMotorcycle(){
return new Motorcycle("哈雷","A7");
}
ApplicationRunner applicationRunner(){
return args -> {
Carrier defaultCarrier = carrierService.getDefaultCarrier();
System.out.println(defaultCarrier);
};
}
}
这里定义的两个 bean 都具备Carrier
类型,因此用@Autowired
注入Carrier defaultCarrier
属性时会报错,Spring 不知道要注入的是哪一个。
可以通过@Qualifier
缩小筛选范围:
public class CarrierService {
"defaultCar")
( private Carrier defaultCarrier;
// ...
}
示例中使用@Qualifier("defaultCar")
将注入限定为名称为defaultCar
的 bean,因此可以正常注入。
除了使用限定符,还可以使用名称进行筛选,@Autowired
使用属性名作为 bean 名称进行匹配:
public class CarrierService {
private Carrier defaultCar;
// ...
}
在用于名称匹配时,
@Autowired
并不好用,因为它只能用属性名作为 bean 名称匹配,@Resource
则可以用name
属性指定 bean 名称,而@inject
可以使用@Named
注解指定 bean 名称。
@Qualifier
还可以更灵活地对 bean 进行筛选,详细内容可以阅读我的。
除了@Qualifier
,还可以使用@Primary
指定一个主要的 bean 作为有多个备选项时的首选注入,详细情况可以参考我的。
多构造器注入
如果 bean 定义中只有一个构造器,则不需要用@Autowired
标记该构造器。如果存在多个构造器,我们可以指定其中一个用于注入:
public class MultiConstructorExample {
private final Car car;
private final Motorcycle motorcycle;
private final String msg;
private static final String DEFAULT_MSG = "hello";
public MultiConstructorExample(Car car, Motorcycle motorcycle, String msg) {
this.car = car;
this.motorcycle = motorcycle;
this.msg = msg;
}
public MultiConstructorExample(Car car, Motorcycle motorcycle) {
this(car, motorcycle, DEFAULT_MSG);
}
}
如果有多个构造器都可以用于注入,并且我们希望 Spring 能根据匹配结果选择一个“合适”的构造器用于注入,可以:
public class MultiConstructorExample {
// ...
required = false)
( public MultiConstructorExample(Car car, Motorcycle motorcycle, String msg) {
// ...
}
required = false)
( public MultiConstructorExample(Car car, Motorcycle motorcycle) {
this(car, motorcycle, DEFAULT_MSG);
}
}
注意,此时多个用于注入的构造器必须用@Autowired(required = false)
标记,如果存在一个用@Autowired
标记的构造器,将抛出一个这样的异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'multiConstructorExample': Invalid autowire-marked constructors: [public com.example.dianno.entity.MultiConstructorExample(com.example.dianno.entity.Car,com.example.dianno.entity.Motorcycle,java.lang.String)]. Found constructor with 'required' Autowired annotation: public com.example.dianno.entity.MultiConstructorExample(com.example.dianno.entity.Car,com.example.dianno.entity.Motorcycle) ...
大意是“存在多个用于注入的构造器”,且存在一个
required=true
的构造器,这种情况不被允许。
此时,Spring 将按照构造器参数去匹配 bean,并且选择匹配结果最多的构造器进行注入:
public class WebConfig {
// ...
String msg(){
return "This is a String bean.";
}
}
如果我们添加了上边这样的 bean,就可以看到输出:
MultiConstructorExample(car=Car(super=Carrier(maker=长安汽车, model=X5)), motorcycle=Motorcycle(super=Carrier(maker=哈雷, model=A7)), msg=This is a String bean.)
这说明构造器MultiConstructorExample(Car car, Motorcycle motorcycle, String msg)
被调用并用于注入。否则,将看到MultiConstructorExample(Car car, Motorcycle motorcycle)
被调用。
当然,无论如何,这两个构造器都需要一个Car
类型的 bean,以及一个Motorcycle
类型的 bean,缺少任意一个都将导致程序运行失败,因为没有合适的构造器用于创建对象。
此时可以指定一个默认的构造器:
public class MultiConstructorExample {
// ...
public MultiConstructorExample(){
this(null, null, DEFAULT_MSG);
}
}
@Resource
@Resource
是 Java 扩展包的注解,属于包javax.annotation.Resource
。
@Autowired
按照类型优先进行匹配,@Resource
按照名称优先进行匹配,其匹配顺序是:
-
按名称(name)匹配
-
按类型(type)匹配
-
按限定符(qualifier)匹配
修改之前的示例,使用@Resource
进行依赖注入:
public class CarrierService {
private Carrier defaultCarrier;
// ...
}
会报错,因为不能按照defaultCarrier
找到 bean,如果按照类型Carrier
匹配,能匹配到2个。
可以通过@Resource
的name
属性指定要匹配的 bean 名称:
public class CarrierService {
name = "defaultCar")
( private Carrier defaultCarrier;
// ...
}
或者也可以修改属性名称:
public class CarrierService {
private Carrier defaultCar;
public Carrier getDefaultCarrier() {
return defaultCar;
}
}
或者按照类型匹配,提供一个唯一 bean 对应的类型:
public class CarrierService {
private Car defaultCarrier;
// ...
}
最后,同样可以使用@Qualifier
:
public class CarrierService {
"defaultCar")
( private Carrier defaultCarrier;
// ...
}
@Resource
还可以通过 Setter 注入,用法与属性注入一致,这里不再说明。
@Inject
@Inject
同样属于 Java 扩展包,属于包javax.inject.Inject
。
@Inject
的匹配顺序是:
-
按类型匹配
-
按限定符匹配
-
按名称匹配
要使用@Inject
注解要先添加依赖:
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
先看按类型匹配的示例:
public class CarrierService {
private Car defaultCarrier;
// ...
}
如果按类型匹配出多个候选项,可以通过限定符进一步匹配:
public class CarrierService {
"defaultCar")
( private Carrier defaultCarrier;
// ...
}
也可以通过名称进一步匹配:
public class CarrierService {
"defaultMotorcycle")
( private Carrier defaultCarrier;
// ...
}
@Named
同样属于 Java 扩展包,属于包jakarta.inject.Named
。
如果
@Named
注解没有起作用,可以检查一下 maven 依赖中的jakarta.inject
模块的版本是否正确。
@Inject
用于 Setter 注入的方式与属性注入类似,这里不再赘述。
@Inject
也可以用于构造器注入:
public class CarrierService {
private Carrier defaultCarrier;
public CarrierService( ("defaultCar") Carrier defaultCarrier) {
this.defaultCarrier = defaultCarrier;
}
// ...
}
注入集合
可以通过@Autowired
注入集合(Collection),且此时如果用构造器注入且缺省 bean 时会有一些不一样的行为,这些在中有过介绍。
BeanPostProcessor
在官方文档中,有一段这样的描述:
@Autowired
、@Inject
、@Value
和@Resource
注释由SpringBeanPostProcessor
实现处理。这意味着您不能在自己的BeanPostProcessor
或BeanFactoryPostProcessor
类型(如果有的话)中应用这些注释。这些类型必须通过使用XML或Spring@Bean
方法显式地“连接”。
但实际测试我发现在 BeanPostProcessor
中依然可以用@Resource
进行连接:
public class MyBeanPostProcessor implements BeanPostProcessor {
name = "defaultMotorcycle")
( private Carrier defaultCarrier;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CarrierService) {
var cs = (CarrierService) bean;
var defaultCarField = cs.getClass().getDeclaredField("defaultCar");
defaultCarField.setAccessible(true);
defaultCarField.set(bean, defaultCarrier);
log.info("CarrierService's defaultCarrier field is set: %s".formatted(defaultCarrier));
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
public class CarrierService {
private Carrier defaultCar;
// ...
}
当然,按照官方文档的建议,对BeanPostProcessor
中的属性进行"手动连接"同样可行:
public class MyBeanPostProcessor implements BeanPostProcessor {
private final Carrier defaultCarrier;
public MyBeanPostProcessor(Carrier defaultCarrier) {
this.defaultCarrier = defaultCarrier;
}
// ...
}
public class WebConfig {
// ...
MyBeanPostProcessor myBeanPostProcessor(){
return new MyBeanPostProcessor(defaultMotorcycle());
}
}
总结
如果需要按照类型优先匹配,可以使用@Autowired
或@inject
,如果需要按照 bean 名称优先匹配,可以使用@Resource
。
本文的所有示例代码可以从
文章评论