您现在的位置是:首页 >学无止境 >全注解下的SpringIoc 续1-依赖注入网站首页学无止境

全注解下的SpringIoc 续1-依赖注入

hello fafa 2023-06-03 16:00:03
简介全注解下的SpringIoc 续1-依赖注入

上篇文章介绍了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注解的应用机制:

Created with Raphaël 2.3.0 开始 根据类型匹配bean 根据类型是否可以准确匹配到? 注入成功 结束 根据名称匹配bean 根据名称是否可以准确匹配到? 是否可以为空? 抛出异常,注入失败 yes no yes no yes no

因为我们没有特殊设置@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的生命周期的,敬请期待。。。。。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。