从零开始 Spring Boot 43:DI 注解

从零开始 Spring Boot 43:DI 注解

从零开始 Spring Boot 43:DI 注解_第1张图片

图源:简书 (jianshu.com)

Spring 通过注解实现 DI(依赖注入),本文详细讨论这些注解。

@Autowired

@Autowired是 Spring 定义的注解,属于包org.springframework.beans.factory.annotation

@Autowired匹配 bean 的顺序是:

  1. 按类型(type)匹配
  2. 按限定符(qualifier)匹配
  3. 按名称(name)匹配

@Autowired是最常用的注解,用它可以通过属性注入、Setter注入、构造器注入来实现DI。在前文中讨论过这些内容,这里不再重复。

筛选备选项

一般来说,用@Autowired注入时必须存在类型匹配的唯一的 bean,没有匹配到和匹配到一个以上的 bean 都会报错:

@ToString
@EqualsAndHashCode
public abstract class Carrier {
    protected final String maker;
    protected final String model;

    protected Carrier(String maker, String model) {
        this.maker = maker;
        this.model = model;
    }
}

@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Car extends Carrier {
    public Car(final String maker, final String model) {
        super(maker, model);
    }
}

@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Motorcycle extends Carrier {
    public Motorcycle(final String maker, final String model) {
        super(maker, model);
    }
}

@Service
public class CarrierService {
    @Autowired
    private Carrier defaultCarrier;

    public Carrier getDefaultCarrier() {
        return defaultCarrier;
    }
}

@Configuration
public class WebConfig {
    @Lazy
    @Autowired
    private CarrierService carrierService;

    @Bean
    Car defaultCar(){
        return new Car("长安汽车","X5");
    }

    @Bean
    Motorcycle defaultMotorcycle(){
        return new Motorcycle("哈雷","A7");
    }

    @Bean
    ApplicationRunner applicationRunner(){
        return args -> {
            Carrier defaultCarrier = carrierService.getDefaultCarrier();
            System.out.println(defaultCarrier);
        };
    }
}

这里定义的两个 bean 都具备Carrier类型,因此用@Autowired注入Carrier defaultCarrier属性时会报错,Spring 不知道要注入的是哪一个。

可以通过@Qualifier缩小筛选范围:

@Service
public class CarrierService {
    @Autowired
    @Qualifier("defaultCar")
    private Carrier defaultCarrier;
    // ...
}

示例中使用@Qualifier("defaultCar")将注入限定为名称为defaultCar的 bean,因此可以正常注入。

除了使用限定符,还可以使用名称进行筛选,@Autowired使用属性名作为 bean 名称进行匹配:

@Service
public class CarrierService {
    @Autowired
    private Carrier defaultCar;
    // ...
}

在用于名称匹配时,@Autowired并不好用,因为它只能用属性名作为 bean 名称匹配,@Resource则可以用name属性指定 bean 名称,而@inject可以使用@Named注解指定 bean 名称。

@Qualifier还可以更灵活地对 bean 进行筛选,详细内容可以阅读我的另一篇文章。

除了@Qualifier,还可以使用@Primary指定一个主要的 bean 作为有多个备选项时的首选注入,详细情况可以参考我的同一篇文章。

@Resource

@Resource是 Java 扩展包的注解,属于包javax.annotation.Resource

@Autowired按照类型优先进行匹配,@Resource按照名称优先进行匹配,其匹配顺序是:

  1. 按名称(name)匹配
  2. 按类型(type)匹配
  3. 按限定符(qualifier)匹配

修改之前的示例,使用@Resource进行依赖注入:

@Service
public class CarrierService {
    @Resource
    private Carrier defaultCarrier;
	// ...
}

会报错,因为不能按照defaultCarrier找到 bean,如果按照类型Carrier匹配,能匹配到2个。

可以通过@Resourcename属性指定要匹配的 bean 名称:

@Service
public class CarrierService {
    @Resource(name = "defaultCar")
    private Carrier defaultCarrier;
    // ...
}

或者也可以修改属性名称:

@Service
public class CarrierService {
    @Resource
    private Carrier defaultCar;

    public Carrier getDefaultCarrier() {
        return defaultCar;
    }
}

或者按照类型匹配,提供一个唯一 bean 对应的类型:

@Service
public class CarrierService {
    @Resource
    private Car defaultCarrier;
    // ...
}

最后,同样可以使用@Qualifier

@Service
public class CarrierService {
    @Resource
    @Qualifier("defaultCar")
    private Carrier defaultCarrier;
    // ...
}

@Resource还可以通过 Setter 注入,用法与属性注入一致,这里不再说明。

@Inject

@Inject同样属于 Java 扩展包,属于包javax.inject.Inject

@Inject的匹配顺序是:

  1. 按类型匹配
  2. 按限定符匹配
  3. 按名称匹配

要使用@Inject注解要先添加依赖:

<dependency>
    <groupId>jakarta.injectgroupId>
    <artifactId>jakarta.inject-apiartifactId>
    <version>2.0.1version>
dependency>

先看按类型匹配的示例:

@Service
public class CarrierService {
    @Inject
    private Car defaultCarrier;
	// ...
}

如果按类型匹配出多个候选项,可以通过限定符进一步匹配:

@Service
public class CarrierService {
    @Inject
    @Qualifier("defaultCar")
    private Carrier defaultCarrier;
	// ...
}

也可以通过名称进一步匹配:

@Service
public class CarrierService {
    @Inject
    @Named("defaultMotorcycle")
    private Carrier defaultCarrier;
	// ...
}

@Named同样属于 Java 扩展包,属于包jakarta.inject.Named

如果@Named注解没有起作用,可以检查一下 maven 依赖中的jakarta.inject模块的版本是否正确。

@Inject用于 Setter 注入的方式与属性注入类似,这里不再赘述。

@Inject也可以用于构造器注入:

@Service
public class CarrierService {
    private Carrier defaultCarrier;

    @Inject
    public CarrierService(@Named("defaultCar") Carrier defaultCarrier) {
        this.defaultCarrier = defaultCarrier;
    }
    // ...
}

总结

如果需要按照类型优先匹配,可以使用@Autowired@inject,如果需要按照 bean 名称优先匹配,可以使用@Resource

参考资料

  • 从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)
  • Using JSR 330 Standard Annotations :: Spring Framework
  • Wiring in Spring: @Autowired, @Resource and @Inject | Baeldung

你可能感兴趣的:(JAVA,spring,boot,java,spring)