上一篇 Spring Boot IoC(三)控制反转IoC
Bean之间的依赖称为依赖注入。
例:人穿不同的鞋子去完成不同的活动。比如,人穿篮球鞋去打篮球,穿跑步鞋去跑步锻炼,穿皮鞋去上班等等。所以人和鞋子就是依赖关系。
我们用代码来展现依赖,定义两个接口,一个事人类(Person),一个是鞋子(Shoes)
package com.lay.ioc.pojo.definiion;
public interface Person {
public void activity();
public void setShoes(Shoes shoes);
}
package com.lay.ioc.pojo.definiion;
public interface Shoes {
public void put();
}
Person实现类
package com.lay.ioc.pojo;
import org.springframework.beans.factory.annotation.Autowired;
import com.lay.ioc.pojo.definiion.Person;
import com.lay.ioc.pojo.definiion.Shoes;
@Component
public class Programmer implements Person{
@Autowired
private Shoes shoes=null;
@Override
public void activity() {
this.shoes.put();
}
@Override
public void setShoes(Shoes shoes) {
this.shoes=shoes;
}
}
Shoes实现类
package com.lay.ioc.pojo;
import com.lay.ioc.pojo.definiion.Shoes;
@Component
public class BasketShoes implements Shoes{
@Override
public void put() {
System.out.println("穿篮球鞋【"+BasketShoes.class.getSimpleName()+"】去打球");
}
}
@Autowired:根据属性的类型(by type)找到对应的Bean进行注入。
这里BasketShoes是Shoes接口的一种,所以Spring IoC容器会把BasketShoes的实例注入Programmer中。这样通过IoC容器获取Programmer实例的时候就能够使用BasketShoes实例来提供服务了。
测试代码如下
public class IocApplication {
private static Logger log=LoggerFactory.getLogger(IocApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
Person person=ctx.getBean(Programmer.class);
person.activity();
}
}
输出
穿篮球鞋【BasketShoes】去打球
IoC
容器是通过顶级接口BeanFactory
的方法getBean方法来获取对应的bean的,而getBean又支持根据类型的(by type)和根据名称的(by name)。下面我们定义另外一个跑鞋RunShoes
RunShoes实现类
package com.lay.ioc.pojo;
import org.springframework.stereotype.Component;
import com.lay.ioc.pojo.definiion.Shoes;
@Component
public class RunShoes implements Shoes {
@Override
public void put() {
System.out.println("穿跑鞋【"+BasketShoes.class.getSimpleName()+"】去跑步");
}
}
然后再测试项目,会看到如下错误日志打印
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'programmer': Unsatisfied dependency expressed through field 'shoes'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lay.ioc.pojo.definiion.Shoes' available: expected single matching bean but found 2: basketShoes,runShoes
由于Shoes有两个实现类,IoC并不知道你需要什么鞋子进行注入。但是我们将依赖属性名称从shoes
变为runShoes
,做如下改动:
@Autowired
private Shoes runShoes=null;
打印日志
穿跑鞋【BasketShoes】去跑步
并没有报错。
原因是@Autowired
注解默认是根据属性的类型(by type)找到对应的Bean进行注入。如果对应的类型Bean并不是唯一的,它会根据属性名称和Bean的名称进行匹配。如果匹配的上,就会使用该Bean,如果还是无法匹配,就会抛出异常。
注意Autowired
是一个默认必须找到对应的Bean的注解,如果不能确定其标注的属性一定会存在并允许这个被标注的属性为null
,那么你可以配置@Autowired
属性required
为false
@Autowired(required=false)
上面我们把依赖名称改为runShoes
虽然可以让Ioc找到注入的Bean,但是这样是不合理的,为了解决这个问题我们需要消除歧义性。主要用到两个注解@Primary
和@Quelifier
@Primary:告诉Spring Ioc容器,当发现有多个同样类型的Bean时,请优先使用这个Bean进行注入。
@Component
@Primary
public class RunShoes implements Shoes {
/*--------*/
}
@Quelifier:与@Autowired组合指定value,通过类型和名称一起找到Bean。
@Autowired
@Quelifier("runShoes")
在上面我们都基于一个默认的情况,那就是不带参数的构造方法下实现依赖注入。但是事实上有些类只有带有参数的构造方法,上面的方法就会失效。为了实现带参数的构造方法类的装配,我们可以使用@Autowired
注解对构造方法的参数进行注入。
我们对Programmer
程序员这个类进行改造
@Component
public class Programmer implements Person{
private Shoes shoes=null;
public Programmmer(@Autowired @Quelifier("runShoes") Shoes shoes){
this.shoes=shoes;
}
@Override
public void activity() {
this.shoes.put();
}
@Override
public void setShoes(Shoes shoes) {
this.shoes=shoes;
}
}
可以看到,代码中取消了@Autowired对属性和方法的标注。在参数上加入了@Autowired和@Qualifier注解,使得它能够注入进来。这里使用@Qualifier是为了避免歧义性。当然,如果环境中只有一个实现类,则可以完全不用@Qualifier,仅使用@Autowired就可以了。
下一篇 Spring Boot IoC(五)Bean生命周期