Skip to content

2. Spring 框架简介

Spring 于 2004 年首次作为对象容器问世。此后,它已发展出多个分支:Spring MVC、Spring Data、Spring Batch 等 [http://spring.io]。 本章仅介绍对象容器。以下是几个关键要点:

  • 一个应用程序包含多个类,其中某些类会共享必须是唯一的对象(单例)。Spring 负责创建和管理这些单例;
  • Spring 将这些单例放置在一个称为“上下文”的结构中;
  • 类可通过名称、类型或两者结合的方式向 Spring 请求,从而访问应用程序中的单例;
  • Spring 会创建单例并管理其可能存在的依赖关系:一个单例实际上可能引用一个或多个其他单例。当 Spring 创建单例时,它也会同时创建这些依赖关系;
  • 当基于 Spring 的应用程序启动时,它可请求 Spring 创建应用程序中的所有单例。这些单例随后将在 Spring 上下文中可用;
  • Spring 简化了分层架构和基于接口的编程。在简单情况下,每一层都由一个单例实现,并实现一个接口。如果应用程序使用的是各层的接口而非其实现类,那么便能构建出一种可扩展的架构,该架构凭借以下两个特性,允许在不改变其他层的情况下更换某一层级的实现:
    • 应用程序通过层名称获取该层的引用。Spring 会返回实现该层的类的引用;
    • 应用程序将此引用视为该层的接口引用,而非具体类的引用;

单例的声明可通过以下三种方式实现,且可以混合使用:

  • 在 XML 文件中,
  • 在专门的配置类中;
  • 通过注解在任意类中声明;

下面我们将介绍三个配置示例:

  • [exemple-01]:在单个文件 XML 中进行集中配置;
  • [exemple-02]:配置集中于单个 Java 类中;
  • [exemple-03]:配置分布在多个 Java 类中;

最后一个示例 [exemple-04] 关注分层架构的 Spring 配置。这是最重要的示例,本文后续将不断引用它来配置各种架构。

这四个示例为后续内容奠定了基础:

  • Spring配置与依赖注入;
  • 使用 Maven 管理项目依赖;
  • 使用 JUnit 测试项目;

2.1. 搭建工作环境

您需要具备:

  • 已安装 JDK(Java 开发工具包)(第 23.1 节);
  • 已安装 Maven 依赖管理器(第 23.2 节);
  • 已安装 IDE Spring Tool Suite(STS)(第 23.3 节);
  • 下载了文档 [http://tahe.developpez.com/java/spring-database] 的源代码;

将示例文件夹 [eclipse config] 中的运行配置导入 STS。这些配置尤为重要。 某些项目在运行时需要向 JVM 传递参数,而此类配置通常令人头疼。此外,本文档使用的是 Maven 项目。当您遇到以下警告时:

注意:请运行 [Alt-F5] 以重新生成所有 Maven 项目。

强烈建议您遵循此建议。如果不采取这一预防措施,项目可能会出现难以理解的错误,仅仅是因为项目之间的 Maven 依赖关系有误。

  • 在 [1] 中,右键单击 [Package Explorer];
  • 在 [4a-4b-4c] 中,选择示例文件夹 [eclipse config / launch configurations] [4b];
  • 在 [5] 中,查看可用配置。全选;
  • 在 [6] 中,完成向导;
  • 在 [7-8] 中,查看已导入的运行配置;
  • 在 [8-9] 中,取消勾选 [9] 以显示未加载到 STS 中的项目配置。当前即为该状态;
  • 在 [10] 中,Java 应用程序;在 [11] 中,我们将要研究的前三个示例的运行配置;
  • [12] 用于测试,JUnit 用于测试,[13] 用于本节第四个示例的运行配置;

现在,请创建一个名为 [M2_REPO] 的 Eclipse 变量,该变量将指定本地 Maven 仓库的文件夹(参见第 23.2 节)。该变量在多个运行配置中使用:

我们为变量 [M2_REPO] 赋予了如下所示的值(见下文 [6]):

现在,导入文件夹 [spring-core] 中的四个示例:

  • 导入到 [1] 中,右键单击 [Package Explorer];
  • 为 [4a-4b],选择示例文件夹 [spring-core];
  • 在 [5] 中,选中文件夹内的所有项目并生成 [Finish];
  • 在 [6] 中,将 [Package Explorer] 中的四个项目;

2.2. 示例-01

2.2.1. Eclipse项目

 

2.2.2. [Personne]类

 

package istia.st.spring.core;

public class Personne {

    // 字段
    private String nom;
    private String prenom;
    private int age;

    // 构造函数
    public Personne() {

    }

    public Personne(String nom, String prénom, int âge) {
        this.nom = nom;
        this.prenom = prénom;
        this.age = âge;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s,%d]", prenom, nom, age);
    }

    // 获取器和设置器

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getPrenom() {
        return prenom;
    }

    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

:getter 和 setter 可以按以下方式自动生成 [1-2]:

2.2.3. 类 [Appartement]

 

package istia.st.spring.core;

public class Appartement {

    // 字段
    private Personne proprietaire;
    private int surface;

    // getter 和 setter

    public Personne getProprietaire() {
        return proprietaire;
    }

    public void setProprietaire(Personne proprietaire) {
        this.proprietaire = proprietaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", proprietaire, surface);
    }

}

注意:该类没有显式构造函数。在这种情况下,默认情况下始终存在一个不带参数的构造函数,该构造函数不执行任何操作。当创建构造函数时,该默认构造函数将不再隐式存在。因此必须显式定义它:

public Appartement(){
}

2.2.4. Spring配置文件

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- 人员 01 -->
    <bean id="personne_01" class="istia.st.spring.core.Personne">
        <constructor-arg index="0" value="dubois" />
        <constructor-arg index="1" value="paul" />
        <constructor-arg index="2" value="34" />
    </bean>
    <!-- 人员 02 -->
    <bean id="personne_02" class="istia.st.spring.core.Personne">
        <property name="nom" value="martin" />
        <property name="prenom" value="micheline" />
        <property name="age" value="18" />
    </bean>
    <!-- 人员列表 -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- 一套公寓 -->
    <bean id="appartement" class="istia.st.spring.core.Appartement">
        <property name="surface" value="100" />
        <property name="proprietaire" ref="personne_01" />
    </bean>
</beans>
  • 第 2、27 行:单例在 <beans> 标签内定义;
  • 第 6-10 行:每个单例由一个 <bean> 标签定义;
  • 第 6 行:[id] 是单例的标识符。[class] 是待实例化类的全名;
  • 第 7-9 行:传递给 [Personne] 类构造函数的三个参数;
  • 第12-16行:首先通过默认构造函数[new Personne()]创建类[Personne]。 然后,对于每个 [property] 标签,都会使用该类的 setter 方法。例如第 13 行,将执行 [setNom("martin")] 方法。因此必须存在 [setNom] 方法。这一点很重要,请务必记住;
  • 第 18-21 行:<util:list> 标签用于定义一个作为列表的单例;
  • 第 19 行:引用了第 6 行定义的单例 [personne_01]。这被称为依赖注入。有两个属性可用于初始化单例的字段:
    • [value]:用于将基本类型值(字符串、数字、日期等)赋值给字段,
    • [ref]:用于将 Spring 对象的引用赋值给字段;

:Spring 配置文件可通过以下方式生成 [1-4]:

2.2.5. 可执行类

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo01 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // 获取 Spring 上下文
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // 获取 Bean
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // 显示它们
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // 获取到的 Bean 是单例
        // 可以多次请求,始终获取到同一个 Bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • 第 14 行:创建 Spring 上下文。此时,[config-01.xml] 文件中定义的所有单例都会被实例化;
  • 第 16 行:请求获取由 [personne_01] 标识的、类型为 [Personne] 的单例的引用。 该第二个参数是可选的,但若省略,则会获得一个 [Object] 类型的引用,该引用随后需要转换为 [Personne] 类型;
  • 第 19 行:由于 [Appartement] 类型仅有一个单例,因此不使用 Bean 的名称,而仅使用其类型;
  • 第 18 行:同时使用了目标单例的标识符和类型。由于 [new ArrayList<Personne>().getClass()] 类型仅有一个单例,因此标识符是多余的;
  • 第 32-33 行:表明如果多次请求同一个单例,确实总是获得相同的引用,从而证明我们确实在处理一个单例。这一点很重要,需要理解;

:可按以下方式生成可执行类 [1-6]:

  • 只有勾选了 [6],生成的类才会包含一个静态方法 [main],从而使其可执行;

2.2.6. 项目的依赖项

 
  • Spring 依赖项:[spring-core, spring-beans, spring-context, spring-expression, commons-logging];

依赖项按以下方式添加到项目中:

  • 在 [1] 中:右键单击项目 / [Build Path] / [Configure Build Path];
  • 转换为 [2]:若待添加的 JARs 位于项目文件夹内,则为 [Add JARs];否则为 [Add External JARs];
  • 在 [3] 中,选择要添加到项目 ClassPath 中的 JARs(此处它们位于项目内的 [lib] 文件夹中);

定义:项目中的 [ClassPath] 是指执行该项目的 JVM(Java 虚拟机)为查找项目引用的类而遍历的所有文件夹。 对于一个 Eclipse 项目,[ClassPath] 由以下元素组成:

  • 项目的 [bin] 文件夹;
  • 项目中的 [Build Path] 文件;

[bin] 文件夹是由编译 [src] 文件夹生成的。 因此,放置在 [src] 文件夹中的所有内容都会自动成为 [ClassPath] 的一部分(即使不是 .java 文件)。 因此,在上述项目中,位于 [src] 文件夹内的 Spring 配置文件 [config-01.xml],将在项目运行时成为 [Classpath] 的一部分。

2.2.7. 结果


févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true

2.3. 示例-02

2.3.1. Eclipse 项目

 

2.3.2. Spring 配置类


package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public Appartement appartement(Personne personne_01) {
        Appartement appartement = new Appartement();
        appartement.setSurface(200);
        appartement.setPropriétaire(personne_01);
        return appartement;
    }
}
  • 第 9 行:注解 [@Configuration] 是 Spring 注解。它表示被注解的类定义了单例。这些单例通过注解 [@Bean] 进行定义。 Spring 将执行所有带有 [@Bean] 注解的方法。这些方法用于创建应用程序的单例;
  • 第 12-15 行:定义了一个由 [personne_01] 标识的单例,即该方法的名称。
  • 第 23 行:参数 [personne_01, personne_02] 采用单例的名称。Spring 将自动使用这些单例的引用对其进行初始化。这被称为参数注入;

这种配置单例的方式比使用 XML 文件的方式更为明确。实际上,我们自己重现了 Spring 基于 XML 文件所做的隐式操作。

2.3.3. 可执行类

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // 获取 Spring 上下文
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // 获取 Bean
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // 显示它们
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // 获取到的 Bean 是单例
        // 可以多次请求,始终获取到同一个 Bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • 第 13 行会实例化 [Config] 类中定义的所有 Bean;
  • 其余代码保持不变;

2.3.4. 项目的依赖关系

依赖关系由以下文件 [pom.xml] 确定:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-02</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
    </dependencies>
    <!-- 插件 -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

当使用那些自身依赖关系不明的 Java 库时,手动管理项目依赖关系会变得令人头疼。例如,管理数据库访问的 [Hibernate] 框架就有数十个依赖项。[Maven] 项目解决了这一问题。 只需指定所需的依赖项,系统便会自动在遍布网络的Maven仓库中进行搜索。如果所请求的依赖项本身还有其他依赖,这些依赖也会被自动下载。下载的依赖项将存储在本地仓库中。如果后续其他应用程序需要相同的依赖项,系统将不再重新下载,而是直接从本地仓库中获取。 一个依赖项由以下元素构成:

  • 第 17 行:一个 <dependency> 标签;
  • 第 18 行:一个 [groupId] 属性,通常用于标识创建该依赖项的公司;
  • 第 19 行:[artifactId] 属性,用于标识该依赖项;
  • 第 20 行:一个 [version] 属性,用于标识所需的版本;

项目生成过程本身将生成一个由第 4-8 行定义的 Maven 组件:

  • 第 4-6 行:即我们刚刚描述的 [ groupId, artifactId,version] 属性;
  • 第 7-8 行:为可选属性;

稍后我们将详细讨论第24-40行的作用。要将一个普通的Eclipse项目转换为Maven项目,需要做两件事:

  • 创建前述的 [pom.xml] 文件;
  • 声明该项目现为 Maven 项目 [1-4]:

Maven 项目的图标带有字母 M [4]。字母 S 表示该项目包含 Spring 组件。不建议(如我们刚才所做的那样)将 Eclipse 项目转换为 Maven 项目,因为这样项目将不具备 Maven 项目应有的结构,这有时可能会导致意想不到的问题。

2.3.5. 生成项目的 Maven 工件

我们称项目中的 Maven 工件为文件 [pom.xml] 第 4-6 行定义的元素:


    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>

要生成此工件,文件 [pom.xml] 中必须包含以下第 3-7 行:


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

这些行定义了能够生成该项目构建成果的 Maven 插件。随后按以下步骤操作:

生成的工件将存入本地 Maven 仓库。该仓库的位置可在 Eclipse 配置中找到:

 

此时可以验证 Maven 构建产物是否安装正确:

 

现在,另一个本地 Maven 项目可以使用该归档文件。

2.4. 示例-03

2.4.1. Eclipse 项目

这次我们创建一个名为 [1-8] 的 Maven 项目:

  • 在 [3b] 中:指定一个用于生成项目的空文件夹;
  • 在 [4] 中:项目所属的 Maven 组标识符;
  • 在 [5] 中:生成的 Maven 工件名称:
  • 在 [6] 中:其版本号;
  • 在 [7] 中:其打包模式(还有 war、ear、apk 等);
  • 在 [8] 中:由此创建的项目;

一个 Maven 项目默认具有明确的目录结构:

  • [src / main / java]:项目的源代码。这些源代码编译后的产物将存放在项目的 [target/classes] 文件夹中;
  • [src / main / resources]:必须位于项目类路径中但并非 Java 源代码的资源。这些资源将原样复制到项目的 [target/classes] 文件夹中;
  • [src / test / java]:项目测试的源代码。这些源代码编译后的产物将放入项目目录下的 [target/test-classes] 文件夹中。这些内容不会被纳入项目的 Maven 归档中;
  • [src / test / resources]:这些资源必须位于项目的类路径中以便进行测试,但并非Java源文件。它们将被原样复制到项目的[target/test-classes]文件夹中;

我们按以下方式完善该项目:

 

2.4.2. Maven配置

系统默认会生成一个名为 [pom.xml] 的文件。我们将对其进行如下修改:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-03</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- 父级 Maven 项目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring 上下文 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

    </dependencies>

    <!-- 插件 -->
    <build>
        <plugins>
            <!-- 用于生成包含其依赖项的项目归档 -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- 用于将项目工件安装到本地 Maven 仓库 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 第 11 行:项目编码为 UTF-8;
  • 第 12 行:使用 JDK 1.8 编译该项目;
  • 第 16-20 行:对于使用 Spring 库的项目,建议使用名为 [spring-boot-starter-parent] 的父 Maven 项目。该项目定义了各种 Spring 库及其依赖项的版本。这样就无需在依赖项定义中再次指定这些版本。 因此第24-27行无需指定[spring-context]的具体版本。它将采用父项目[spring-boot-starter-parent]中定义的版本。这种方法可避免因依赖项之间版本不兼容而产生的问题。父项目中定义的依赖项彼此兼容;
  • 第29-32行:Spring通过日志库在控制台输出大量信息。此处导入了该日志库;
  • 第 40-47 行:一个 Maven 插件,我们稍后将详细讨论;
  • 第 50-52 行:用于生成项目 Maven 构建产物的插件;

2.4.3. Spring 的配置类

  

[Config] 类如下:


package istia.st.spring.core.config;

import istia.st.spring.core.entities.Personne;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public int mySurface() {
        return 200;
    }
}
  • 这里包含一段已注释的代码,并有两处新内容:
    • 第 13 行:表明在 [spring.core.entities] 包中还有其他 Bean 需要实例化,
    • 第 34-37 行:一个名为 [mySurface] 的 Bean;

2.4.4. 类 [Appartement]

 

package spring.core.entities;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Appartement {

    // 由 Spring 注入的字段
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;

    @Autowired
    @Qualifier("mySurface")
    private int surface;

    // getter 和 setter
    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", propriétaire, surface);
    }

}
  • 第 7 行:注解 [@Component] 告知 Spring 该类是一个单例,框架需对其进行实例化并管理。正因为在类 [Config] 中,我们写入了 [@ComponentScan({ "istia.st.spring.core.entities" })],所以该单例会被找到;
  • 第 11 行:请求 Spring 将某个单例的引用注入该字段。该引用可通过两种方式定义:
    • 通过其标识符(第 12、16 行),
    • 通过其类型(若仅存在一个该类型的单例);

2.4.5. 项目运行

运行项目后,控制台显示以下结果:

17:32:39.797 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
....
17:32:40.134 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appartement'
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
17:32:40.135 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personne_01'
beans [p01,p01b] identiques ? true
  • 第1-3行:Spring会生成大量日志,多达数十行。这些日志对于调试无法正常运行的项目非常有用。当项目运行正常时,可通过以下方式减少日志输出:
  

在 [src / main / resources] 文件夹中创建以下 [logback.xml] 文件:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- 编码器默认被分配类型
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- 日志级别控制 -->
  <root level="info"> <!-- info、debug、warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • 第12行,设置日志级别。[debug]的日志级别非常详细,而[info]则简略得多;

以下是使用 [level=info] 生成的结果:

17:39:58.580 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7cf10a6f: startup date [Tue Apr 07 17:39:58 CEST 2015]; root of context hierarchy
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
beans [p01,p01b] identiques ? true

日志仅剩一行。

2.4.6. 生成包含依赖项的项目归档

在上一个项目中创建的归档文件也可用于非Maven的Eclipse项目。某些项目会使用大量库,确保不遗漏任何一个可能颇具挑战。这正是Maven大显身手之处,因为只需指定最高级别的依赖,其他较低级别的依赖就会自动添加到项目的类路径中。 当非Maven的Eclipse项目需要使用Maven项目的归档文件时,可以生成包含所有依赖关系的Maven项目工件(这与前一个项目的情况不同)。为此,文件[pom.xml]中必须包含以下第3至10行代码:


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

这些行定义了能够生成包含所有依赖项的项目工件的 Maven 插件。随后按以下方式操作 [1-6]:

  • [4-6] 代表一个 Maven 运行配置;
  • 在 [4] 中,输入任意名称;
  • 在 [5] 中,指定项目文件夹;
  • 在 [6] 中,填写 Maven 目标(goals):
    • [clean]:删除项目的 [target] 文件夹;
    • [compile]:项目被编译。编译产物被放置在重新生成的 [target] 文件夹中;
    • [assembly:single]:项目及其依赖项的类被放入位于 [target] 文件夹中的单个 jar 归档文件中;

执行后,得到以下结果:

JAR 归档文件是一种压缩文件,因此可以使用解压工具打开。解压上述归档文件后,将得到以下目录结构:

  • [8] 文件中包含项目依赖项的类;
  • [9] 包含项目本身的类;

2.5. 示例-04

2.5.1. 目标

本示例改编自文档 [Introduction à Spring IoC] 中展示的一个示例,该文档展示了 Spring 在多层架构配置中的作用。在原始文档中,该示例采用通过文件 XML 进行的 Spring 配置。 在此,我们将通过Java类和注解进行配置来处理该示例。

我们希望在此为以下架构配置一个 Spring 项目:

每个层都提供了一个由两个类实现的接口。我们希望展示:借助Spring,可以更改某一层级的实现,而对其他层级的代码完全不产生影响。

2.5.2. Eclipse 项目

2.5.2.1. Génération

我们创建一个新类型的项目:

  • 在 [4] 中,输入 Eclipse 项目的名称;
  • 在 [5] 中,选择 Maven 项目;
  • 在 [6] 中,选择 Java 版本 >=1.7;
  • 在 [7] 中,选择提供的 Spring Boot 版本;
  • [8-11] 中的信息是 Maven 信息;
  • 在 [12] 中,可以选择一个或多个建议的依赖项。这将导致在 Maven 的 [pom.xml] 文件中集成若干依赖项;
  • 在 [13] 中,指定一个现有的空文件夹来存放该项目;
  • 生成名为 [14] 的项目。接下来我们将对其组成部分进行详细解析;

该项目是一个由以下 [pom.xml] 文件配置的 Maven 项目:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-04</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-core-04</name>
    <description>Programmation par interfaces</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- 从存储库查找父级 -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.SpringCore04Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 第 6-12 行:包含在项目创建向导中输入的信息;
  • 第 14-19 行:父 Maven 项目,其中定义了若干库及其版本。如果其中某个库是该项目的依赖项,则会在 [pom.xml] 文件中列出该库,但不包含其版本;
  • 第 23 行:仅当计划生成项目的可执行归档文件时才使用此行。否则该行将被忽略;
  • 第 28-31 行:Spring Boot 项目的最低依赖。需注意,我们在复选框中未选择任何依赖;
  • 第 33-37 行:管理与 Spring 集成的单元测试所需的依赖项 JUnit [http://junit.org/]。第 36 行表明该依赖项仅用于测试。因此,它不会被包含在项目归档中;
  • 第 42-45 行:用于生成项目 Maven 构建产物的插件;

该文件引入的依赖项列表如下:[1]:

我们将看到,这些依赖对于我们此处的操作已足够。

2.5.2.2. 可执行类

  

可执行类 [SpringCore04Application] [[2] 如下所示:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCore04Application.class, args);
    }
}
  • 第6行,注解 [@SpringBootApplication] 是三个注解 [@Configuration, @EnableAutoConfiguration, @ComponentScan] 的简写,这意味着:
    • 类 [SpringCore04Application] 是一个 Spring 配置类;
    • 要求 Spring Boot 根据项目类路径(此处即 Maven 依赖项)中找到的类进行配置;
    • 检查当前目录(即 [SpringCore04Application] 类所在的目录),以查找其中可能存在的其他 Spring 组件;
  • 第 10 行:执行静态方法 [SpringApplication.run]。其第一个参数是一个 Spring 配置类,此处为类 [SpringCore04Application]。其第二个参数是传递给方法 [main](第 9 行)的参数列表。 静态方法 [SpringApplication.run] 的作用是创建 Spring 上下文,即创建配置类中或通过注解 [@ComponentScan] 扫描到的文件夹中找到的各个 Bean。 此处的 [main] 方法仅此而已。为了使其功能更丰富,我们将按以下方式对其进行改造:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        // Spring 上下文实例化
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        // 显示上下文
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        // 关闭上下文
        context.close();
    }
}
  • 第 12 行:静态方法 [SpringApplication.run] 返回其构建的 Spring 上下文;
  • 第 15-17 行:显示该上下文中所有 Bean 的名称;

可以通过以下方式运行该应用程序:[1-3]。常规方法 [Run As Java Application] 同样有效。

将得到以下结果:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-04-08 11:18:38.254  INFO 4796 --- [           main] demo.SpringCore04Application             : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788  INFO 4796 --- [           main] demo.SpringCore04Application             : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
  • 第 14-28 行:Spring 上下文中的 Bean。我们尚不清楚它们的作用。第 18 行出现了 Bean [springCore04Application],由于其注解 [@SpringBootApplication],该 Bean 自动成为 Spring Bean;
  • 其余行是级别为 [INFO] 的 Spring 日志。正如我们之前所见,这些日志可以通过放置在项目 Classpath 中的 [logback.xml] 文件进行控制:
  

<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- 编码器默认被分配类型
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- 日志级别控制 -->
  <root level="warn"> <!-- info、debug、warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

如果将上文第 12 行中的 [info] 替换为 [warn],则会得到以下结果:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer

日志已消失。仅显示级别为 [warn] 的消息,而此处并未出现该级别消息。

2.5.3. 架构各层的实现

现在我们将实现上述架构的三个层:

  

[DAO]层由[spring.core.dao]包实现。它提供以下[IDao]接口:


package spring.core.dao;

public interface IDao {

    public int doSomethingInDaoLayer(int a, int b);
}

该接口有两个实现:[Dao1] 和 [Dao2]:


package spring.core.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }

}

package spring.core.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }

}

[métier] 层由 [spring.core.metier] 包实现。它提供以下 [IMetier] 接口:


package spring.core.metier;

public interface IMetier {

    public int doSomethingInMetierLayer(int a, int b);
}

该接口有两个实现:[Metier1] 和 [Metier2]:


package spring.core.metier;

import spring.core.dao.IDao;

public class Metier1 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }
}

package spring.core.metier;

import spring.core.dao.IDao;

public class Metier2 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a--;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }


}

[UI] 层由 [spring.core.ui] 包实现。它提供以下 [IUi] 接口:


package spring.core.ui;

public interface IUi {

    public int doSomethingInUiLayer(int a, int b);
}

该接口有两个实现:[Ui1] 和 [Ui2]:


package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui1 implements IUi {

    private IMetier metier;
    
    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui2 implements IUi {

    private IMetier metier;

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

2.5.4. Spring 项目配置

  

配置类 [Config] 如下:


package spring.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;

@Configuration
public class Config {

    // -------------- 实现 [Ui1, Metier1, Dao1]
    @Bean
    public IDao dao1() {
        return new Dao1();
    }

    @Bean
    public IMetier metier1(IDao dao1) {
        Metier1 metier = new Metier1();
        metier.setDao(dao1);
        return metier;
    }

    @Bean
    public IUi ui1(IMetier metier1) {
        Ui1 ui = new Ui1();
        ui.setMetier(metier1);
        return ui;
    }

    // -------------- 实现 [Ui2, Metier2, Dao2]
    @Bean
    public IDao dao2() {
        return new Dao2();
    }

    @Bean
    public IMetier metier2(IDao dao2) {
        Metier2 metier = new Metier2();
        metier.setDao(dao2);
        return metier;
    }

    @Bean
    public IUi ui2(IMetier metier2) {
        Ui2 ui = new Ui2();
        ui.setMetier(metier2);
        return ui;
    }
}
  • 第 20-23 行:名为 [dao1](方法名)的 Bean 是类 [Dao1](第 22 行)的实例,该类被视为接口 [IDao] (第21行)。因此,Bean [dao1]被视为接口的实例(虽然术语不准确,但意思可以理解),而非类的实例。这一点非常重要。所有其他Bean也将是接口的实例;
  • 第 25-30 行:由类 [Metier1] 实现的接口 [IMetier] 的实例;
  • 第 32-37 行:由类 [Ui1] 实现的接口 [IUi] 的实例;
  • 第 20-37 行:使用 [Ui1, Metier1, Dao1] 实例实现 [UI, Metier, DAO] 层;
  • 第 40-57 行:使用 [Ui2, Metier2, Dao2] 实例实现 [UI, Metier, DAO] 层;

2.5.5. [JUnitTest] 单元测试

  

类 [JUnitTest] 被放置在 Maven 项目的 [src / test / java] 分支中。该分支中的元素不会被放入项目的最终归档中。其代码如下:


package spring.core.tests;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
  • 第 16 行:注解 [@SpringApplicationConfiguration] 是 Spring Boot Test 项目(第 8 行)的注解。它由文件 [pom.xml] 的以下依赖引入:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
</dependency>

该注解的参数是用于构建测试所需 Spring 上下文的配置类列表。此处我们使用了前面已介绍的配置类 [Config];

  • 第 17 行:注解 [@RunWith] 是注解 JUnit(第 5 行)的子注解。 其参数是负责执行测试的类,该类将取代JUnit框架的默认类。该类是一个Spring类(第9行)。它将使用测试类中存在的Spring注解;

完整的类如下


...

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {

    // [UI] 层
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;

    @Autowired
    @Qualifier("ui2")
    private IUi ui2;

    // [métier] 层
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;

    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;

    // 图层 [dao]
    @Autowired
    @Qualifier("dao1")
    private IDao dao1;

    @Autowired
    @Qualifier("dao2")
    private IDao dao2;

    @Test
    public void testDao() {
        Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
        Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
    }

    @Test
    public void testMetier() {
        Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
        Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
    }

    @Test
    public void testUI() {
        Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
        Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
    }

}
  • 第 8-10 行:注入(第 8 行)名为(第 9 行)[ui1] 的 Bean。请注意第 10 行注入的是接口实例而非类实例;
  • 第21-32行:以相同方式注入类[Config]中定义的其他Bean;
  • 第 34 行:注解 [@Test] 指定了一个在测试过程中需要执行的方法。其他可能的注解如下:
    • [@BeforeClass]:在开始测试前执行的方法;
    • [@AfterClass]:所有测试完成后需执行的方法;
    • [@Before]:在每次测试前执行的方法;
    • [@After]:每次测试结束后需执行的方法;
  • 第 36 行:验证调用 [dao1.doSomethingInDaoLayer(10, 20)] 是否确实返回 30。按惯例,第一个参数为预期值,第二个参数为实际值;
  • 第36行:测试接口[IDao]的实例[dao1];
  • 第 37 行:测试接口 [IDao] 的实例 [dao2];
  • 第 42 行:测试接口 [IMetier] 的实例 [metier1];
  • 第 43 行:测试接口 [IMetier] 的实例 [metier2];
  • 第 48 行:测试接口 [IUi] 的实例 [ui1];
  • 第 36 行:测试接口 [IUi] 的实例 [ui2];

测试方法中可使用的断言如下:

  • assertEquals(表达式1, 表达式2):验证两个表达式的值是否相等。支持多种表达式类型(int、String、float、double、boolean、char、short)。如果两个表达式不相等,则抛出类型为 [AssertionFailedError ] 的异常,
  • assertEquals(实数1, 实数2, delta):验证两个实数在误差 delta 范围内相等,即 c.a.d abs(实数1-实数2)<=delta。 例如,可以编写 assertEquals(实数1, 实数2, 1E-6) 来验证两个值在 10⁻⁶ 误差范围内相等,
  • assertEquals(message, expression1, expression2)assertEquals(message, 实数1, 实数2, delta) 是用于指定错误消息的变体,该消息将与 [AssertionFailedError] 类型的异常相关联,当 [assertEquals] 方法失败时会抛出该异常,
  • assertNotNull(Object)assertNotNull(message, Object):验证 Object 引用不为 null,
  • assertNull(Object)assertNull(message, Object):验证 Object 引用是否为 null,
  • assertSame(Object1, Object2)assertSame(message, Object1, Object2):验证 Object1Object2 引用是否指向同一个对象,
  • assertNotSame(Object1, Object2) assertNotSame(message, Object1, Object2):检查 Object1 Object2 引用是否指向不同的对象;

要执行该测试,可以按照以下步骤操作:

结果如下:

 

这里所有测试均已通过。那么,这个例子说明了什么?它展示了Spring框架在分层架构配置方面带来的灵活性。我们只需通过配置,即可决定使用[Ui1, Metier1, Dao1]还是[Ui2, Metier2, Dao2]的实现。 因此,在之前的 JUnit 测试中,如果仅保留 [ui1, metier1, dao1] 的 Bean 注入,则采用的是第一种架构。要更改架构,只需更换被注入的 Bean 即可。这一操作无需修改实现接口的各层代码。 这种编程方式被称为接口编程,因为我们不使用实现各层的类实例,而是使用其接口的实例。

2.6. 结论

  • Spring 管理着单例(仅有一个实例)对象。Spring 还管理着每次向 Spring 请求实例时都会被实例化的对象。本文档也将介绍这种情况;
  • 这些对象可以通过多种方式声明,且这些方式可以混合使用:
    • 在名为 XML 的文件中,
    • 在带有 [@Configuration] 注解的 Java 类中,
    • 使用任何带有 [@Component, @Service, ...] 注解的 Java 类;
  • Spring 对象可通过 [@Autowired] 注解注入到另一个 Spring 对象中。这被称为依赖注入(DI:Dependency Injection);
  • Spring 结合接口编程范式,在配置分层架构时表现得非常实用;