这篇文章来自我的博客
正文之前
上一篇文章已经介绍了 Bean 的基本概念,所以今天这篇文章是基于上一篇中 Bean 的三种装配方式来进一步探讨,内容中会穿插着基础的依赖注入的概念
主要内容
- 自动装配
- 使用 XML 装配
- 使用 Java 装配
正文
1. 自动装配
- Bean 的命名
上一篇文章中使用注解定义 Bean 的时候都是在类的定义上加注解,小写的类名作为 Bean 的 ID,但是我们可以自己命名 Bean:
设置组件的值为 Bean 的名字,如果这么做了,在测试中就找不到名为“student”的 Bean 了:
将测试中获取的 Bean 的名字改为“myBean”之后就能通过测试了
- 组件扫描
上次讲到了组件扫描,设置组件扫描的方式有两种:
- XML 文件中配置
- Java 配置类中添加注解
我们再还原 student 这个 Bean 的状态,也就是在类的定义上加一个注解 @Component:
在配置文件中添加组建扫描:
这个是上次讲过的,就不再多说,然后来看看 Java 配置类的组件扫描:
添加的注解 @Component 带一个参数,指定了扫描的包的位置,就相当于 XML 配置文件中的 base-package 属性
如果不填写参数,就扫描此文件所在的包,在这里,我们扫描名为“bean”的包,点击注解左边的那个按钮就能跳转到 Student 类(bean 包中)
- 进阶自动装配
此处引用《Spring实战》的一句话:
简单来说,自动装配就是让 Spring 自动满足 Bean 依赖的一种方法,在满足依赖的过程中,会在 Spring 应用上下文中寻找匹配某个 Bean 需求的其他 Bean
我们这里添加一个接口 Book,至于为什么用接口而不用实现类,因为我们后文要对这个接口做多个实现:
然后创建一个实现了 Book 接口的类,并且将其作为一个 Bean:
既然是自动装配,肯定要用到注解 @Autowired,那么,这个注解可以用在什么地方呢?
- 实例化
- 构造器(构造器注入)
- Setter 方法(设值注入)
第一点之前说过,在实例化时候添加这个注解进行自动装配
怎么在构造器上使用呢?
我们修改一下 Student 类:
在构造 Student 的时候,我将一个 Book 类注入,因为我们刚才已经有了 Book 类的实现,所以这里注入的是 englishBook 这个 Bean
需要注意的是:Spring 在创建 student 这个 Bean 的时候,会传入一个 Book 类型的 Bean
有两种情况会导致抛出异常,没有所匹配的 Bean 或有多个所匹配的 Bean
为了验证第一种情况,我将 EnglishBook 的 @Component 注解去掉,这样子就没有 Book 类型的 Bean 了
- 将 @Autowired 的属性值 required 设置为 false,可避免图中的缺少 Bean 的情况:
运行测试之后,看到两条主要的错误信息:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'student' defined in file [C:\Users\94545\Desktop\Developer Folder\Java Test Folder\springtest\out\production\springtest\bean\Student.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'bean.Book' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'bean.Book' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
首先是名为“student” 的 Bean 的构造器中的依赖关系出错了,第二是没有 Book 类型的 Bean,导致 Student 的 Bean 的构造器中条件不符合
为了验证第二种情况,我将代码还原,并且创建另一个 Book 类型的 Bean:mathBook,在此情况下,测试是不会通过的,因为没有单一符合条件的 Bean
- 若有多个符合要求的 Bean,就需要一下方案来消除歧义:
- 标识首选的一个 Bean:@Primary 注解
测试后,证实了注解有效:
- 在需要注入的地方使用 @Qualifier注解:
这个注解对于构造器注入是不可用的,接下来在 Setter 方法上装配的时候再讲解
怎么在 Setter 方法上使用呢?
我们需要修改一下 Student 类,并且暂时隐藏一下 mathBook 这个 Bean:
带有 @Autowired 注解,在实例化时能够自动匹配 Book 类型的 Bean:
关于 Setter 方法和构造器来装配 Bean,就目前的学习情况来说,只有一点区别,如果有多个不同类型的 Bean,使用构造器能够指定装配的顺序
现在我们还原 mathBook 这个 Bean,如果出现上述的两个可能抛出异常的情况,这里有两种解决方式:
- @Primary 注解:
和上述类似,不多说
- 在需要注入的地方使用限定符,也就是 @Qualifier注解:
然后就能打印出英语书的信息(不截图了)
这个注解有一点灵活的地方就是,能够在 Bean 的定义上设置限定符,也就是说,限定符不一定要是 Bean 的名字,做个测试:
这时候,如果还使用 englishBook 作为限定符,也能够定位到这个 Bean,但是我们用自定义的限定符也可以,修改 Setter 方法上的限定符,进行测试:
证明使用 testQualifier 作为限定符也能得到结果
2. 使用 XML 装配
使用 XML 装配也有分构造器注入还是设值注入(Setter)方法,接下来的前三个使用的是构造器注入
1. 构造器注入
传递 Bean 的引用
将自动装配部分添加的注解取消掉,把代码还原成初始状态,然后我们开始使用 XML 装配:
暂时不配置 mathBook 这个 Bean,并修改 StudentTest 的上下文配置信息:
最后便是 Bean 的配置文件:
首先创建 englishBook 这个 Bean,在创建 student 这个 Bean,这里有一个元素
<constructor-arg ref=""/>
因为我在 student 的构造器中有一个名叫 englishBook 的Bean 作为参数,所以 Spring 会创建一个 ID 为 englishBook 的Bean,并将其引用传递至 student 的构造器中,ref 属性表明了传递引用的 Bean 的ID
传递字符串
如果我在 student 的构造器中传入的不是某个 Bean 的引用,而是某个字符串呢?
修改 XML 文件中的配置信息:
value 属性表示的是构造器中的字面量的值,可以是字符串、数字或布尔值等
然后测试一下:
证明字面量被成功注入了
传递列表
一个学生不可能只有一本书,所以我们要考虑,如何传入列表呢?
首先我们先修改 Student 类,传入的参数不仅是字符串,还有一个存放书本的列表:
然后在 XML 文件中配置 Bean:
<bean id="englishBook" class="bean.EnglishBook"/>
<bean id="mathBook" class="bean.MathBook"/>
最后配置 student 这个 Bean,并且配置构造器参数:
<bean id="student" class="bean.Student">
<constructor-arg value="I have some books"/>
<constructor-arg>
<list>
<ref bean="englishBook"/>
<ref bean="mathBook"/>
</list>
</constructor-arg>
</bean>
第一个参数是字符串,第二个是 Book 类型的 List,列表中存放的是刚才创建的两个 Bean 的引用,然后来进行测试:
如果列表中也存放字符串,而不是引用,在元素<list></list>中使用<value>属性来代替<ref>属性
2. 设值注入
修改 Student 类和 Bean 配置文件:
然后在 Bean 的配置文件中加上这么一句:
<bean id="student" class="bean.Student">
<property name="book" ref="englishBook"/>
</bean>
<property>属性是为 Setter 方法提供服务的,和上述的<constructor-arg>属性所提供的服务是一样的,在这个语句中,我在 book 中注入了 ID 为 englishBook 的 Bean 的引用,然后测试,会输出英语书的信息:
关于字面量和列表的注入,其实和构造器注入是类似的,将<constructor-arg>属性替换为<property>,只不过,设置注入需要设置属性名,也就是将 a Bean 的引用(或字面量或集合)注入到 b 属性中,就不再详述
3. 使用 Java 装配
除了使用 XML,还可使用 Java 代码来装配 Bean 并且达到依赖注入的目的
所以我们需要先修改测试中的上下文配置信息:
@ContextConfiguration(classes = StudentConfig.class)
再将 Student 类改回构造器注入的类型:
使用 Java 进行 Bean 的装配在上一篇文章中已经提到,这里就说一说依赖注入的实现:
在 Java 配置类中定义两个 Bean:
在 student 中使用构造器注入了 englishBook 这个 Bean,运行测试,将会得到英语书的输出:
总结
其实,这一篇文章把 Bean 的装配和依赖注入混合起来将了,可能有些人会觉得乱,但是三种方式其实从思路来说是类似的,通过认真地阅读这两篇关于 Bean 的介绍,就能对 Spring 的 DI 以及 IoC 有一个基本了解
DI(Dependency Injection)称为依赖注入,通俗的说,是在运行时,动态地满足某个对象对其他对象的需求,用上面的例子来说,student 在运行时需要 Book 类型的 Bean 作为依赖,我就给它注入一个 Bean,叫做 englishBook,这就能够叫做依赖注入
IoC(Inversion of Control)称为控制反转,在上面做的所有测试中,我都没有手动创建某个对象的实例,比如说注入一个列表,我甚至都没有创建一个列表的实例,那么为什么还会通过测试呢?因为 Spring 帮我在程序运行时创建了各个 Bean 的实例
IoC 能够由 DI 来实现,也就是说,程序中的各个组件之间的依赖关系都是由 Spring 来管理的,这就称作控制反转,这是 Spring 的核心
接下来还陆续会有文章来讲述这一概念