阅读 1750

初识Spring —— Bean的装配(二)

这篇文章来自我的博客

正文之前

上一篇文章已经介绍了 Bean 的基本概念,所以今天这篇文章是基于上一篇中 Bean 的三种装配方式来进一步探讨,内容中会穿插着基础的依赖注入的概念

主要内容

  1. 自动装配
  2. 使用 XML 装配
  3. 使用 Java 装配

正文

1. 自动装配

  • Bean 的命名

上一篇文章中使用注解定义 Bean 的时候都是在类的定义上加注解,小写的类名作为 Bean 的 ID,但是我们可以自己命名 Bean:

设置组件的值为 Bean 的名字,如果这么做了,在测试中就找不到名为“student”的 Bean 了:

将测试中获取的 Bean 的名字改为“myBean”之后就能通过测试了

  • 组件扫描

上次讲到了组件扫描,设置组件扫描的方式有两种:

  1. XML 文件中配置
  2. 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,就需要一下方案来消除歧义:
  1. 标识首选的一个 Bean:@Primary 注解

测试后,证实了注解有效:

  1. 在需要注入的地方使用 @Qualifier注解:

这个注解对于构造器注入是不可用的,接下来在 Setter 方法上装配的时候再讲解

怎么在 Setter 方法上使用呢?

我们需要修改一下 Student 类,并且暂时隐藏一下 mathBook 这个 Bean:

带有 @Autowired 注解,在实例化时能够自动匹配 Book 类型的 Bean:

关于 Setter 方法和构造器来装配 Bean,就目前的学习情况来说,只有一点区别,如果有多个不同类型的 Bean,使用构造器能够指定装配的顺序

现在我们还原 mathBook 这个 Bean,如果出现上述的两个可能抛出异常的情况,这里有两种解决方式:

  1. @Primary 注解:

和上述类似,不多说

  1. 在需要注入的地方使用限定符,也就是 @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 的引用,然后来进行测试:

如果列表中也存放字符串,而不是引用,在元素中使用属性来代替属性

2. 设值注入

修改 Student 类和 Bean 配置文件:

然后在 Bean 的配置文件中加上这么一句:

    <bean id="student" class="bean.Student">
        <property name="book" ref="englishBook"/>
    </bean>
复制代码

属性是为 Setter 方法提供服务的,和上述的属性所提供的服务是一样的,在这个语句中,我在 book 中注入了 ID 为 englishBook 的 Bean 的引用,然后测试,会输出英语书的信息:

关于字面量和列表的注入,其实和构造器注入是类似的,将属性替换为,只不过,设置注入需要设置属性名,也就是将 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 的核心

接下来还陆续会有文章来讲述这一概念

文章分类
后端