注解 @Autowired 的注入机制

8 篇文章 1 订阅
7 篇文章 1 订阅

在Spring IoC 的概念中,依赖注入( Dependency Injection, DI )可以通过注解 @Autowired 来实现,下面就举例说明该注解的注入机制.

假设人类(Person)有时需要自我介绍,比方说 Charles 是来自英国的,张三是来自中国的。为了更好的展示这个过程,我们首先定义一个接口:Person

public interface Person {
    /**
     * 自我介绍
     */
    void introduce();
}

接下来创建张三(ZhangSan)的实现类去实现人类(Person)这个接口,并做自己的自我介绍,

@Component
public class ZhangSan implements Person {
    /**
     * 自我介绍
     */
    @Override
    public void introduce() {
        System.out.println("大家好,我叫张三,我来自中国。");
    }
}

接下来,我们使用@Autowired注解将Person注入到我们的启动类中。

@SpringBootApplication
public class SpringbootApplication implements ApplicationRunner {

	/**
	 * 将 Person 注入进来
	 */
	@Autowired
	private Person person;

	@Override
	public void run(ApplicationArguments args) throws Exception {
		// 调用介绍方法
		person.introduce();
	}

	public static void main(String[] args) {
		SpringApplication.run(SpringbootApplication.class, args);
	}
}

@Autowired 是我们使用得最多的注解之一,它会根据属性的类型( by type )找到对应的Bean 进行注入。这里的ZhangSan是Person的实现类,所以Spring的IOC容器会把ZhangSan的实例注入到我们启动类里面去。

下面来执行一下,看看能不能得到张三的自我介绍:

......
INFO 18632 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1250 ms
INFO 18632 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
INFO 18632 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8083 (http) with context path ''
INFO 18632 --- [           main] c.d.springboot.SpringbootApplication     : Started SpringbootApplication in 2.652 seconds (JVM running for 4.194)
大家好,我叫张三,我来自中国。

显然,测试是成功的, 这个时候Spring IoC 容器己经通过注解@Autowired 成功地将ZhangSan 注入到了启动类中。

接下来,Charles来报到了,还是实现Person类:

@Component
public class Charles implements Person{
    /**
     * 自我介绍
     */
    @Override
    public void introduce () {
        System.out.println("My name is Charles. I'm from England");
    }
}

好了,现在那么麻烦来了,上面我们说过 @Autowired 是通过属性的类型( by type )来查找具体的实现类的,而我们现在却有两个人都是Person类型的, 一个Charles, 一个ZhangSan, 那么Spring IoC 如何注入呢?

如果我们还进行测试,很快我们就可以看到IoC 容器抛出异常

Field person in com.demos.springboot.SpringbootApplication required a single bean, but 2 were found:
......

这时 Spring IoC 容器并不能知道我们需要注入哪个人(是Charles? 是ZhangSan? )给启动类对象,从而引起错误的发生。那么使用@Autowired 能处理这个问题吗?答案是肯定的。假设我们目前需要的是Charles做自我介绍,那么可以把属性名称转化为charles,也就是将原来的注入代码:

/**
 * 将 Person 注入进来
 */
@Autowired
private Person person;

修改成指定的人名:

/**
 * 将 Person 注入进来
 */
@Autowired
private Person charles;

下面测试结果:

...
INFO 19676 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8083 (http) with context path ''
INFO 19676 --- [           main] c.d.springboot.SpringbootApplication     : Started SpringbootApplication in 1.678 seconds (JVM running for 2.359)
My name is Charles. I'm from England

这里, 我们只是将属性的名称从 person 修改为了 charles,那么我们再测试的时候,可以看到是调用的Charles的自我介绍。那是因为@Autowired 提供这样的规则: 首先它会根据类型找到对应的Bean,如果对应类型的Bean 不是唯一的,那么它会根据其属性名称和Bean 的名称进行匹配。如果匹配得上,就会使用该Bean :如果还无法匹配,就会抛出异常。


注意:这里的属性名称也不是随便写的,默认应该是写实现类类名,并将首字母小写。

这里还要注意的是@Autowired 是一个默认必须找到对应Bean 的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null , 那么你可以配置@Autowired 属性required 为false,例如,像下面一样:

@Autowired(required = false)

在上面我们发现有Charles,有ZhangSan的时候, 为了使@Autowired 能够继续使用,我们做了一个决定,将Person 的属性名称从 person 修改为 charles 。显然这是一个憋屈的做法,而且这让我们的代码不灵活了。

产生注入失败问题的根本原因是按类型( by type ) 查找, 正如人类可以有多个,这样会造成Spring IoC 容器注入的困扰,我们把这样的一个问题称为歧义性。知道这个原因后, 那么下面就来解决这个歧义性。

消除歧义性一一@Primary 和@Quelifier

首先是一个注解@Primary ,它是一个修改优先权的注解,当我们有Charles,有ZhangSan的时候,假设这次需要听张三的自我介绍, 那么只需要在ZhangSan类的定义上加入@Primarγ 就可以了,类似下面这样:

@Component
@Primary
public class ZhangSan implements Person {
   ......
}

这里的@Primary 的含义告诉Spring IoC 容器, 当发现有多个同样类型的Bean 时,请优先使用我进行注入,于是再进行测试时会发现,系统将调用张三的自我介绍。因为当Spring 进行注入的时候虽然它发现存在多个人, 但因为ZhangSan被标注为了@Primarγ ,所以优先采用 ZhangSan 的实例进行了注入,这样就通过优先级的变换使得IoC 容器知道注入哪个具体的实例来满足依赖注入。

然后,有时候@Primary 也可以使用在多个类上,也许无论是 Charles 还是 ZhangSan 都可能带上 @Primary 注解,其结果是IoC 容器还是无法区分采用哪个Bean 的实例进行注入, 又或者说我们需要更加灵活的机制来实现注入,那么@Quelifier可以满足你的这个愿望。它的配置项value 需要一个字符串去定义,它将与@Autowired 组合在一起,通过类型和名称一起找到Bean 。我们知道Bean 名称在Spring IoC容器中是唯一的标识,通过这个就可以消除歧义性了。

下面假设ZhangSan己经标注了@Primary ,而我们需要的是Charles的自我介绍,因此需要修改启动类中Person的属性(person) 的标注以适合我们的需要,如下所示:

/**
 * 将 Person 注入进来
 */
@Autowired
@Qualifier(value = "charles")
private Person person;

虽然ZhangSan表明了优先权,但是一旦这样声明, Spring IoC 将会以类型和名称去寻找对应的Bean 进行注入。根据类型和名称,显然也只能找到Charles了。

总结

  1. 注解@Autowired首先会根据类型找到对应的Bean,如果对应类型的Bean 不是唯一的,那么它会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就会使用该Bean ,如果还无法匹配,就会抛出异常。
  2. 如果有多个实现类,可以使用注解@Primary表明优先权。
  3. 如果多个实现类都声明了优先权,那么还可以使用注解@Qualifier根据类型和名称去寻找对应的Bean。

技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值