上篇文章介绍了ioc的基本用法和@ComponentScan注解的使用,这篇文章我们来看看依赖注入的部分。
提起依赖注入,想必大家肯定会想到@Autowired注解,的确,它是我们用的最多的一个。
还记得容器的顶级接口BeanFactory 吗,它定义了获取bean的几个方法,主要是基于类型和基于名称来获取bean,而这里即将要介绍的@Autowired注解便是首先基于类型来获取bean的。
下面我们来看个例子,有个人需要让自己的宠物来帮自己做些事情,这里我们先定义人和动物的接口:
public interface Person {
/**
* 使用动物服务
*/
void service();
}
public interface Animal {
/**
* 动物的使用方法
*/
void use();
}
然后分别定义它们的实现类:
@Component
public class DiBusinessPerson implements Person {
@Autowired
private Animal cat;
@Override
public void service() {
this.cat.use();
}
@Component
@Slf4j
public class DiCat implements Animal {
@Override
public void use() {
log.warn("猫【{}】是抓老鼠用的。", DiCat.class.getSimpleName());
}
}
注意:对于DiBusinessPerson 类中的成员变量dog,我们使用了@Autowired注解标记,这样spring容器在创建这个bean时,会自动将其依赖的Animal 注入进来,也就是spring会帮助我们创建Animal 的实例,并将其赋值给dog,完成依赖注入。
然后我们还是定义一个配置类,用来配置扫描规则;
@Configuration
@ComponentScan(basePackages = {"com.zzm.iocdi"})
public class DiAppConfig {
}
最后创建测试类,运行main方法看下效果:
@Slf4j
public class IocDiTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(DiAppConfig.class);
Person person = context.getBean(DiBusinessPerson.class);
person.service();
}
}
从日志可以看到,cat对象已经被成功注入到DiBusinessPerson对象中,完成了依赖注入。
歧义性问题
但是,如果这个人想要养多只不同种类的宠物时,要怎么办呢?假设现在想再养只狗狗,那么首先,我们需要创建狗狗的类,然后在DiBusinessPerson类中再添加一只狗狗的变量
@Component
@Slf4j
public class DiDog implements Animal {
@Override
public void use() {
log.warn("狗【{}】是看门用的。", DiDog.class.getSimpleName());
}
}
@Component
public class DiBusinessPerson implements Person {
@Autowired
private Animal cat;
@Autowired
private Animal dog;
@Override
public void service() {
this.cat.use();
this.dog.use();
}
}
再次运行测试类的main方法,可以看到如下日志:
发现程序报错了,原因是Animal接口存在两个实现类,@Autowired注解根据Animal类型找到了两只动物,它不知道应该要注入哪一个,所以出现了这个错误。
为了解决这个问题,我们首先再回顾一下@Autowired注解的应用机制:
因为我们没有特殊设置@Autowired注解的属性,所以会按照它的默认机制,也就是上述流程进行bean的注入,它默认被注入的对象是不能为空的,所以这里就抛出了异常。
方案1
现在,我们来解决这个问题,既然它根据类型匹配到了多个宠物(猫、狗),那么我们让它根据名称可以定位到,修改下DiBusinessPerson类中猫和狗的变量名称,和对应的bean的名称保持一致,如下:
@Component
public class DiBusinessPerson implements Person {
@Autowired
private Animal diCat;
@Autowired
private Animal diDog;
@Override
public void service() {
this.diCat.use();
this.diDog.use();
}
}
然后再次运行程序,可见如下日志,表示已经注入成功了。
但是,这种写法业界内并不推荐,因为成员变量的名称被限制,怎么都感觉不靠谱,所以下面介绍另外一种方案。
方案2
@Primary注解的使用。
该注解用于修改注入优先级的,它会使得被标记的对象优先注入。
这里我们以猫对象为例,修改代码如下:
@Primary
@Component
@Slf4j
public class DiCat implements Animal {
@Override
public void use() {
log.warn("猫【{}】是抓老鼠用的。", DiCat.class.getSimpleName());
}
}
@Component
public class DiBusinessPerson implements Person {
@Autowired
private Animal cat;
@Autowired
private Animal dog;
@Override
public void service() {
this.cat.use();
this.dog.use();
}
}
然后再次运行程序,可见如下日志:
程序没有报错了,注入已经成功了。但是,有没有发现,这两个宠物都变成了猫,我们原先的设想是一只猫、一只狗,现在因为猫对象里面使用了@Primary注解提升了注入优先级,所以@Autowired注解在注入时就会优先选择猫。
很显然,这并不是我们想要的,为了达到目的,这里引出了这个注解,@Qualifier
它的value属性可以让我们指定需要注入的bean名称,从而和@Autowired相互配合,使我们得到正确的结果。
修改代码如下:
@Component
public class DiBusinessPerson implements Person {
@Autowired
@Qualifier("diCat")
private Animal cat;
@Autowired
@Qualifier("diDog")
private Animal dog;
@Override
public void service() {
this.cat.use();
this.dog.use();
}
}
然后再次运行程序,可见如下日志:
可以看到,猫和狗都各司其职了。
带有参数的构造方法类的装配
上面的例子都是基于这些类的构造方法是没有参数的,那么,有参数的构造方法的类要怎么注入呢?让我们一起来看看。。。
修改DiBusinessPerson类的代码,使其包含有参数的构造方法,代码如下:
@Component
public class DiBusinessPerson implements Person {
@Autowired
@Qualifier("diCat")
private Animal cat;
private Animal dog;
@Autowired(required = false)
private Animal dog2;
private Animal cat2;
public DiBusinessPerson(@Autowired @Qualifier("diDog") Animal dog){
this.dog = dog;
}
@Override
public void service() {
this.cat.use();
this.dog.use();
this.cat2.use();
}
@Autowired
@Qualifier("diCat")
public void setAnimal(Animal animal) {
this.cat2 = animal;
}
}
可以看到,这里将@Autowired @Qualifier两个注解用来了构造方法的参数和自定义方法上,所以spring在创建DiBusinessPerson 对象调用构造方法时,也会将需要的狗狗实例注入进来。这两个注解也可以用于自定义的方法参数之上,感兴趣的小伙伴可以自行尝试下。
然后我们再次运行main方法,得到如下日志:
可见是注入成功的。
另外,@Autowired注解默认是必须要匹配到一个值的,不然就会报错,但是我们可以设置其允许为空,如dog2属性的注入。
下次写关于bean的生命周期的,敬请期待。。。。。