在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 ,它是一个修改优先权的注解,当我们有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了。
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。