本章的开始讲述了Spring IoC的两个作用,上一节只讨论了将Bean装配到IoC容器中,对于如何进行获取,还有一个作用没有谈及,那就是Bean之间的依赖。在Spring IoC容器中,我们称之为依赖注入(Dependency Injection, DI).
例如,人类(Persion)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)用来看门,猫(cat)用来抓老鼠。。。于是一些事情就依赖于那些可爱的动物了,如图。
为了更好的展示这个过程,首先定义两个接口,一个是人类(Persion)另一个是动物(Animal)。人类通过动物去提供一些特殊服务
定义人和动物接口
package cn.hctech2006.boot.bootall.bean;
/**
* 人类接口
*/
public interface Person {
//使用动物服务
public void servie();
//设置动物
public void setAnimanl(Animal animanl);
}
package cn.hctech2006.boot.bootall.bean;
/**
* 动物接口
*/
public interface Animal {
public void use();
}
两个实现类
package cn.hctech2006.boot.bootall.bean;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 人的实现类
*/
@Component
public class BussinessPersion implements Person {
@Autowired
private Animal animal = null;
@Override
public void servie() {
this.animal.use();
}
@Override
public void setAnimanl(Animal animanl) {
this.animal=animanl;
}
}
package cn.hctech2006.boot.bootall.bean;
/**
* 动物实现类
*/
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("狗["+Dog.class.getSimpleName()+"]是看门用的。");
}
}
暂时不扫描
package cn.hctech2006.boot.bootall.bean;
/**
* 动物实现类
*/
public class Cat implements Animal {
@Override
public void use() {
System.out.println("猫["+Cat.class.getSimpleName()+"]是抓老鼠用的。");
}
}
这里应该注意加粗的注解@Autowired,这是我们在Spring里面最常用的注解之一,十分重要,他会根据属性的类型(by type)找到对应的Bean进行注入。这里的Dog类是动物的一种,所以Spring IoC容器会把Dog的实例注入BussinessPersion中。这样通过Spring IoC容器获取BussinessPersion实例的时候就能够使用Dog实例来提供服务了,下面是测试代码。
显然测试成功,这个时候Spring IoC容器已经通过注解@AutoWired成功的将dog注入到了BussinessPersion实例当中,但是这只是一个比较简单的例子,我们有必要继续探讨@Autowired
- 注解@Autowired
@Autowired注解注入的机制最基本的一条是根据类型(by type),我们回顾IoC容器的顶级接口BeanFactory,就可以知道IoC容器是通过getBean方法获取对应的Bean,而getBean方法又支持支持根据类型(by type)和根据名称(by name)。在回到上面的例子,我们只是创建了一个动物-狗,而实际上动物还有猫等,猫可以抓老鼠,于是我们又创建一个猫类
/**
- 动物实现类
*/
@Component
public class Cat implements Animal {
@Override
public void use() {
System.out.println(“猫[”+Cat.class.getSimpleName()+"]是抓老鼠用的。");
}
}
如果我们继续使用之前的BussinessPerson类,那么麻烦来了,因为这个类只是定义了一个动物属性(Animal),而我们确实有两个动物,一个狗,一个猫,Spring IoC如何注入呢?如果你还进行测试,很快你就可以看到IoC容器抛出异常,如下面日志所示:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.hctech2006.boot.bootall.bean.Animal' available: expected single matching bean but found 2: cat,dog
从日志可以看出,SPring IoC容器并不知道你需要注入什么动物(是狗?是猫?)给BussinessPersion类对象,从而引发错误的产生。那么@Autowired能处理这个问题吗?
答案是肯定的。假设我们现在需要的是狗提供服务,那么可以把属性的名称转换为dog,也就是原来的
@Autowired
private Animal animal = null;
改为
@Autowired
private Animal dog = null;
再次测试会发现是狗在提供服务
因为@Autowired提供这样的规则,首先他会根据类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么他会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就使用该Bean,如果还是无法匹配就会报异常。
需要注意的是@Autowired是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定存在并且允许这个被标注的属性为null,那么你可以设置@Autowired属性required为false,例如向下面一样
@Autowired(required=false)
同样,他除了可以标注属性外还可以标注方法,如setAnimal方法,如下
@Override
@Autowired
public void setAnimanl(Animal animanl) {
this.animal=animanl;
}
}
这样他也会使用setAnimal方法从IoC容器中找到对应的动物进行注入,甚至可以用在方法的参数上,后面会再谈到他。
2. 消除歧义性-@Primary和@Quelifier
在上面我们发现有狗有猫的时候,为了能够继续使用@Autowired,我们做了一个决定,将BuinessPerson的属性名称从animal改成了dog。显然这是一种憋屈的做法。好好一个动物却被我们定义成了狗。产生注入失败问题的根本是按照类型(by type)查找,正如动物卡哇伊有多个类型,这样就会造成Spring IoC容器注入的困扰,我们把这一个问题成为歧义性。知道这个原因之后,那么这两个注解是从哪一个是从哪个角度去解决这些问题的呢?这就是本节要解决的问题。
首先是一个注解@Primary,他是一个修改优权的注解,当我们有猫有狗的时候,假设这次需要使用猫,那么需要在猫类的定义上加上@Primary注解就可以了,类似下面:
/**
* 动物实现类
*/
@Primary
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("狗["+Dog.class.getSimpleName()+"]是看门用的。");
}
}
这里的Primary的含义告诉Spring IoC容器,当发现有多个同样类型的Bean时,请优先使用我进行注入,于是再次发现狗为你提供服务。因为当Spring进行注入的时候,虽然他发现存在多个动物,但是因为cat被标注为@Primary,所以优先采用Dog实例进行注入,这样就通过优先级的变化使得IoC容器知道注入那个具体的实例来满足依赖注入。
然后,有时候@Primary也可以使用在多个类上,也许无论猫狗都可以带上@Primary注解,其结果是IoC容器还是无法区分采用那个Bean的实例进行注入,又或者说我们需要更加灵活的机制来实现注入,那么@Quelifier可以满足你的愿望。他的字符串value需要一个字符串去定义,他将与@Autowired组合在一起,通过类型和名称一起找到Bean。我们知道Bean的名称在Spring IoC容器是唯一标识。通过这个就可以消除歧义性了。此时你是否想起BeanFactory接口中的这个方法呢?
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
通过它既可以按照名称和类型的组合找到对象了。下面假设狗已经标注了@Primary,而我们需要猫提供服务,因此需要修改BussinessPerson属性animal的标注以适合我们的需要,如下所示:
@Qualifier("cat")
@Autowired
private Animal animal = null;
一旦这样声明,Spring IoC容器将会以类型和名称去寻找对应的Bean进行注入。根据类型和名称显然也只能找到猫为我们服务了。
猫[Cat]是抓老鼠用的。
- 带有参数的构造方法
在上面,我们都是基于一个默认的情况,那就是不带参数的构造方法实现依赖注入。但是事实上,有些类只有带有参数的构造方法,于是上述的方法就不能使用了。为了满足这个功能,我们可以使用@Autowired注解对构造方法的参数进行注入,例如,修改类BussinessPersion来满足这个功能。代码如下
package cn.hctech2006.boot.bootall.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
/**
* 人的实现类
*/
@Component
public class BussinessPersion implements Person {
private Animal animal = null;
public BussinessPersion(@Autowired @Qualifier("cat") Animal animal) {
this.animal = animal;
}
@Override
public void servie() {
this.animal.use();
}
@Override
public void setAnimanl(Animal animanl) {
this.animal=animanl;
}
}
可以看到,代码中取消了@Autowired对属性和方法的标注。在构造方法的参数加上了@Autowired和@Qulifier注解,使得他们能够注入进来。这里是用@Qulifier是为了避免歧义性。当然如果只有一种动物,直接使用@Autowired即可