7. IoC 容器

7.1 Spring IoC 容器和 beans 简介

本章介绍了 Spring Framework implementation of the Inversion of Control(IoC)[1]原理。 IoC 也称为依赖注入(DI)。它是一个 process,其中 objects 定义它们的依赖关系,即它们使用的其他 objects,只能通过构造函数 arguments,工厂方法的 arguments,或者 object 实例在构造或从工厂方法返回后设置的 properties 。然后容器在创建 bean 时注入这些依赖项。这个 process 基本上是反向的,因此 name 控制反转(IoC),bean 本身通过使用 classes 的直接构造或诸如 Service Locator pattern 之类的机制来控制其依赖关系的实例化或位置。

org.springframework.beansorg.springframework.context包是 Spring Framework 的 IoC 容器的基础。 Bean 工厂接口提供了一种高级 configuration 机制,能够管理任何类型的 object。 ApplicationContextBeanFactory的 sub-interface。它增加了 Spring 的 AOP features 更容易整合;消息资源处理(用于国际化),事件发布;和 application-layer 特定的上下文,例如用于 web applications。

简而言之,BeanFactory提供 configuration framework 和基本功能,ApplicationContext添加更多 enterprise-specific 功能。 ApplicationContextBeanFactory的完整超集,在本章中专门用于 Spring 的 IoC 容器的描述。有关使用BeanFactory而不是ApplicationContext,的更多信息,请参阅第 7.16 节,“The BeanFactory”

在 Spring 中,构成 application 主干并由 Spring IoC 容器管理的 objects 称为 beans。 _ bean 是一个 object,它由 Spring IoC 容器实例化,组装和管理。否则,bean 只是 application 中许多 object 之一。 Beans 及其之间的依赖关系反映在容器使用的 configuration 元数据中。

7.2 容器概述

接口org.springframework.context.ApplicationContext表示 Spring IoC 容器,负责实例化,配置和组装上述 beans。容器通过读取 configuration 元数据获取有关 objects 实例化,配置和汇编的指令。 configuration 元数据以 XML,Java annotations 或 Java code 表示。它允许您表达组成您的 application 的 objects 以及这些 objects 之间丰富的相互依赖关系。

使用 Spring 为ApplicationContext提供了ApplicationContext接口的几个 implementation。在独立的 applications 中,创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext 来的实例是 common。虽然 XML 是定义 configuration 元数据的传统格式,但您可以通过提供少量 XML configuration 来声明性地启用对这些其他元数据格式的支持,从而指示容器使用 Java annotations 或 code 作为元数据格式。

在大多数 application 场景中,不需要显式用户 code 来实例化 Spring IoC 容器的一个或多个实例。对于 example,在 web application 场景中,application 的web.xml文件中的简单八(或左右)_boles 样板 web 描述符 XML 通常就足够了(参见Section 7.15.4,“web applications 的便捷 ApplicationContext 实例化”)。如果您使用Spring 工具套件 Eclipse-powered 开发环境,只需点击几下鼠标或按键即可轻松创建此样板文件配置。

下图是 Spring 如何工作的 high-level 视图。您的 application classes 与 configuration 元数据结合使用,以便在创建和初始化ApplicationContext之后,您拥有一个完全配置且可执行的系统或 application。

图 1_.Spring IoC 容器

容器魔术

7.2.1 配置元数据

如上图所示,Spring IoC 容器使用一种 configuration 元数据形式;此 configuration 元数据表示您作为 application 开发人员如何告诉 Spring 容器在 application 中实例化,配置和组装 objects。

Configuration 元数据传统上以简单直观的 XML 格式提供,本章大部分内容用于传达 Spring IoC 容器的 key 概念和 features。

XML-based 元数据不是唯一允许的 configuration 元数据形式。 Spring IoC 容器本身与实际编写此 configuration 元数据的格式完全分离。这些天,许多开发人员选择Java-based configuration作为他们的 Spring applications。

有关在 Spring 容器中使用其他形式的元数据的信息,请参阅:

  • Annotation-based configuration:Spring 2.5 引入了对 annotation-based configuration 元数据的支持。

  • Java-based configuration:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多 features 成为核心 Spring Framework 的一部分。因此,您可以使用 Java 而不是 XML files 定义 application classes 外部的 beans。要使用这些新的 features,请参阅@Configuration@Bean@Import@DependsOn 注释。

Spring configuration 包含容器必须管理的至少一个且通常不止一个 bean 定义。 XML-based configuration 元数据显示这些 beans 在 top-level <beans/>元素内配置为<bean/>元素。 Java configuration 通常在@Configuration class 中使用@Bean带注释的方法。

这些 bean 定义对应于构成 application 的实际 objects。通常,您可以定义服务层对象,数据访问对象(DAO),表示对象(如 Struts Action实例),基础结构对象(如 Hibernate SessionFactories,JMS Queues等)。通常,不会在容器中配置 fine-grained domain objects,因为创建和加载域 objects 通常是 DAO 和业务逻辑的责任。但是,您可以使用 Spring 与 AspectJ 的 integration 来配置在 IoC 容器控制之外创建的 objects。见使用 AspectJ 将 dependency-inject 域 objects 与 Spring 一起使用

以下 example 显示了 XML-based configuration 元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id属性是一个 string,用于标识单个 bean 定义。 class属性定义 bean 的类型并使用完全限定的类名。 id 属性的 value 指的是协作 objects。用于引用协作 objects 的 XML 未在此 example 中显示;有关详细信息,请参阅依赖

7.2.2 实例化容器

实例化 Spring IoC 容器非常简单。提供给ApplicationContext构造函数的位置路径或_path 实际上是资源 strings,它允许容器从各种外部资源(如本地文件系统,Java CLASSPATH等)加载 configuration 元数据。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

在了解 Spring 的 IoC 容器之后,您可能想要了解 Spring 的Resource抽象的更多信息,如第 8 章,资源中所述,它提供了一种从 URI 语法中定义的位置读取 InputStream 的便捷机制。特别是,Resource _path 用于构造 applications 上下文,如部分 8.7,“应用程序上下文和资源路径”中所述。

以下 example 显示了服务层 objects (services.xml) configuration 文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

以下 example 显示了数据访问 objects daos.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

在前面的示例中,服务层由 class PetStoreServiceImpl和两个数据访问 object 组成,类型为JpaAccountDaoJpaItemDao(基于 JPA Object/Relational 映射标准)。 property name元素引用 JavaBean property 的 name,ref元素引用另一个 bean 定义的 name。 idref元素之间的这种联系表达了协作 objects 之间的依赖关系。有关配置 object 依赖项的详细信息,请参阅依赖

编写 XML-based configuration 元数据

使 bean 定义 span 多个 XML files 非常有用。通常,每个单独的 XML configuration 文件都代表 architecture 中的逻辑层或模块。

您可以使用 application context 构造函数从所有这些 XML 片段加载 bean 定义。此构造函数需要多个Resource位置,如上一节中所示。或者,使用一个或多个<import/>元素来从另一个文件或 files 加载 bean 定义。例如:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

在前面的 example 中,外部 bean 定义从三个 files 加载:services.xmlmessageSource.xmlthemeSource.xml。所有位置_path 都与执行 importing 的定义文件相关,因此services.xml必须与执行 importing 的文件位于同一目录或 classpath 位置,而messageSource.xmlthemeSource.xml必须位于 importing 文件位置下方的resources位置。如您所见,忽略前导斜杠,但鉴于这些_path 是相对的,最好不要使用斜杠。根据 Spring Schema,导入的 files 的内容(包括 top level <beans/>元素)必须是有效的 XML bean 定义。

使用相对“../”路径在 parent 目录中 reference files 是可能的,但不建议这样做。这样做会对当前 application 之外的文件创建依赖关系。特别是,不建议将此 reference 用于“classpath:”URL(对于 example,“classpath:../services.xml”),其中运行时解析 process 选择“最近的”classpath 根,然后查看其 parent 目录。 Classpath configuration 更改可能导致选择不同的,不正确的目录。

您始终可以使用完全限定的资源位置而不是相对的 paths:for example,“file:C:/config/services.xml”或“classpath:/config/services.xml”。但是,请注意您将 application 的 configuration 与特定的绝对位置耦合。通常最好为这样的绝对位置保持间接,例如,通过在运行时针对 JVM 系统 properties 解析的“$ {}”占位符。

import 指令是 beans 命名空间本身提供的 feature。 Spring,e.g 提供的一系列 XML 命名空间中提供了进一步 configuration features 以外的普通 bean 定义。 “context”和“util”命名空间。

Groovy Bean 定义 DSL

作为外部化配置元数据的进一步示例,bean 定义也可以在 Spring 的 Groovy Bean Definition DSL 中表示,如 Grails framework 中所知。通常,此类 configuration 将存在于“.groovy”文件中,其结构如下所示:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

这种 configuration 样式在很大程度上等同于 XML bean 定义,甚至支持 Spring 的 XML configuration 命名空间。它还允许通过“importBeans”指令 importing XML bean definition files。

7.2.3 使用容器

ApplicationContext是高级工厂的接口,能够维护不同 beans 及其依赖项的注册表。使用方法T getBean(String name, Class<T> requiredType),您可以检索 beans 的实例。

ApplicationContext使您能够读取 bean 定义并按如下方式访问它们:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

使用 Groovy configuration,bootstrapping 看起来非常相似,只是一个不同的 context implementation class,它是 Groovy-aware(但也理解 XML bean 定义):

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的变体是GenericApplicationContext与 reader 委托,e.g 的组合。使用XmlBeanDefinitionReader for XML files:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

或者使用GroovyBeanDefinitionReader for Groovy files:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

如果需要,这些 reader 委托可以在相同的ApplicationContext上混合和匹配,从不同的 configuration 源读取 bean 定义。

然后,您可以使用getBean来检索 beans 的实例。 ApplicationContext接口有一些其他方法可以检索 beans,但理想情况下你的 application code 应该永远不会使用它们。实际上,你的 application code 根本不应该对getBean()方法有 calls,因此根本不依赖于 Spring API。例如,Spring 与 web 框架的 integration 为各种 web framework 组件(如控制器和 JSF-managed beans)提供依赖注入,允许您通过元数据声明对特定 bean 的依赖(e.g. 自动装配 annotation)。

7.3 Bean 概述

Spring IoC 容器管理一个或多个 beans。这些 beans 是使用您提供给容器的 configuration 元数据创建的,例如,以 XML <bean/>定义的形式。

在容器本身中,这些 bean 定义表示为BeanDefinition objects,其中包含(以及其他信息)以下元数据:

  • package-qualified class name:通常是 bean 的实际 implementation class 被定义。

  • Bean 行为 configuration 元素,它们 state bean 应该如何在容器中运行(范围,生命周期回调等)。

  • 引用 bean 执行其工作所需的其他 beans;这些 references 也称为协作者或依赖关系。

  • 要在新创建的 object 中设置的其他 configuration 设置,用于 example,管理连接池的 bean 中使用的连接数,或池的大小限制。

此元数据转换为构成每个 bean 定义的一组 properties。

表格 1_.bean 定义

属性解释在......
第 7.3.2 节,“实例化 beans”
名称第 7.3.1 节,“命名 beans”
范围第 7.5 节,“ Bean 范围”
构造函数 arguments第 7.4.1 节,“依赖注入”
properties第 7.4.1 节,“依赖注入”
自动装配模式第 7.4.5 节,“自动装配协作者”
lazy-initialization 模式第 7.4.4 节,“Lazy-initialized beans”
初始化方法称为“初始化回调”的部分
破坏方法“毁灭回调”一节

除了包含有关如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext __mplement 还允许用户注册在容器外部创建的现有 objects。这是通过方法getBeanFactory()访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 BeanFactory implementation DefaultListableBeanFactoryDefaultListableBeanFactory通过方法registerSingleton(..)registerBeanDefinition(..)支持此注册。但是,典型的 applications 只适用于通过元数据 bean 定义定义的 beans。

Bean 元数据和手动提供的 singleton 实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有 singleton 实例,但是在运行时注册新的 beans(与对工厂的实时访问同时)并未得到官方支持,并且可能导致在 bean 容器中并发访问 exceptions and/or 不一致 state。

7.3.1 命名 beans

每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。 bean 通常只有一个标识符,但如果它需要多个标识符,则额外的标识符可以被视为别名。

在 XML-based configuration 元数据中,使用id and/or name属性指定 bean identifier(s)。 id属性允许您指定一个 id。通常这些名称是字母数字('myBean','fooService',etc.),但也可能包含特殊字符。如果要将其他别名引入 bean,您还可以在name属性中指定它们,用逗号分隔(,),分号(;)或空格。作为历史记录,在 Spring 3.1 之前的版本中,id属性被定义为xsd:ID类型,它约束了可能的字符。从 3.1 开始,它被定义为xsd:string类型请注意,bean id uniqueness 仍由容器强制执行,但不再由 XML 解析器强制执行。

您不需要为 bean 提供 name 或 id。如果未明确提供 name 或 id,则容器会为该 bean 生成唯一的 name。但是,如果要通过 name 引用 bean,通过使用ref元素或服务定位器样式查找,则必须提供 name。不提供 name 的动机与使用内豆自动装配合作者有关。


Bean 命名约定

约定是在命名 beans 时使用标准 Java 约定作为实例字段名称。也就是说, bean 名称以小写字母开头,从那时开始是 camel-cased。此类名称的示例将是(不带引号)'accountManager''accountService''userDao''loginController'等。

命名 beans 一致地使您的 configuration 更容易阅读和理解,如果您使用 Spring AOP,在将建议应用于 name 相关的一组 beans 时会有很大帮助。


使用 classpath 中的 component 扫描,Spring 会根据上述规则为未命名的组件生成 bean 名称:基本上,使用简单的 class name 并将其初始字符转换为 lower-case。但是,在(不常见的)特殊情况下,当有多个字符且第一个和第二个字符都是大写字母时,原始外壳将被保留。这些是java.beans.Introspector.decapitalize(Spring 在这里使用)定义的相同规则。

在 bean 定义之外别名 bean

在 bean 定义本身中,通过使用id属性指定的最多一个 name 和name属性中的任意数量的其他名称,可以为 bean 提供多个 name。这些名称可以是同一 bean 的等效别名,并且在某些情况下很有用,例如允许 application 中的每个 component 通过使用特定于该 component 本身的 bean name 来引用 common 依赖项。

但是,指定实际定义 bean 的所有别名并不总是足够的。有时需要为其他地方定义的 bean 引入别名。在大型系统中通常就是这种情况,其中 configuration 在每个子系统之间分配,每个子系统都有自己的 object 定义集。在 XML-based configuration 元数据中,您可以使用<alias/>元素来完成此操作。

<alias name="fromName" alias="toName"/>

在这种情况下,在使用此别名定义之后,名为fromName的 bean(在同一容器中)也可以称为toName

例如,子系统 A 的 configuration 元数据可以通过subsystemA-dataSource的 name 引用 DataSource。子系统 B 的 configuration 元数据可以通过subsystemB-dataSource的 name 引用 DataSource。在编写使用这两个子系统的主 application 时,main application 通过myApp-dataSource的 name 引用 DataSource。要使所有三个名称引用相同的 object,可以将以下别名定义添加到 configuration 元数据中:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在每个 component 和 main application 都可以通过 name 来引用 dataSource,该 name 是唯一的,并且保证不与任何其他定义冲突(有效地创建命名空间),但它们引用相同的 bean。


Java-configuration

如果您使用 Java-configuration,@Bean annotation 可用于提供别名,请参阅第 7.12.3 节,“使用 @Bean 注释”以获取详细信息。


7.3.2 实例化 beans

bean 定义本质上是 creating 一个或多个 objects 的配方。容器在询问时查看命名 bean 的配方,并使用由 bean 定义封装的 configuration 元数据来创建(或获取)实际的 object。

如果使用 XML-based configuration 元数据,则指定要在<bean/>元素的class属性中实例化的 object 的类型(或 class)。这个class属性在BeanDefinition实例上内部是Class property,通常是必需的。 (对于 exceptions,请参阅“使用实例工厂方法实例化”一节第 7.7 节,“ Bean 定义继承” .)您可以通过以下两种方式之一使用Class property:

  • 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下指定要构造的 bean class,使用new operator 稍微等同于 Java code。

  • 要指定包含将被调用以创建 object 的static工厂方法的实际 class,在容器调用 class 上的static工厂方法以创建 bean 的较少 common 情况下。从static工厂方法的调用返回的 object 类型可能完全是相同的 class 或另一个 class。


**内部 class 名称.**如果要为static嵌套 class 配置 bean 定义,则必须使用嵌套 class 的二进制 name。

例如,如果在com.example包中有一个名为Foo的 class,并且Foo class 有一个static嵌套 class,名为Bar,那么 bean

com.example.Foo$Bar

注意在 name 中使用$字符将嵌套的 class name 与外部 class name 分开。


使用构造函数实例化

当您通过构造方法创建 bean 时,所有正常的 classes 都可以使用 Spring 并与之兼容。也就是说,正在开发的 class 不需要实现任何特定接口或以特定方式编码。只需指定 bean class 就足够了。但是,根据您为特定 bean 使用的 IoC 类型,您可能需要一个默认(空)构造函数。

Spring IoC 容器几乎可以管理您希望它管理的任何 class;它不仅限于管理 true JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans,只有一个默认的(no-argument)构造函数,并且在容器中的 properties 之后建模了适当的 setter 和 getter。您还可以在容器中拥有更多异域 non-bean-style classes。例如,对于 example,您需要使用绝对不符合 JavaBean 规范的 legacy 连接池,Spring 也可以对其进行管理。

使用 XML-based configuration 元数据,您可以指定 bean class,如下所示:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

有关在构造 object 之后向构造函数提供 arguments(如果需要)和设置 object instance properties 的机制的详细信息,请参阅注入依赖关系

使用静态工厂方法实例化

定义使用静态工厂方法创建的 bean 时,可以使用class属性指定包含static工厂方法的 class 和名为factory-method的属性,以指定工厂方法本身的 name。您应该能够调用此方法(使用后面描述的可选 arguments)并 return 一个实时 object,随后将其视为通过构造函数创建的。这种 bean 定义的一个用途是在 legacy code 中调用static工厂。

以下 bean 定义指定通过调用 factory-method 来创建 bean。该定义未指定返回的 object 的类型(class),仅指定包含工厂方法的 class。在此 example 中,createInstance()方法必须是静态方法。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

有关在工厂返回 object 之后向工厂方法提供(可选)arguments 并设置 object instance properties 的机制的详细信息,请参阅依赖关系和 configuration 详细

使用实例工厂方法实例化

与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化会从容器中调用现有 bean 的 non-static 方法来创建新的 bean。要使用此机制,请将class属性保留为空,并在factory-bean属性中,在当前(或 parent/ancestor)容器中指定 bean 的 name,该容器包含要调用以创建 object 的实例方法。使用factory-method属性设置工厂方法本身的 name。

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂 class 也可以包含多个工厂方法,如下所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

这种方法表明工厂 bean 本身可以通过依赖注入(DI)进行管理和配置。见依赖关系和 configuration 详细

在 Spring 文档中,factory bean 指的是 Spring 容器中配置的 bean,它将通过静态工厂方法创建 objects。相比之下,FactoryBean(注意大写)指的是 Spring-specific FactoryBean

7.4 依赖关系

典型的企业应用程序不包含单个 object(或 Spring 用语中的 bean)。即使是最简单的 application 也有一些 objects 可以协同工作来呈现 end-user 所看到的连贯应用程序。下一节将介绍如何定义一些独立的 bean 定义到完全实现的 application,其中 objects 协作实现目标。

7.4.1 依赖注入

依赖注入(DI)是一个 process,其中 objects 定义它们的依赖关系,即它们使用的其他 objects,仅通过构造函数 arguments,工厂方法的 arguments,或 object 实例在构造或返回后设置的 properties 从工厂方法。然后容器在创建 bean 时注入这些依赖项。这个 process 基本上是反向的,因此 name 反转控制(IoC), bean 本身通过使用 classes 的直接构造或服务定位器 pattern 来控制其依赖项的实例化或位置。

使用 DI 原理,Code 更干净,当 objects 具有依赖关系时,解耦更有效。 object 不查找其依赖项,也不知道依赖项的位置或 class。因此,您的 classes 变得更容易测试,特别是当依赖关系在接口或 abstract base classes 上时,它们允许在单元测试中使用 stub 或 mock implementation。

DI 存在两个主要变体,Constructor-based 依赖注入Setter-based 依赖注入

Constructor-based 依赖注入

Constructor-based DI 由容器调用具有多个 arguments 的构造函数完成,每个 arguments 表示一个依赖项。调用具有特定 arguments 的static工厂方法来构造 bean 几乎是等效的,本讨论同样将 arguments 视为构造函数和static工厂方法。以下 example 显示了一个只能使用构造函数注入的 class。请注意,这个 class 没有什么特别之处,它是一个 POJO,它不依赖于容器特定的接口,base classes 或 annotations。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
构造函数参数解析

使用参数的类型进行构造函数参数解析匹配。如果 bean 定义的构造函数 arguments 中不存在潜在的歧义,则在 bean 定义中定义构造函数 arguments 的 order 是 order,其中在实例化 bean 时将这些 arguments 提供给适当的构造函数。考虑以下 class:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

假设BarBaz classes 与继承无关,则不存在潜在的歧义。因此,以下 configuration 工作正常,并且您不需要在<constructor-arg/>元素中显式指定构造函数参数索引 and/or 类型。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

当引用另一个 bean 时,类型是已知的,并且可以进行匹配(与前面的 example 的情况一样)。当使用简单类型(例如<value>true</value>)时,Spring 无法确定 value 的类型,因此无法在没有帮助的情况下按类型匹配。考虑以下 class:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

在前面的场景中,如果使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型。例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

使用index属性明确指定构造函数 arguments 的索引。例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有两个相同类型的 arguments 的歧义。请注意,索引是基于 0 的。

您还可以使用构造函数参数 name 进行 value 消歧:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,要使这项工作开箱即用,必须在启用 debug flag 的情况下编译 code,以便 Spring 可以从构造函数中查找参数 name。如果你无法用 debug flag(或者不想)编译你的 code,你可以使用@ConstructorProperties JDK annotation 显式 name 你的构造函数 arguments。然后 sample class 必须如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

Setter-based 依赖注入

Setter-based DI 是在调用 no-argument 构造函数或 no-argument static工厂方法实例化 bean 之后,在 beans 上调用 setter 方法的容器来完成的。

以下 example 显示了一个只能使用纯 setter 注入的 class。这个 class 是传统的 Java。它是一个 POJO,它不依赖于容器特定的接口,base classes 或 annotations。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持它管理的 beans 的 constructor-based 和 setter-based DI。在通过构造函数方法注入了一些依赖项之后,它还支持 setter-based DI。您可以以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例结合使用,以将 properties 从一种格式转换为另一种格式。然而,大多数 Spring 用户不直接与这些 classes 工作(i.e,编程),而是用 XML bean定义,注释部件(i.e,classes 与@Component@Controller,etc.),或@Bean方法 Java-based @Configuration classes。这些来源注解然后在内部转换为BeanDefinition的实例,并用于加载整个 Spring IoC 容器实例。


Constructor-based 或 setter-based DI?

由于您可以混合使用 constructor-based 和 setter-based DI,因此将构造函数用于强制依赖项和 setter 方法或使用 configuration 方法作为可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用@Required annotation 可用于使 property 成为必需的依赖项。

Spring 团队通常提倡构造函数注入,因为它允许将 application 组件实现为不可变 objects 并确保所需的依赖项不是null。此外,constructor-injected 组件总是在完全初始化的 state 中返回到 client(调用)code。作为旁注,大量的构造函数 arguments 是一个糟糕的 code 气味,暗示 class 可能有太多的责任,应该重构以更好地解决关注点的正确分离。

Setter 注入应主要仅用于可在 class 中指定合理默认值的可选依赖项。否则,必须在 code 使用依赖项的任何位置执行 not-null 检查。 setter 注入的一个好处是 setter 方法使 class 的 objects 适合重新配置或 re-injection 以后。 因此,通过JMX MBeans管理是一个引人注目的用于二传手注射的用例。

使用对特定 class 最有意义的 DI 样式。有时,在处理没有源的 third-party classes 时,会选择您。例如,如果 third-party class 没有公开任何 setter 方法,那么构造函数注入可能是唯一可用的 DI 形式。


依赖关系解析 process

容器执行 bean 依赖项解析,如下所示:

  • 使用描述所有 beans 的 configuration 元数据创建和初始化ApplicationContext。 Configuration 元数据可以通过 XML,Java code 或 annotations 指定。

  • 对于每个 bean,如果使用的是_pro_,而不是普通的构造函数,则它的依赖关系将以 properties,constructor arguments 或 arguments 的形式表示。当实际创建 bean 时,这些依赖项将提供给 bean。

  • 每个 property 或构造函数参数都是要设置的 value 的实际定义,或者是容器中另一个 bean 的 reference。

  • 作为 value 的每个 property 或构造函数参数都从其指定的格式转换为该 property 或 constructor 参数的实际类型。默认情况下 Spring 可以将 string 格式提供的 value 转换为所有 built-in 类型,例如intlongStringboolean等。

Spring 容器在创建容器时验证每个 bean 的 configuration。但是,在实际创建 bean 之前,不会设置 bean properties 本身。创建容器时会创建 singleton-scoped 并设置为 pre-instantiated(默认值)的 Beans。范围在第 7.5 节,“ Bean 范围”中定义。否则,只有在请求时才会创建 bean。创建 bean 可能会导致创建 beans 图,因为 bean 的依赖项及其依赖项的依赖项(依此类推)会被创建和分配。请注意,这些依赖项之间的分辨率不匹配可能会显示较晚,i.e。首次创建受影响的 bean。


循环依赖

如果您主要使用构造函数注入,则可以创建无法解析的循环依赖关系场景。

对于 example:Class A 需要通过构造函数注入实现 class B,而 class B 需要通过构造函数注入实现 class A.如果为 classes A 和 B 配置 beans 以相互注入,则 Spring IoC 容器会在运行时检测到此循环 reference,并抛出BeanCurrentlyInCreationException

一种可能的解决方案是编辑某些 classes 的 source code,以便由 setter 而不是构造函数配置。或者,避免构造函数注入并仅使用 setter 注入。换句话说,尽管不推荐使用,但您可以使用 setter 注入配置循环依赖项。

与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖强制其中一个 beans 在完全初始化之前被注入另一个(经典的 chicken/egg 场景)。


您通常可以信任 Spring 做正确的事情。它在容器 load-time 处检测 configuration 问题,例如 reference to non-existent beans 和循环依赖项。 Spring sets properties 并在实际创建 bean 时尽可能晚地解析依赖项。这意味着,如果在创建 object 或其中一个依赖项时出现问题,则在请求 object 时,正确加载的 Spring 容器可以稍后生成 exception。对于 example,bean 因缺少或无效的 property 而抛出 exception。这可能会延迟一些 configuration 问题的可见性,这就是为什么ApplicationContext implementations 默认为 pre-instantiate singleton beans。以某些前期 time 和 memory 为代价,在实际需要之前创建这些 beans,您会在创建ApplicationContext时发现 configuration 问题,而不是以后。您仍然可以覆盖此默认行为,以便 singleton beans 将 lazy-initialize,而不是 pre-instantiated。

如果不存在循环依赖关系,则当一个或多个协作 beans 被注入依赖 bean 时,每个协作 bean 在被注入依赖 bean 之前完全配置。这意味着如果 bean A 依赖于 bean B,Spring IoC 容器在 bean A 上调用 setter 方法之前完全配置 bean B.换句话说,bean 被实例化(如果不是 pre-instantiated singleton),它的依赖关系是 set,并调用相关的生命周期方法(例如配置的 init 方法InitializingBean 回调方法)。

依赖注入的例子

以下 example 使用 setter-based 配置元数据用于 setter-based DI。 Spring XML configuration 文件的一小部分指定了一些 bean 定义:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

在前面的 example 中,setter 被声明为与 XML 文件中指定的 properties 进行 match。以下示例使用 constructor-based DI:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

bean 定义中指定的构造函数 arguments 将用作ExampleBean的构造函数的 arguments。

现在考虑这个 example 的变体,其中不是使用构造函数,而是告诉 Spring 调用static工厂方法来 return object 的一个实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

工厂方法的参数是通过<constructor-arg/>元素提供的,与实际使用的构造函数完全相同。工厂方法返回的 class 类型不必与包含static工厂方法的 class 类型相同,尽管在此 example 中它是。实例(non-static)工厂方法将以基本相同的方式使用(除了使用factory-bean属性而不是class属性),因此这里不再讨论细节。

7.4.2 详细信息的依赖关系和 configuration

如上一节所述,您可以将 bean properties 和 constructor arguments 定义为对其他托管 beans(协作者)的 references,或者作为内联定义的值。为此,Spring 的 XML-based configuration 元数据在其<property/><constructor-arg/>元素中支持 sub-element 类型。

直值(primitives,Strings 等)

<property/>元素的value属性将 property 或构造函数参数指定为 human-readable string 表示。 Spring 的转换服务用于将这些值从String转换为 property 或参数的实际类型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

以下 example 使用p-namespace进行更简洁的 XML configuration。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

前面的 XML 更简洁;但是,在运行时发现拼写错误而不是设计 time,除非您在创建 bean 定义时使用支持自动 property 完成的IntelliJ IDEASpring 工具套件(STS)等 IDE。强烈建议使用此类 IDE 帮助。

您还可以将java.util.Properties实例配置为:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring 容器通过使用 JavaBeans PropertyEditor机制将<value/>元素内的文本转换为java.util.Properties实例。这是一个很好的快捷方式,是 Spring 团队支持在value属性样式上使用嵌套<value/>元素的少数几个地方之一。

idref 元素

idref元素只是一种 error-proof 方式,用于将容器中另一个 bean 的 id(string value - not a reference)传递给<constructor-arg/><property/>元素。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面的 bean 定义片段与以下片段完全等效(在运行时):

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一种形式比第二种形式更可取,因为使用idref标签允许容器在部署 time 时验证引用的名为 bean 的实际存在。在第二个变体中,不会对传递给client bean 的targetName property 的 value 执行验证。当client bean 实际被实例化时,才会发现错别字(最有可能致命的结果)。如果client bean 是原型 bean,则只能在部署容器后 long 发现此错误和结果 exception。

4.0 beans xsd 不再支持idref元素上的local属性,因为它不再为常规bean reference 提供 value。升级到 4.0 schema 时,只需将现有的idref local references 更改为idref bean

<idref/>元素带来 value 的 common 位置(至少在 Spring 2.0 之前的版本中)位于ProxyFactoryBean bean 定义的AOP 拦截器的 configuration 中。指定拦截器名称时使用<idref/>元素可以防止拼写错误的拦截器 ID。

对其他 beans(合作者)的引用

ref元素是<constructor-arg/><property/>定义元素中的最后一个元素。在这里,您将 bean 的指定 property 的 value 设置为由容器管理的另一个 bean(协作者)的 reference。引用的 bean 是 bean 的依赖关系,其 property 将被设置,并且在设置 property 之前根据需要初始化它。 (如果协作者是 singleton bean,它可能已经被 container.)初始化所有 references 最终都是 reference 给另一个 object。作用域和验证取决于你是否通过beanlocal,parent属性指定另一个 object 的 id/name。

通过<ref/>标记的bean属性指定目标 bean 是最常用的形式,并允许在同一容器或 parent 容器中创建 reference 到任何 bean,无论它是否在同一 XML 文件中。 bean属性的 value 可以与 target bean 的id属性相同,也可以作为 target bean 的name属性中的一个值。

<ref bean="someBean"/>

通过parent属性指定目标 bean 会创建一个 reference 给位于当前容器的 parent 容器中的 bean。 parent属性的 value 可以与 target bean 的id属性相同,也可以与目标 bean 的name属性中的一个值相同,而 target bean 必须位于当前 parent 容器的 parent 容器中。您主要在拥有容器层次结构时使用此 bean reference 变体,并且希望将现有 bean 包装在 parent 容器中,并使用与 parent bean 具有相同 name 的代理。

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

4.0 beans xsd 不再支持ref元素上的local属性,因为它不再为常规bean reference 提供 value。升级到 4.0 schema 时,只需将现有的ref local references 更改为ref bean

内部 beans

<property/><constructor-arg/>元素中的<bean/>元素定义 so-called inner bean。

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部 bean 定义不需要定义的 id 或 name;如果指定,则容器不使用这样的 value 作为标识符。容器也会在创建时忽略scope flag:内部 beans 始终是匿名的,它们始终使用外部 bean 创建。不可能将内部 beans 注入协作 beans 而不是封闭 bean 或独立访问它们。

作为一个极端情况,可以从自定义作用域 e.g 接收销毁回调。对于 singleton bean 中包含的 request-scoped inner bean:内部 bean 实例的创建将绑定到包含 bean 的内容,但是销毁回调允许它参与请求范围的生命周期。这不是一个常见的场景; inner beans 通常只是共享它们包含 bean 的范围。

收藏

<list/><set/><map/><props/>元素中,分别设置 Java Collection,,SetMapProperties的 properties 和 arguments。

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[emailprotected]</prop>
            <prop key="support">[emailprotected]</prop>
            <prop key="development">[emailprotected]</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

map key 或 value 的 value 或 set value 也可以是以下任何元素:

bean | ref | idref | list | set | map | props | value | null
集合合并

Spring 容器还支持集合的合并。 application 开发人员可以定义 parent-style <list/><map/><set/><props/>元素,并使 child-style <list/><map/><set/><props/>元素继承并覆盖 parent 集合中的值。也就是说,child 集合的值是合并 parent 和 child 集合的元素的结果,其中 child 的集合元素覆盖 parent 集合中指定的值。

关于合并的这一部分讨论了 parent-child bean 机制。 不熟悉 parent 和 child bean 定义的读者可能希望在继续之前阅读相关部分

以下 example 演示了集合合并:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[emailprotected]</prop>
                <prop key="support">[emailprotected]</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">[emailprotected]</prop>
                <prop key="support">[emailprotected]</prop>
            </props>
        </property>
    </bean>
<beans>

注意在child bean 定义的adminEmails property 的<props/>元素上使用merge=true属性。当child bean 由容器解析并实例化时,生成的实例有一个adminEmails Properties集合,其中包含 child 的adminEmails集合与 parent 的adminEmails集合的合并结果。

[emailprotected]
[emailprotected]
[emailprotected]

child Properties集合的 value 集合从 parent <props/>继承所有 property 元素,child _ value 的 child 的 value 覆盖 parent 集合中的 value。

此合并行为同样适用于<list/><map/><set/>集合类型。在<list/>元素的特定情况下,保持与List集合类型相关联的语义,即ordered值集合的概念; parent 的值在所有 child 列表的值之前。对于MapSetProperties集合类型,不存在 ordering。因此,对于作为容器内部使用的关联MapSetProperties implementation 类型的基础的集合类型,没有 ordering 语义生效。

集合合并的限制

您无法合并不同的集合类型(例如MapList),如果您尝试这样做,则会抛出相应的Exception。必须在较低的,继承的 child 定义上指定merge属性;在 parent 集合定义上指定merge属性是多余的,不会导致所需的合并。

Strongly-typed 集合

通过在 Java 5 中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含String个元素(对于 example)。如果您使用 Spring dependency-inject @ @

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

foo bean 的accounts property 准备好进行注入时,可以通过反射获得有关 strongly-typed Map<String, Float>的元素类型的泛型信息。因此,Spring 的类型转换基础结构将各种 value 元素识别为Float类型,并将 string 值9.99, 2.753.99转换为实际的Float类型。

Null 和空 string 值

Spring 将 properties 等的空 arguments 视为空Strings。以下 XML-based configuration 元数据片段将电子邮件 property 设置为空String value(“”)。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

前面的 example 等效于以下 Java code:

exampleBean.setEmail("");

<null/>元素处理null值。例如:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的 configuration 相当于以下 Java code:

exampleBean.setEmail(null);

带有 p-namespace 的 XML 快捷方式

p-namespace 使您能够使用bean元素的属性而不是嵌套的<property/>元素来描述 property 值 and/or collaborating beans。

Spring 支持可扩展的 configuration 格式使用命名空间,它们基于 XML Schema 定义。本章中讨论的beans configuration 格式在 XML Schema 文档中定义。但是,p-namespace 未在 XSD 文件中定义,仅存在于 Spring 的核心中。

以下 example 显示了两个解析为相同结果的 XML 片段:第一个使用标准 XML 格式,第二个使用 p-namespace。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="[emailprotected]"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[emailprotected]"/>
</beans>

example 在 bean 定义中显示 p-namespace 中称为电子邮件的属性。这告诉 Spring 包含 property 声明。如前所述,p-namespace 没有 schema 定义,因此您可以将属性的 name 设置为 property name。

下一个 example 包含两个 bean 定义,它们都有一个 reference 给另一个 bean:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

如您所见,此 example 不仅包含使用 p-namespace 的 property value,还使用特殊格式声明 property references。第一个 bean 定义使用<property name="spouse" ref="jane"/>从 bean john到 bean jane创建 reference,而第二个 bean 定义使用p:spouse-ref="jane"作为属性来完成同样的事情。在这种情况下,spouse是 property name,而-ref部分表示这不是一个直的 value,而是一个 reference 给另一个 bean。

p-namespace 不如标准 XML 格式灵活。例如,声明 property references 的格式与以Ref结尾的 properties 冲突,而标准 XML 格式则不然。我们建议您仔细选择您的方法并将其传达给您的团队成员,以避免生成在同一时间使用所有三种方法的 XML 文档。

带有 c-namespace 的 XML 快捷方式

名为“带有 p-namespace 的 XML 快捷方式”的部分类似,Spring 3.1 中新引入的 c-namespace 允许使用内联属性来配置构造函数 arguments 而不是嵌套的constructor-arg元素。

让我们回顾称为“Constructor-based 依赖注入”的部分中带有c:命名空间的示例:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="[emailprotected]"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[emailprotected]"/>

</beans>

c:命名空间使用与p:一样的约定(bean references 的尾随-ref),用于按名称设置构造函数 arguments。同样,它需要声明,即使它没有在 XSD schema 中定义(但它存在于 Spring 核心内)。

对于构造函数参数名称不可用的罕见情况(通常如果字节码是在没有调试信息的情况下编译的话),可以使用回退到参数索引:

<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

由于 XML 语法,索引表示法要求存在前导_,因为 XML 属性名称不能以数字开头(即使某些 IDE 允许它)。

实际上,构造函数解析机制在匹配 arguments 方面非常有效,所以除非确实需要,否则我们建议使用 name 表示法 through-out your configuration。

复合 property 名称

将 bean properties 设置为 long 时,可以使用复合或嵌套 property 名称,因为除最终 property name 之外的路径的所有组件都不是null。考虑以下 bean 定义。

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

foo bean 有一个fred property,它有一个bob property,它有sammy property,最后sammy property 被设置为 value 123。为了使其工作,的fred property 和fredbob property 在构造 bean 之后不能是null,否则抛出NullPointerException

7.4.3 使用 depends-on

如果 bean 是另一个的依赖关系,通常意味着一个 bean 被设置为另一个 bean 的 property。通常,您可以使用 XML-based XML-based configuration 元数据完成此操作。但是,有时 beans 之间的依赖关系不那么直接;对于 example,需要触发 class 中的静态初始化程序,例如数据库驱动程序注册。在使用此元素的 bean 初始化之前,depends-on属性可以显式强制初始化一个或多个 beans。以下 example 使用depends-on属性表示对单个 bean 的依赖:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

要表达对多个 beans 的依赖关系,请提供 bean 名称列表作为depends-on属性的 value,并使用逗号,空格和分号作为有效分隔符:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

bean 定义中的depends-on属性既可以指定 initialization-time 依赖项,也可以指定singleton beans,相应的 destruction-time 依赖项。在给定 bean 本身被销毁之前,首先销毁与给定 bean 定义depends-on关系的依赖 beans。因此depends-on也可以控制 shutdown order。

7.4.4 Lazy-initialized beans

默认情况下,ApplicationContext implementations 急切地创建和配置所有singleton beans 作为初始化 process 的一部分。通常,这个 pre-instantiation 是可取的,因为 configuration 或周围环境中的错误会立即被发现,而不是几小时甚至几天。如果不希望出现这种情况,可以通过将 bean 定义标记为 lazy-initialized 来阻止 singleton bean 的 pre-instantiation。 lazy-initialized bean 告诉 IoC 容器在第一次请求时创建 bean 实例,而不是在启动时创建 bean 实例。

在 XML 中,此行为由<bean/>元素上的lazy-init属性控制;例如:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

ApplicationContext使用前面的 configuration 时,ApplicationContext启动时

但是,当 lazy-initialized bean 是 singleton bean 的依赖关系而不是 lazy-initialized 时,ApplicationContext会在启动时创建 lazy-initialized bean,因为它必须满足 singleton 的依赖关系。 lazy-initialized bean 被注入 singleton bean,而不是 lazy-initialized。

您还可以使用<beans/>元素上的default-lazy-init属性在容器 level 上控制 lazy-initialization;例如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

7.4.5 自动装配合作者

Spring 容器可以自动配合协作 beans 之间的关系。您可以通过检查ApplicationContext的内容,允许 Spring 自动为您的 bean 解析协作者(其他 beans)。自动装配具有以下优点:

  • 自动装配可以显着减少指定 properties 或构造函数 arguments 的需要。 (其他机制,如 bean 模板在本章的其他地方讨论过在此 regard.)也很有价值

  • 随着 objects 的发展,自动装配可以更新 configuration。例如,如果需要向 class 添加依赖项,则可以自动满足该依赖项,而无需修改 configuration。因此,自动装配在开发期间尤其有用,而不会在 code base 变得更稳定时否定切换到显式布线的选项。

使用 XML-based configuration metadata [2]时,可以使用<bean/>元素的autowire属性为 bean 定义指定 autowire 模式。自动装配功能有四种模式。您为每个 bean 指定自动装配,因此可以选择要自动装配的那些。

表格 1_.自动装配模式

模式说明
没有(默认)无自动装配。 Bean references 必须通过ref元素定义。不建议对较大的部署更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
按名字property name 自动装配。 Spring 查找 bean,其 name 与需要自动装配的 property 相同。例如,如果 bean 定义由 name 设置为 autowire,并且它包含 master property(即,它具有 setMaster(..)方法),则 Spring 将查找名为master的 bean 定义,并使用它来设置 property。
byType如果容器中只存在 property 类型的一个 bean,则允许 property 自动装配。如果存在多个,则抛出致命的 exception,这表示您不能对该 bean 使用 byType 自动装配。如果没有匹配的 beans,则没有任何反应; property 未设置。
构造函数类似于 byType,但适用于构造函数 arguments。如果容器中没有构造函数参数类型的一个 bean,则会引发致命错误。

使用 byType 或构造函数自动装配模式,您可以连接数组和 typed-collections。在这种情况下,提供容器内的 match 预期类型的所有 autowire 候选者以满足依赖性。如果预期的 key 类型是String,则可以自动装配 strongly-typed Maps。自动装配的 Maps 值将包含 match 预期类型的所有 bean 实例,Maps 键将包含相应的 bean 名称。

您可以将 autowire 行为与依赖性检查相结合,这将在自动装配完成后执行。

自动装配的限制和缺点

当在整个项目中一致地使用自动装配时,自动装配效果最佳。如果一般不使用自动装配,那么开发人员使用它来连接一个或两个 bean 定义可能会让人感到困惑。

考虑自动装配的局限和缺点:

  • propertyconstructor-arg设置中的显式依赖项始终覆盖自动装配。你无法自动装载 so-called 简单的 properties,例如 primitives,StringsClasses(以及这些简单 properties 的数组)。这个限制是 by-design。

  • 自动装配不如显式布线精确。虽然如上面的 table 所述,Spring 谨慎避免在可能产生意外结果的歧义的情况下进行猜测,但object 之间的关系不再明确记录。

  • 可能无法为可能从 Spring 容器生成文档的工具提供接线信息。

  • 容器中的多个 bean 定义可以 match 由 setter 方法或构造函数参数指定的类型以进行自动装配。对于数组,集合或 Maps,这不一定是个问题。但是对于期望单个 value 的依赖关系,这种模糊性不是任意解决的。如果没有唯一的 bean 定义,则抛出 exception。

在后一种情况下,您有几种选择:

  • 放弃自动装配,支持显式布线。

  • 通过将属性设置为false,避免为 bean 定义自动装配,如下一节所述。

  • 通过将元素的primary属性设置为true,将单个 bean 定义指定为主要候选者。

  • 使用 annotation-based configuration 实现更多 fine-grained 控件,如第 7.9 节,“Annotation-based 容器配置”中所述。

从自动装配中排除 bean

在 per-bean 的基础上,您可以从自动装配中排除 bean。在 Spring 的 XML 格式中,将<bean/>元素的autowire-candidate属性设置为false;容器使特定的 bean 定义对自动装配基础结构不可用(包括 annotation 样式配置,如@Autowired)。

autowire-candidate属性旨在仅影响 type-based 自动装配。它不会影响 name 的显式 references,即使指定的 bean 未标记为 autowire 候选,它也会得到解析。因此,如果 name 匹配,name 的自动装配将会 inject 一个 bean。

您还可以根据 pattern-matching 对 bean 名称限制 autowire 候选者。 top-level <beans/>元素在其default-autowire-candidates属性中接受一个或多个模式。对于 example,要将 autowire 候选状态限制为 name ends with Repository 的任何 bean,请提供* Repository 的 value。要提供多个模式,请在 comma-separated 列表中定义它们。 bean属性的truefalse的显式 value 始终优先,对于此类 beans,pattern 匹配规则不适用。

这些技术对 beans 很有用,你永远不想通过自动装配注入其他 beans。这并不意味着排除的 bean 本身不能使用自动装配进行配置。相反,bean 本身不是自动装配其他 beans 的候选者。

7.4.6 方法注入

在大多数 application 场景中,容器中的大多数 beans 都是单身。当 singleton bean 需要与另一个 singleton bean 协作,或者 non-singleton bean 需要与另一个 non-singleton bean 协作时,通常通过将一个 bean 定义为另一个 property 来处理依赖关系。当 bean 生命周期不同时会出现问题。假设 singleton bean A 需要使用 non-singleton(prototype)bean B,可能是在 A 上的每个方法调用上。容器只创建 singleton bean A 一次,因此只有一次机会来设置 properties。容器不能为 bean A 提供 bean B 的新实例,每个 time B 都需要一个。

解决方案是放弃一些控制反转。您可以make bean 了解容器实现ApplicationContextAware接口,并通过对容器进行 getBean(“B”)调用请求(一个通常是新的)bean B 实例每 time bean A 需要它。以下是此方法的示例:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

前面的内容是不可取的,因为 business code 知道并耦合到 Spring Framework。方法注入是 Spring IoC 容器的一种先进的功能,允许以干净的方式处理这个用例。


您可以在这篇博客文章中阅读有关方法注入动机的更多信息。


查找方法注入

查找方法注入是容器覆盖容器托管 beans 上的方法的能力,以便_return 查找容器中另一个名为 bean 的查找结果。查找通常涉及原型 bean,如上一节中描述的场景。 Spring Framework 通过使用 CGLIB library 中的字节码生成来实现此方法注入,以动态生成覆盖该方法的子类。

  • 为了使这个动态子类化起作用,Spring bean 容器将子类化的 class 不能是final,并且要重写的方法也不能是final
  • Unit-testing 具有abstract方法的 class 要求您自己子类化 class 并提供abstract方法的 stub implementation。

  • component 扫描也需要具体的方法,这需要具体的 classes 来获取。

  • 另一个 key 限制是查找方法不能与工厂方法一起使用,特别是在 configuration classes 中不能使用@Bean方法,因为在这种情况下容器不负责 creating 实例,因此无法动态创建 runtime-generated 子类。

查看前一个 code 片段中的CommandManager class,您会看到 Spring 容器将动态覆盖createCommand()方法的 implementation。你的CommandManager class 将没有任何 Spring 依赖项,如在重写的 example 中可以看到的:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

在包含要注入的方法的 client class 中(在本例中为CommandManager),要注入的方法需要以下形式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是abstract,则 dynamically-generated 子类实现该方法。否则,dynamically-generated 子类将覆盖原始 class 中定义的具体方法。例如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

只要需要 myCommand bean 的新实例,bean 标识为 commandManager calls 自己的方法createCommand()。您必须小心部署myCommand bean 作为原型,如果这实际上是需要的话。如果它是singleton,则每 time 返回相同的myCommand bean 实例。

或者,在 annotation-based component model 中,您可以通过@Lookup annotation 声明一个查找方法:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或者,更具惯用性,您可以依赖于目标 bean 来解析查找方法的声明的 return 类型:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,您通常会在 order 中使用具体的 stub implementation 声明这样带注释的查找方法,以使它们与 Spring 的 component 扫描规则兼容,其中 abstract classes 默认被忽略。此限制不适用于显式注册或显式导入的 bean classes。

访问不同范围的目标 beans 的另一种方法是ObjectFactory/Provider注入点。退房名为“Scoped beans as dependencies”的部分

感兴趣的 reader 也可能会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)是有用的。

任意方法更换

与查找方法注入相比,一种不太有用的方法注入形式是能够使用另一种方法 implementation 替换托管 bean 中的任意方法。用户可以安全地跳过本节的 rest,直到实际需要该功能。

使用 XML-based configuration 元数据,您可以使用replaced-method元素将已存在的方法 implementation 替换为另一个,用于已部署的 bean。考虑以下 class,使用方法 computeValue,我们要覆盖它:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

实现org.springframework.beans.factory.support.MethodReplacer接口的 class 提供了新的方法定义。

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

部署原始 class 并指定方法覆盖的 bean 定义如下所示:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

您可以在<replaced-method/>元素中使用一个或多个包含的<arg-type/>元素来指示被覆盖的方法的方法签名。仅当方法重载且 class 中存在多个变体时,arguments 的签名才是必需的。为方便起见,参数的类型 string 可以是完全限定类型 name 的子字符串。对于 example,以下所有 match java.lang.String

java.lang.String
String
Str

因为 arguments 的数量通常足以区分每个可能的选择,所以通过允许您只键入将匹配参数类型的最短 string,此快捷方式可以节省大量 typing。

7.5 Bean 范围

创建 bean 定义时,将创建一个配方,用于创建由 bean 定义定义的 class 的实际实例。 bean 定义是配方的 idea 很重要,因为它意味着,与 class 一样,您可以从单个配方创建许多 object 实例。

您不仅可以控制要插入到从特定 bean 定义创建的 object 的各种依赖项和 configuration 值,还可以控制从特定 bean 定义创建的 objects 的范围。这种方法功能强大且灵活,您可以选择通过 configuration 创建的 objects 的范围,而不必在 Java class level 的 object 范围内进行烘焙。 Beans 可以定义为部署在多个范围之一中:开箱即用,Spring Framework 支持七个范围,其中五个范围仅在您使用 web-aware ApplicationContext时可用。

开箱即用支持以下范围。你也可以创建自定义范围。

表格 1_.Bean 范围

范围描述
singleton(默认)根据 Spring IoC 容器将单个 bean 定义范围限定为单个 object 实例。
原型为任意数量的 object 实例定义单个 bean 定义。
请求将单个 bean 定义范围限定为单个 HTTP 请求的生命周期;也就是说,每个 HTTP 请求都有自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在 web-aware Spring ApplicationContext的 context 中有效。
session将单个 bean 定义范围限定为 HTTP Session的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。
globalSession为 global HTTP Session的生命周期定义单个 bean 定义。通常仅在 Portlet context 中使用时有效。仅在 web-aware Spring ApplicationContext的 context 中有效。
应用将单个 bean 定义范围限定为ServletContext的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。
WebSocket将单个 bean 定义范围限定为WebSocket的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。

从 Spring 3.0 开始,线程范围可用,但默认情况下未注册。有关更多信息,请参阅SimpleThreadScope的文档。有关如何注册此范围或任何其他自定义范围的说明,请参阅“使用自定义范围”一节

7.5.1 singleton 范围

只管理 singleton bean 的一个共享实例,并且对具有与该 bean 定义匹配的 id 或 id 的 beans 的所有请求都会导致 Spring 容器返回一个特定的 bean 实例。

换句话说,当您定义 bean 定义并将其范围限定为 singleton 时,Spring IoC 容器只创建该 bean 定义定义的 object 的一个实例。此单个实例存储在此类 singleton beans 的缓存中,并且所有后续请求和 references 都指向 bean return 缓存的 object。

singleton

Spring 的 singleton bean 概念与 Singleton pattern 的概念不同,如四人帮(GoF)模式书中所定义的那样。 GoF Singleton hard-codes object 的范围,使得每个 ClassLoader 创建一个且只有一个特定 class 的实例。 Spring singleton 的范围最好按容器和每 bean 描述。这意味着如果在单个 Spring 容器中为特定 class 定义一个 bean,则 Spring 容器将创建该 bean 定义所定义的 class 的一个且仅一个实例。 singleton 范围是 Spring 中的默认范围。要将 bean 定义为 XML 中的 singleton,您可以为 example 编写:

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

7.5.2 原型范围

,bean 部署的原型范围导致每 time 时创建一个新的 bean 实例,并对该特定 bean 发出请求。也就是说,bean 被注入到另一个 bean 中,或者通过容器上的getBean()方法调用来请求它。通常,对所有有状态 beans 使用原型范围,对 stateless beans 使用 singleton 范围。

下图说明了 Spring 原型范围。数据访问 object(DAO)通常不配置为原型,因为典型的 DAO 不包含任何会话 state;这个作者更容易重用 singleton 图的核心。

原型

以下 example 将 bean 定义为 XML 中的原型:

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

与其他范围相比,Spring 不管理原型 bean 的完整生命周期:容器实例化,配置和组装原型 object,并将其交给 client,而没有该原型实例的进一步 record。因此,尽管无论范围如何都在所有 object 上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。 client code 必须清理 prototype-scoped object 并释放原型 bean(s 所持有的昂贵资源.要让 Spring 容器释放 prototype-scoped beans 所拥有的资源,请尝试使用自定义bean post-processor,它包含需要清理的 beans 的 reference。

在某些方面,Spring 容器关于 prototype-scoped bean 的角色是 Java new operator 的替代品。超过该点的所有生命周期管理必须由 client 处理。 (有关 Spring 容器中 bean 生命周期的详细信息,请参阅第 7.6.1 节,“生命周期回调” .)

7.5.3 Singleton beans with prototype-bean dependencies

当您对原型 beans 使用带有依赖关系的 singleton-scoped beans 时,请注意在实例化 time 时解析依赖关系。因此,如果你原型实例是提供给 singleton-scoped bean 的唯一实例。

但是,假设您希望 singleton-scoped bean 在运行时重复获取 prototype-scoped bean 的新实例。你不能 dependency-inject bean 进入你的 singleton bean,因为当 Spring 容器实例化 singleton bean 并解析和注入它的依赖项时,只发生一次注入。如果您需要在运行时多次使用原型 bean 的新实例,请参阅第 7.4.6 节,“方法注入”

7.5.4 Request,session,global session,application 和 WebSocket 范围

只有在使用 web-aware Spring ApplicationContext implementation(例如XmlWebApplicationContext)时,requestsessionglobalSessionapplicationwebsocket范围才可用。如果将这些范围与常规的 Spring IoC 容器(例如ClassPathXmlApplicationContext)一起使用,则会抛出IllegalStateException抱怨未知的 bean 范围。

初始 web configuration

要在requestsessionglobalSessionapplicationwebsocket级别(web-scoped beans)支持 beans 的范围设定,在定义 beans 之前需要一些小的初始 configuration。 (标准范围不需要此初始设置,singletonprototype .)

如何完成此初始设置取决于您的特定 Servlet 环境。

如果在 Spring Web MVC 中实际访问范围 beans,则在 Spring DispatcherServletDispatcherPortlet处理的请求中,则不需要进行特殊设置:DispatcherServletDispatcherPortlet已公开所有相关的 state。

如果使用 Servlet 2.5 web 容器,并且在 Spring 的DispatcherServlet之外处理请求(对于 example,当使用 JSF 或 Struts 时),则需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。对于 Servlet 3.0,可以通过WebApplicationInitializer接口以编程方式完成。或者,或者对于旧容器,将以下声明添加到 web application 的web.xml文件中:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

或者,如果 listener 设置存在问题,请考虑使用 Spring 的RequestContextFilter。过滤器映射取决于周围的 web application configuration,因此您必须根据需要进行更改。

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServletRequestContextListenerRequestContextFilter都完全相同,即将 HTTP 请求 object 绑定到为该请求提供服务的Thread。这使 beans 在请求和 session-scoped 可进一步在调用链中可用。

请求范围

考虑以下针对 bean 定义的 XML configuration:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

Spring 容器通过对每个 HTTP 请求使用loginAction bean 定义来创建LoginAction bean 的新实例。也就是说,loginAction bean 的作用域是 HTTP 请求 level。您可以根据需要更改创建的实例的内部 state,因为从同一loginAction bean 定义创建的其他实例将不会在 state 中看到这些更改。它们特别针对个人要求。当请求完成处理时,将放弃作用于请求的 bean。

使用 annotation-driven 组件或 Java Config 时,@RequestScope annotation 可用于将 component 分配给request范围。

@RequestScope
@Component
public class LoginAction {
    // ...
}

Session 范围

考虑以下针对 bean 定义的 XML configuration:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

Spring 容器通过在单个 HTTP Session的生命周期中使用userPreferences bean 定义来创建UserPreferences bean 的新实例。换句话说,userPreferences bean 有效地限定在 HTTP Session level。与request-scoped beans 一样,您可以根据需要更改创建的实例的内部 state,因为知道同样使用从同一userPreferences bean 定义创建的实例的其他 HTTP Session实例在 state 中看不到这些更改,因为它们特定于单个 HTTP Session。当最终丢弃 HTTP Session时,也将丢弃作用于该特定 HTTP Session的 bean。

使用 annotation-driven 组件或 Java Config 时,@SessionScope annotation 可用于将 component 分配给session范围。

@SessionScope
@Component
public class UserPreferences {
    // ...
}

Global session 范围

考虑以下 bean 定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

globalSession范围类似于标准 HTTP Session范围(如上所述),并且仅适用于 portlet-based web applications 的 context。 portlet 规范定义了 global Session的概念,该概念在构成单个 portlet web application 的所有 portlet 之间共享。在globalSession范围定义的 Beans 被限定(或绑定)到 global portlet Session的生命周期。

如果编写标准 Servlet-based web application 并将一个或多个 beans 定义为具有globalSession作用域,则使用标准 HTTP Session作用域,并且不会引发错误。

Application 范围

考虑以下针对 bean 定义的 XML configuration:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

Spring 容器通过对整个 web application 使用appPreferences bean 定义一次来创建AppPreferences bean 的新实例。也就是说,appPreferences bean 的作用域为ServletContext level,存储为常规ServletContext属性。这有点类似于 Spring singleton bean 但在两个重要方面有所不同:它是 singleton 每ServletContext,而不是 Spring'ApplicationContext'(在任何给定的 web application 中可能有几个),它实际上是暴露的,因此作为ServletContext属性可见。

使用 annotation-driven 组件或 Java Config 时,@ApplicationScope annotation 可用于将 component 分配给application范围。

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

Scoped beans 作为依赖项

Spring IoC 容器不仅管理 objects(beans)的实例化,还管理协作者(或依赖项)的连接。如果要 inject(for example)将 HTTP 请求范围 bean 作为 longer-lived 范围的另一个 bean,您可以选择 inject AOP 代理来代替作用域 bean。也就是说,您需要 inject 一个代理 object,它暴露与作用域 object 相同的公共接口,但也可以从相关范围(例如 HTTP 请求)中检索真实目标 object,并将方法 calls 委托给真实的 object。

您也可以在作为singleton的 beans 之间使用<aop:scoped-proxy/>,然后 reference 将通过可序列化的中间代理,因此能够在反序列化时 re-obtain 目标 singleton bean。

当对范围prototype的 bean 声明<aop:scoped-proxy/>时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后该调用将被转发到该目标实例。

此外,范围代理不是以 lifecycle-safe 方式从较短范围访问 beans 的唯一方法。您也可以简单地将注入点(i.e.constructor/setter 参数或自动装配字段)声明为ObjectFactory<MyTargetBean>,允许getObject()调用在每次需要时按需检索当前实例 - 无需保留实例或单独存储它。

作为扩展变体,您可以声明ObjectProvider<MyTargetBean>,它提供了几个额外的访问变体,包括getIfAvailablegetIfUnique

这个 JSR-330 变体称为Provider,与Provider<MyTargetBean>声明一起使用,并且每次检索尝试都使用相应的get()调用。有关 JSR-330 整体的更多详细信息,请参阅这里

以下 example 中的 configuration 只有一个 line,但了解“为什么”以及它背后的“如何”非常重要。

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

要创建这样的代理,请将 child <aop:scoped-proxy/>元素插入到作用域 bean 定义中(请参阅名为“选择要创建的代理类型”的部分第 41 章,XML Schema-based configuration)。为什么在requestsessionglobalSession和 custom-scope 级别定义 beans 的定义需要<aop:scoped-proxy/>元素?让我们检查以下 singleton bean 定义,并将其与您需要为上述范围定义的内容进行对比(请注意,以下userPreferences bean 定义不完整)。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在前面的 example 中,singleton bean userManager注入一个 reference 到 HTTP Session -scoped bean userPreferences。这里的重点是userManager bean 是 singleton:它将在每个容器中实例化一次,并且它的依赖项(在这种情况下只有一个,userPreferences bean)也只注入一次。这意味着userManager bean 将仅在完全相同的userPreferences object 上操作,即最初注入的那个。

这不是将 shorter-lived 作用域 bean 注入 longer-lived 作用域 bean 时所需的行为,因为 example 将 HTTP Session -scoped 协作 bean 作为依赖项注入 singleton bean。相反,您需要一个userManager object,并且在 HTTP Session的生命周期中,您需要一个特定于 HTTP SessionuserPreferences object。因此,容器创建一个 object,它公开与UserPreferences class 完全相同的公共接口(理想情况下是一个UserPreferences实例的 object),它可以从作用域机制中获取真正的UserPreferences object(HTTP 请求,Session,etc.)。容器注入此 proxy object 进入userManager bean,它不知道这个UserPreferences reference 是一个代理。在这个例子中,当UserManager实例调用 dependency-injected UserPreferences object 上的方法时,它实际上是在代理上调用一个方法。然后代理取出实际UserPreferences object 来自(在本例中)HTTP Session,并将方法调用委托给检索到的真实UserPreferences object。

因此,在将request-session-globalSession-scoped beans 注入协作 objects 时,您需要以下正确和完整的 configuration:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型

默认情况下,当 Spring 容器为使用<aop:scoped-proxy/>元素标记的 bean 创建代理时,会创建 CGLIB-based class 代理。

CGLIB 代理只拦截公共方法 calls!不要在这样的代理上调用 non-public 方法;它们不会被委托给实际的作用域 object。

或者,您可以通过为<aop:scoped-proxy/>元素的proxy-target-class属性的 value 指定false来配置 Spring 容器以为此类作用域 beans 创建标准 JDK interface-based 代理。使用 JDK interface-based 代理意味着您在 application classpath 中不需要额外的 libraries 来实现此类代理。但是,它还意味着作用域 bean 的 class 必须至少实现一个接口,并且注入了作用域 bean 的所有协作者必须通过其中一个接口引用 bean。

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

有关选择 class-based 或 interface-based 代理的更多详细信息,请参阅第 11.6 节,“代理机制”

7.5.5 自定义范围

bean 范围机制是可扩展的;您可以定义自己的范围,甚至可以重新定义现有范围,尽管后者被认为是不好的做法,您无法覆盖 built-in singletonprototype范围。

创建自定义范围

要将自定义 scope(s)集成到 Spring 容器中,需要实现org.springframework.beans.factory.config.Scope接口,本节将对此进行介绍。有关如何实现自己的作用域的 idea,请参阅 Spring Framework 本身和范围 javadocs提供的Scope implementations,它解释了您需要更详细地实现的方法。

Scope接口有四种方法可以从作用域中获取 objects,从作用域中删除它们,并允许它们被销毁。

以下方法从基础范围返回 object。 session 范围 implementation,对于 example,返回 session-scoped bean(如果它不存在,则该方法在将__ssion 绑定到 session 以用于将来 reference 之后返回 bean 的新实例)。

Object get(String name, ObjectFactory objectFactory)

以下方法从基础范围中删除 object。 example 的 session 范围 implementation 从基础 session 中删除 session-scoped bean。应返回 object,但如果找不到具有指定 name 的 object,则可以 return null。

Object remove(String name)

以下方法注册范围在销毁时或范围中指定的 object 被销毁时应执行的回调。有关销毁回调的更多信息,请参阅 javadocs 或 Spring 范围 implementation。

void registerDestructionCallback(String name, Runnable destructionCallback)

以下方法获取基础范围的对话标识符。每个范围的标识符都不同。对于 session 范围的 implementation,此标识符可以是 session 标识符。

String getConversationId()

使用自定义范围

在编写并测试一个或多个自定义Scope __mplement 之后,您需要让 Spring 容器知道您的新 scope(s)。以下方法是使用 Spring 容器注册新Scope的核心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过 BeanFactory property 在 Spring 附带的大多数具体ApplicationContext __mplement 上使用。

registerScope(..)方法的第一个参数是与范围关联的唯一 name; Spring 容器本身中此类名称的示例是singletonprototyperegisterScope(..)方法的第二个参数是您希望注册和使用的自定义Scope implementation 的实际实例。

假设您编写自定义Scope implementation,然后将其注册如下。

下面的 example 使用,它包含在 Spring 中,但默认情况下未注册。对于您自己的自定义Scope __mplement,说明将是相同的。

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

然后创建符合自定义Scope的作用域规则的 bean 定义:

<bean id="..." class="..." scope="thread">

使用自定义Scope implementation,您不仅限于范围的编程注册。你也可以使用CustomScopeConfigurer class 以声明方式进行Scope注册:

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>

</beans>

<aop:scoped-proxy/>放在FactoryBean implementation 中时,工厂 bean 本身是作用域的,而不是从getObject()返回的 object。

7.6 自定义 bean 的性质

7.6.1 生命周期回调

要与容器的 bean 生命周期的 management 进行交互,可以实现 Spring InitializingBeanDisposableBean接口。容器 calls afterPropertiesSet()为前者,destroy()为后者,允许 bean 在初始化和销毁 beans 时执行某些操作。

JSR-250 @PostConstruct@PreDestroy 注释通常被认为是在现代 Spring application 中接收生命周期回调的最佳实践。使用这些注释意味着 beans 未与 Spring 特定接口耦合。有关详细信息,请参阅第 7.9.8 节,“@PostConstruct 和@PreDestroy”

如果您不想使用 JSR-250 注释但仍希望删除耦合,请考虑使用 init-method 和 destroy-method object 定义元数据。

在内部,Spring Framework 使用BeanPostProcessor implementations 来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义 features 或其他生命周期行为 Spring 不提供 out-of-the-box,您可以自己实现BeanPostProcessor。有关更多信息,请参阅第 7.8 节,“集装箱扩展点”

除了初始化和销毁回调之外,Spring-managed objects 还可以实现Lifecycle接口,以便那些 objects 可以参与由容器自身生命周期驱动的启动和关闭 process。

本节描述了生命周期回调接口。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许 bean 在容器设置 bean 上所有必需的 properties 之后执行初始化工作。 InitializingBean接口指定单个方法:

void afterPropertiesSet() throws Exception;

建议您不要使用InitializingBean接口,因为它会不必要地将 code 耦合到 Spring。或者,使用@PostConstruct annotation 或指定 POJO 初始化方法。对于 XML-based configuration 元数据,使用init-method属性指定具有 void no-argument 签名的方法的 name。使用 Java 配置,您可以使用@BeaninitMethod属性,请参阅名为“接收生命周期回调”的部分。例如,以下内容:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

......与......完全一样

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

但不会将 code 与 Spring 结合。

销毁回调

实现org.springframework.beans.factory.DisposableBean接口允许 bean 在包含它的容器被销毁时获得回调。 DisposableBean接口指定单个方法:

void destroy() throws Exception;

建议您不要使用DisposableBean回调接口,因为它会不必要地将 code 耦合到 Spring。或者,使用@PreDestroy annotation 或指定 bean 定义支持的泛型方法。使用 XML-based configuration 元数据,可以使用<bean/>上的destroy-method属性。使用 Java 配置,您可以使用@BeandestroyMethod属性,请参阅名为“接收生命周期回调”的部分。对于 example,以下定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

与以下内容完全相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

但不会将 code 与 Spring 结合。

可以为<bean>元素的destroy-method属性分配一个特殊的(inferred) value,指示 Spring 自动检测特定 bean class 上的公共closeshutdown方法(任何实现java.lang.AutoCloseablejava.io.Closeable的 class 因此 match)。也可以在<beans>元素的default-destroy-method属性上设置此特殊(inferred) value,以将此行为应用于整个 beans 集(请参阅名为“默认初始化和销毁方法”的部分)。请注意,这是 Java 配置的默认行为。

默认初始化和销毁方法

当您编写初始化和销毁不使用 Spring-specific InitializingBeanDisposableBean回调接口的方法回调时,通常会编写名称为init()initialize()dispose()等的方法。理想情况下,此类生命周期回调方法的名称在项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

您可以将 Spring 容器配置为look以进行命名初始化,并在每个 bean 上销毁回调方法名称。这意味着,作为 application 开发人员,您可以编写 application classes 并使用名为init()的初始化回调,而无需为每个 bean 定义配置init-method="init"属性。 Spring IoC 容器在创建 bean 时(根据前面描述的标准生命周期回调 contract)调用该方法。此 feature 还强制执行初始化和销毁方法回调的一致命名约定。

假设您的初始化回调方法名为init(),并且 destroy 回调方法名为destroy()。您的 class 将类似于以下 example 中的 class。

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}
<beans default-init-method="init">

    <bean id="blogService" class="com.foo.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

top-level <beans/>元素属性上存在default-init-method属性会导致 Spring IoC 容器将 beans 上的init方法识别为初始化方法回调。当创建和组装 bean 时,如果 bean class 具有这样的方法,则在适当的 time 调用它。

您可以使用 top-level <beans/>元素上的default-destroy-method属性类似地(在 XML 中)配置 destroy 方法回调。

如果现有的 bean classes 已经具有以约定方式命名的回调方法,则可以通过使用<bean/>本身的init-methoddestroy-method属性指定(在 XML 中,即)方法 name 来覆盖默认值。

Spring 容器保证在为 bean 提供所有依赖项后立即调用已配置的初始化回调。因此,在原始 bean reference 上调用初始化回调,这意味着 AOP 拦截器等尚未应用于 bean。首先完全创建目标 bean,然后应用具有拦截器链的 AOP 代理(用于 example)。如果目标 bean 和代理是分开定义的,那么 code 甚至可以绕过代理与原始目标 bean 进行交互。因此,将拦截器应用于 init 方法是不一致的,因为这样做会将目标 bean 的生命周期与其 proxy/interceptors 耦合,并在 code 直接与原始目标 bean 进行交互时留下奇怪的语义。

结合生命周期机制

从 Spring 2.5 开始,您有三个控制 bean 生命周期行为的选项:InitializingBeanDisposableBean回调接口;自定义init()destroy()方法;和@PostConstruct 和 @PreDestroy 注释。您可以组合这些机制来控制给定的 bean。

如果为 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法 name,则每个配置的方法都在下面列出的 order 中执行。但是,如果为多个这些生命周期机制配置了相同的方法 name(对于 example,init()表示初始化方法),则该方法将执行一次,如上一节中所述。

为同一 bean 配置的多个生命周期机制具有不同的初始化方法,如下所示:

  • @PostConstruct注释的方法

  • afterPropertiesSet()InitializingBean回调接口定义

  • 自定义配置的init()方法

Destroy 方法在同一个 order 中调用:

  • @PreDestroy注释的方法

  • destroy()DisposableBean回调接口定义

  • 自定义配置的destroy()方法

启动和关闭回调

Lifecycle接口为任何具有自己的生命周期要求的 object 定义基本方法(e.g. 启动和停止一些后台 process):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 Spring-managed object 都可以实现该接口。然后,当ApplicationContext本身接收到启动和停止信号时,e.g. 对于运行时的 stop/restart 场景,它会将这些 calls 级联到 context 中定义的所有Lifecycle __mplement。它通过委托给LifecycleProcessor来做到这一点:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

请注意,LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两种方法来响应正在刷新和关闭的 context。

请注意,常规org.springframework.context.Lifecycle接口只是用于显式 start/stop 通知的普通 contract,并不意味着 auto-startup 在 context refresh time。考虑实现org.springframework.context.SmartLifecycle而不是 fine-grained 控制特定 bean 的 auto-startup(包括启动阶段)。此外,请注意,在销毁之前不能保证停止通知:在常规关闭时,所有Lifecycle beans 将在传播一般销毁回调之前首先收到停止通知;但是,在 context 生命周期内的热刷新或中止刷新尝试时,只会调用 destroy 方法。

启动和关闭调用的 order 非常重要。如果任何两个 object 之间存在“depends-on”关系,则依赖方将在其依赖之后启动,并且它将在其依赖之前停止。但是,有时直接依赖性是未知的。您可能只知道某种类型的 objects 应该在另一种类型的 objects 之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即,Phased上定义的getPhase()方法。

public interface Phased {

    int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,具有最低相位的 objects 首先开始,停止时,遵循 reverse order。因此,实现SmartLifecycle且其getPhase()方法返回Integer.MIN_VALUE的 object 将是第一个开始,最后一个停止。在频谱的另一端,Integer.MAX_VALUE的阶段值将指示 object 应该最后启动并首先停止(可能因为它依赖于其他进程 running)。在考虑阶段 value 时,同样重要的是要知道任何未实现SmartLifecycle的“正常”Lifecycle object 的默认阶段都是 0.因此,任何负阶段 value 都表示 object 应该在那些标准组件之前启动(和在他们之后停止),反之亦然,任何正相值。

如您所见,SmartLifecycle定义的 stop 方法接受回调。在 implementation 的 shutdown process 完成之后,任何 implementation 都必须调用该回调的run()方法。这样就可以在必要时启用异步关闭,因为LifecycleProcessor接口的默认_impleration DefaultLifecycleProcessor将等待每个阶段 objects 的 group 的超时值,以调用该回调。默认 per-phase 超时为 30 秒。您可以通过在 context 中定义名为“lifecycleProcessor”的 bean 来覆盖默认生命周期处理器实例。如果您只想修改超时,那么定义以下内容就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如上所述,LifecycleProcessor接口定义了用于刷新和关闭 context 的回调方法。后者将简单地驱动 shutdown process,就像显式调用stop()一样,但是当 context 关闭时会发生。另一方面,'refresh'回调启用SmartLifecycle beans 的另一个 feature。当 context 被刷新时(在所有 objects 被实例化并初始化之后),将调用该回调,并且此时默认生命周期处理器将检查每个SmartLifecycle object 的isAutoStartup()方法返回的 boolean value。如果是“true”,那么 object 将在那一点开始,而不是等待 context 或它自己的start()方法的显式调用(与 context 刷新不同,context start 不会自动发生在标准的 context implementation 中)。 “phase”value 以及任何“depends-on”关系将以与上述相同的方式确定 startup order。

在 non-web applications 中正常关闭 Spring IoC 容器

本节仅适用于 non-web applications。当相关的 web application 关闭时,Spring 的 web-based ApplicationContext implementations 已经有 code 来正常关闭 Spring IoC 容器。

如果您在 non-web application 环境中使用 Spring 的 IoC 容器;对于 example,在富 client 桌面环境中;你在 JVM 上注册了一个 shutdown hook。这样做可确保正常关闭并 calls singleton beans 上的相关 destroy 方法,以便释放所有资源。当然,您仍然必须正确配置和实现这些 destroy 回调。

要注册 shutdown hook,请调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法:

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

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

7.6.2 ApplicationContextAware 和 BeanNameAware

ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的 object 实例时,该实例将提供的 reference。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,beans 可以通过ApplicationContext接口以编程方式操作创建它们的ApplicationContext,或者通过将 reference 转换为此接口的已知子类(例如ConfigurableApplicationContext)来公开其他功能。一种用途是对其他 beans 进行编程检索。有时这种能力很有用;但是,一般情况下你应该避免它,因为它将 code 耦合到 Spring 并且不遵循 Inversion of Control 样式,其中协作者被提供给 beans 作为 properties。 ApplicationContext的其他方法提供对文件资源的访问,发布 application events 和访问MessageSource。这些额外的 features 在第 7.15 节,“ApplicationContext 的附加功能”中描述

从 Spring 2.5 开始,自动装配是获取ApplicationContext的 reference 的另一种选择。 “传统”constructorbyType自动装配模式(如第 7.4.5 节,“自动装配协作者”中所述)可分别为构造函数参数或 setter 方法参数提供类型ApplicationContext的依赖关系。要获得更多灵活性,包括自动装配字段和多参数方法的功能,请使用新的 annotation-based 自动装配 features。如果这样做,ApplicationContext将自动装入一个字段,构造函数参数或方法参数,如果有问题的字段,构造函数或方法带有@Autowired annotation,则该参数需要ApplicationContext类型。有关更多信息,请参阅第 7.9.2 节,“@Autowired”

ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的 class 时,class 会提供对其关联的 object 定义中定义的 name 的 reference。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在普通 bean properties 的填充之后但在初始化回调之前调用回调,例如InitializingBean afterPropertiesSet 或自定义 init-method。

7.6.3 其他 Aware 接口

除了上面讨论的ApplicationContextAwareBeanNameAware之外,Spring 还提供了一系列Aware接口,允许 beans 向容器指示它们需要某种基础结构依赖性。最重要的Aware接口总结如下 - 作为一般规则,name 是依赖类型的良好指示:

表格 1_.意识到接口

名称注入依赖解释在......
ApplicationContextAware声明ApplicationContext第 7.6.2 节,“ApplicationContextAware 和 BeanNameAware”
ApplicationEventPublisherAware封闭ApplicationContext的 Event 出版商第 7.15 节,“ApplicationContext 的附加功能”
BeanClassLoaderAwareClass loader 用于加载 bean classes。第 7.3.2 节,“实例化 beans”
BeanFactoryAware声明BeanFactory第 7.6.2 节,“ApplicationContextAware 和 BeanNameAware”
BeanNameAware声明 bean 的名称第 7.6.2 节,“ApplicationContextAware 和 BeanNameAware”
BootstrapContextAware资源适配器BootstrapContext容器运行。通常仅在 JCA 感知ApplicationContext中可用第 32 章,JCA CCI
LoadTimeWeaverAware定义的 weaver 用于在 load time 处理 class 定义第 11.8.4 节,“在 Spring Framework 中使用 AspectJ 编织 Load-time”
MessageSourceAware用于解析消息的已配置策略(支持参数化和国际化)第 7.15 节,“ApplicationContext 的附加功能”
NotificationPublisherAwareSpring JMX 通知发布者第 31.7 节,“通知”
PortletConfigAware当前PortletConfig容器运行。仅在 web-aware Spring ApplicationContext中有效第 25 章,Portlet MVC Framework
PortletContextAware当前PortletContext容器运行。仅在 web-aware Spring ApplicationContext中有效第 25 章,Portlet MVC Framework
ResourceLoaderAware配置加载程序以 low-level 访问资源第 8 章,资源
ServletConfigAware当前ServletConfig容器运行。仅在 web-aware Spring ApplicationContext中有效第 22 章,Web MVC framework
ServletContextAware当前ServletContext容器运行。仅在 web-aware Spring ApplicationContext中有效第 22 章,Web MVC framework

请再次注意,这些接口的使用会将 code 绑定到 Spring API,而不会遵循 Inversion of Control 样式。因此,建议将它们用于需要以编程方式访问容器的基础结构 beans。

7.7 Bean 定义继承

bean 定义可以包含许多 configuration 信息,包括构造函数 arguments,property 值和 container-specific 信息,如初始化方法,静态工厂方法 name 等。 child bean 定义从 parent 定义继承 configuration 数据。 child 定义可以根据需要覆盖某些值或添加其他值。使用 parent 和 child bean 定义可以节省大量的 typing。实际上,这是一种模板形式。

如果以编程方式使用ApplicationContext接口,则 child bean 定义由ChildBeanDefinition class 表示。大多数用户不在这个 level 上使用它们,而是以ClassPathXmlApplicationContext之类的方式以声明方式配置 bean 定义。使用 XML-based configuration 元数据时,使用parent属性指定 child bean 定义,并将 parent bean 指定为此属性的 value。

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

如果指定了 none,则 child bean 定义使用 parent 定义中的 bean class,但也可以覆盖它。在后一种情况下,child bean class 必须与 parent 兼容,也就是说,它必须接受 parent 的 property 值。

child bean 定义从 parent 继承范围,构造函数参数值,属性值和方法覆盖,并带有添加新值的选项。您指定的任何范围,初始化方法,销毁方法,and/or static工厂方法设置都将覆盖相应的 parent 设置。

其余设置始终取自 child 定义:依赖于,autowire 模式,依赖性检查,singleton,lazy init。

前面的 example 使用abstract属性显式地将 parent bean 定义标记为 abstract。如果 parent 定义未指定 class,则需要将 parent bean 定义明确标记为abstract,如下所示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

parent bean 无法单独实例化,因为它不完整,并且也明确标记为abstract。当定义是abstract时,它只能用作纯模板 bean 定义,作为 child 定义的 parent 定义。尝试使用这样的abstract parent bean,通过将其称为另一个 bean 的 ref property 或使用 parent bean id 进行显式getBean()调用,会返回错误。类似地,容器的内部preInstantiateSingletons()方法忽略定义为 abstract 的 bean 定义。

ApplicationContext pre-instantiates 默认情况下所有单身人士。因此,重要的是(至少对于 singleton beans),如果你有一个(parent)bean 定义,你打算只用作模板,并且这个定义指定一个 class,你必须确保将 abstract 属性设置为 true ,否则 application context 将实际(尝试)pre-instantiate abstract bean。

7.8 集装箱扩建点

通常,application 开发人员不需要子类ApplicationContext implementation classes。相反,可以通过插入特殊 integration 接口的 implementations 来扩展 Spring IoC 容器。接下来的几节将介绍这些 integration 接口。

7.8.1 使用 BeanPostProcessor 自定义 beans

BeanPostProcessor接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,dependency-resolution 逻辑等。如果要在 Spring 容器完成实例化,配置和初始化 bean 之后实现某些自定义逻辑,则可以插入一个或多个BeanPostProcessor implementations。

您可以配置多个BeanPostProcessor实例,并且可以通过设置order property 来控制执行这些BeanPostProcessor的 order。仅当BeanPostProcessor实现Ordered接口时,才能设置此 property;如果你自己编写BeanPostProcessor,你也应该考虑实现Ordered接口。有关更多详细信息,请参阅BeanPostProcessorOrdered接口的 javadoc。另见下面关于BeanPostProcessors 的程序化注册的说明。

BeanPostProcessor对 bean(或 object)实例进行操作;也就是说,Spring IoC 容器实例化一个 bean 实例,然后BeanPostProcessor做它们的工作。

BeanPostProcessor s 的范围是 per-container。这仅在您使用容器层次结构时才有意义。如果在一个容器中定义BeanPostProcessor,那么它只会be在该容器中。换句话说,在一个容器中定义的 beans 不是由另一个容器中定义的BeanPostProcessor post-processed,即使两个容器都是同一层次结构的一部分。

要更改实际的 bean 定义(i.e.,定义 bean 的蓝图),您需要使用部分 7.8.2,“使用 BeanFactoryPostProcessor 自定义配置元数据”中描述的BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor接口恰好包含两个回调方法。当这样的 class 被注册为容器的 post-processor 时,对于容器创建的每个 bean 实例,post-processor 在容器初始化方法(例如 InitializingBean 的 afterPropertiesSet()和任何声明的 init 方法)被调用之前都从容器中获取回调以及任何 bean 初始化回调之后。 post-processor 可以对 bean 实例执行任何操作,包括完全忽略回调。 bean post-processor 通常会检查回调接口,或者可以使用代理包装 bean。一些 Spring AOP 基础结构 classes 在 order 中实现为 bean post-processors 以提供 proxy-wrapping 逻辑。

ApplicationContext自动检测 configuration 元数据中定义的任何 beans,这些 beans 实现了BeanPostProcessor接口。 ApplicationContext将这些 beans 注册为 post-processors,以便稍后在 bean 创建时调用它们。 Bean post-processors 可以像任何其他 beans 一样部署在容器中。

请注意,在 configuration class 上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的 return 类型应该是 implementation class 本身或至少org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地表明该 bean 的 post-processor 性质。否则,ApplicationContext将无法在完全_create 之前按类型自动检测它。由于需要在 order 中提前实例化以应用于 context 中其他 beans 的初始化,因此这种早期类型检测至关重要。

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext auto-detection(如上所述),但也可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory进行注册。当需要在注册之前评估条件逻辑,或者甚至在层次结构中跨上下文复制 bean 后处理器时,这可能很有用。但请注意,以编程方式添加的BeanPostProcessor s 不尊重Ordered接口。这是注册的 order 指示执行的 order。另请注意,以编程方式注册的BeanPostProcessor始终在通过 auto-detection 注册的那些之前处理,而不管任何显式的 ordering。

_实现BeanPostProcessor接口的类是特殊的,容器对它们的处理方式不同。作为ApplicationContext特殊启动阶段的一部分,它们直接引用的所有BeanPostProcessor和 beans 都会在启动时实例化。接下来,所有BeanPostProcessor都以排序方式注册,并应用于容器中的所有其他 beans。因为 AOP auto-proxying 是作为BeanPostProcessor本身实现的,所以BeanPostProcessor和 beans 它们直接引用都不适合 auto-proxying,因此没有编织方面。

对于任何此类 bean,您应该看到一条信息 log 消息:“Bean foo 不符合所有 BeanPostProcessor 接口处理的条件(对于 example:不符合 auto-proxying 的条件)”。

请注意,如果您使用自动装配将 beans 连接到BeanPostProcessor@Resource(可能会回退到自动装配),Spring 可能会在搜索 type-matching 依赖项候选时访问意外的 beans,从而使它们不适合 auto-proxying 或其他类型的 bean post-processing。例如,如果您有一个使用@Resource注释的依赖项,其中 field/setter name 不直接对应于 bean 的声明 name 且未使用 name 属性,则 Spring 将访问其他 beans 以按类型匹配它们。

以下示例显示如何在ApplicationContext中编写,注册和使用BeanPostProcessor

示例:Hello World,BeanPostProcessor-style

第一个例子说明了基本用法。 example 显示了一个自定义BeanPostProcessor implementation,它调用容器创建的每个 bean 的toString()方法,并将生成的 string 打印到系统 console。

在下面找到自定义BeanPostProcessor implementation class 定义:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

注意如何简单地定义InstantiationTracingBeanPostProcessor。它甚至没有 name,因为它是 bean,它可以像任何其他 bean 一样 dependency-injected。 (前面的 configuration 还定义了一个由 Groovy 脚本支持的 bean.Spring 动态语言支持在名为第 35 章,动态语言支持 .)的章节中有详细说明。

以下简单的 Java application 执行前面的 code 和 configuration:

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

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

前面的 application 的输出类似于以下内容:

Bean 'messenger' created : [emailprotected]
[emailprotected]

Example:RequiredAnnotationBeanPostProcessor

将回调接口或 annotations 与自定义BeanPostProcessor implementation 结合使用是扩展 Spring IoC 容器的常用方法。一个 example 是 Spring 的RequiredAnnotationBeanPostProcessor - 一个带有 Spring 分布的BeanPostProcessor implementation,它确保 beans 上标记有(任意)annotation 的 JavaBean properties 实际上(配置为)带有 value 的 dependency-injected。

7.8.2 使用 BeanFactoryPostProcessor 自定义 configuration 元数据

我们将要看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义类似于BeanPostProcessor的语义,主要区别在于:BeanFactoryPostProcessor对 bean configuration 元数据进行操作;也就是说,Spring IoC 容器允许BeanFactoryPostProcessor读取 configuration 元数据,并可能在容器实例化BeanFactoryPostProcessor以外的任何 beans 之前更改它。

您可以配置多个BeanFactoryPostProcessor,并且可以通过设置order property 来控制执行这些BeanFactoryPostProcessor的 order。但是,如果BeanFactoryPostProcessor实现Ordered接口,则只能设置此 property。如果你自己编写BeanFactoryPostProcessor,你也应该考虑实现Ordered接口。有关更多详细信息,请参阅BeanFactoryPostProcessorOrdered接口的 javadoc。

如果要更改实际的 bean 实例(i.e.,从 configuration 元数据创建的 objects),则需要使用BeanPostProcessor(如上所述第 7.8.1 节,“使用 BeanPostProcessor 自定义 beans”)。虽然技术上可以在BeanFactoryPostProcessor(e.g. ,使用BeanFactory.getBean())中使用 bean 实例,但这样做会导致过早的 bean 实例化,从而违反标准容器生命周期。这可能会导致负面影响,例如绕过 bean 后期处理。

此外,BeanFactoryPostProcessor s 的范围是 per-container。这仅在您使用容器层次结构时才有意义。如果在一个容器中定义BeanFactoryPostProcessor,它将仅应用于该容器中的 bean 定义。即使两个容器都是同一层次结构的一部分,一个容器中的 Bean 定义也不会BeanFactoryPostProcessor在另一个容器中BeanFactoryPostProcessor

工厂 post-processor 在ApplicationContext中声明,在 order 中自动执行,以便将更改应用于定义容器的 configuration 元数据。 Spring 包含许多预定义的 bean factory post-processors,例如PropertyOverrideConfigurerPropertyPlaceholderConfigurer。例如,也可以使用自定义BeanFactoryPostProcessor来注册自定义 property 编辑器。


ApplicationContext自动检测部署到其中的任何实现BeanFactoryPostProcessor接口的 beans。它在适当的 time 使用 beans 作为 bean factory post-processors。您可以像处理任何其他 bean 一样部署这些 post-processor beans。

BeanPostProcessor一样,您通常不希望为延迟初始化配置BeanFactoryPostProcessor。如果没有其他 bean references a Bean(Factory)PostProcessor,post-processor 将根本不会被实例化。因此,将忽略将其标记为延迟初始化,即使在<beans />元素的声明上将default-lazy-init属性设置为true,也会急切地实例化Bean(Factory)PostProcessor

Example:Class name 替换 PropertyPlaceholderConfigurer

您可以使用PropertyPlaceholderConfigurer来使用标准 Java Properties格式在单独的文件中将 bean 定义中的 property 值外部化。这样做可以使部署 application 的人员自定义 environment-specific properties,例如数据库 URL 和密码,而不会有修改主 XML 定义文件或容器 files 的复杂性或风险。

请考虑以下 XML-based configuration 元数据片段,其中定义了带有占位符值的DataSource。 example 显示了从外部Properties文件配置的 properties。在运行时,PropertyPlaceholderConfigurer将应用于将替换 DataSource 的某些 properties 的元数据。要替换的值指定为${property-name}形式的占位符,该形式遵循 Ant/log4j/JSP EL 样式。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

实际值来自标准 Java Properties格式的另一个文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

因此,string ${jdbc.username}在运行时将替换为 value'sa',这同样适用于 properties 文件中 match 键的其他占位符值。 PropertyPlaceholderConfigurer检查大多数 properties 中的占位符和 bean 定义的属性。此外,可以自定义占位符前缀和后缀。

使用 Spring 2.5 中引入的context名称空间,可以使用专用的 configuration 元素配置 property 占位符。可以在location属性中提供一个或多个位置作为 comma-separated 列表。

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

PropertyPlaceholderConfigurer不仅在您指定的Properties文件中查找 properties。默认情况下,如果它在指定的 properties files 中找不到 property,它还会检查 Java System properties。您可以通过使用以下三个支持的 integer 值之一设置 configurer 的systemPropertiesMode property 来自定义此行为:

  • never(0):从不检查系统 properties

  • fallback(1):如果在指定的 properties files 中无法解析,则检查系统 properties。这是默认值。

  • override(2):在尝试指定的 properties files 之前,首先检查系统 properties。这允许系统 properties 覆盖任何其他 property 源。

有关更多信息,请参阅PropertyPlaceholderConfigurer javadocs。

您可以使用PropertyPlaceholderConfigurer替换 class 名称,这在您必须在运行时选择特定的 implementation class 时有用。例如:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.foo.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果 class 在运行时无法解析为有效的 class,则 bean 的解析将在创建时失败,即

Example:PropertyOverrideConfigurer

PropertyOverrideConfigurer,另一个 bean 工厂 post-processor,类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义可以为 bean properties 提供默认值或根本没有值。如果重写的Properties文件没有某个 bean property 的条目,则使用默认的 context 定义。

请注意,bean 定义不知道被覆盖,因此从 XML 定义文件中可以立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer实例为同一个 bean property 定义了不同的值,则由于覆盖机制,最后一个获胜。

Properties 文件 configuration lines 采用以下格式:

beanName.property=value

例如:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

此 example 文件可以与容器定义一起使用,该容器定义包含一个名为 dataSource 的 bean,它包含 driver 和 url properties。

复合 property 名称也被支持,因为 long 作为路径的每个 component 除了被覆盖的最终 property 已经 non-null(可能由构造函数初始化)。在这个 example 中......

foo.fred.bob.sammy=123
  • foo bean 的fred property 的bob property 的sammy property 被设置为标量 value 123

指定的覆盖值始终是字面值;它们没有被翻译成 bean references。当 XML bean 定义中的原始 value 指定 bean reference 时,此约定也适用。

使用 Spring 2.5 中引入的context名称空间,可以使用专用的 configuration 元素配置 property 覆盖:

<context:property-override location="classpath:override.properties"/>

7.8.3 使用 FactoryBean 自定义实例化逻辑

为本身为工厂的 objects 实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是可插入 Spring IoC 容器的实例化逻辑的一个点。如果你有一个复杂的初始化 code,用 Java 表示,而不是(可能)冗长的 XML,你可以创建自己的FactoryBean,在 class 中编写复杂的初始化,然后将自定义FactoryBean插入容器。

FactoryBean接口提供了三种方法:

  • Object getObject():返回此工厂创建的 object 的实例。可以共享实例,具体取决于此工厂是返回单例还是原型。

  • boolean isSingleton():如果FactoryBean返回单例,则返回true,否则返回false

  • Class getObjectType():返回getObject()方法返回的 object 类型,如果事先不知道类型,则返回null

FactoryBean概念和接口用于 Spring Framework 中的许多地方; FactoryBean接口的 50 多个 implementations 与 Spring 本身一起发布。

当你需要向一个容器询问一个实际的FactoryBean实例本身而不是它生成的 bean 时,在调用ApplicationContextgetBean()方法时,使用&符号(&)作为 bean 的 id 前缀。因此对于 id 为myBean的给定FactoryBean,在容器上调用getBean("myBean")会返回FactoryBean的乘积;而,调用getBean("&myBean")会返回FactoryBean实例本身。

7.9 Annotation-based container configuration


annotations 是否比 XML 更适合配置 Spring?

annotation-based 配置的引入提出了这种方法是否比 XML 更“好”的问题。简短的答案取决于它。答案是,每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,annotations 在其声明中提供了大量的 context,从而导致更简洁,更简洁的 configuration。但是,XML 擅长在不触及 source code 或重新编译它们的情况下连接组件。一些开发人员更喜欢将布线靠近源,而其他开发人员则认为带注释的 classes 不再是 POJO,而且 configuration 变得分散且难以控制。

无论选择如何,Spring 都可以兼顾两种风格,甚至可以将它们混合在一起。值得指出的是,通过选项,Spring 允许以 non-invasive 方式使用注释,而不触及目标组件 source code,并且在工具方面,Spring 工具套件支持所有 configuration 样式。


annotation-based configuration 提供了 XML 设置的替代方法,它依赖于字节码元数据来连接组件而不是 angle-bracket 声明。开发人员不使用 XML 来描述 bean 布线,而是通过在相关的 class,方法或字段声明上使用 annotations 将 configuration 移动到 component class 本身。如名为“Example:RequiredAnnotationBeanPostProcessor”的部分中所述,将BeanPostProcessor与 annotations 结合使用是扩展 Spring IoC 容器的常用方法。例如,Spring 2.0 引入了使用@Required annotation 强制执行所需 properties 的可能性。 Spring 2.5 使得有可能采用相同的通用方法来驱动 Spring 的依赖注入。从本质上讲,@Autowired annotation 提供了与第 7.4.5 节,“自动装配协作者”中描述的相同的功能,但具有更多 fine-grained 控制和更广泛的适用性。 Spring 2.5 还添加了对 JSR-250 注释的支持,例如@PostConstruct@PreDestroy。 Spring 3.0 添加了 JSR-330(Java 的依赖注入)_注释包含在 javax.inject 包中的支持,例如@Inject@Named。有关这些注释的详细信息可以在相关部分中找到。

注释注入是在 XML 注入之前执行的,因此后一种 configuration 将覆盖前两种方法连接的 properties。

与往常一样,您可以将它们注册为单独的 bean 定义,但也可以通过在 XML-based Spring configuration 中包含以下标记来隐式注册它们(注意包含context命名空间):

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

(隐式注册的 post-processors 包括AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 会PersistenceAnnotationBeanPostProcessor,以及前面提到的RequiredAnnotationBeanPostProcessor .)

<context:annotation-config/>仅在定义它的同一 application context 中查找 beans 上的 annotations。这意味着,如果将<context:annotation-config/>放在WebApplicationContextDispatcherServlet,它只检查控制器中的@Autowired beans,而不检查您的服务。有关更多信息,请参见第 22.2 节,“DispatcherServlet”

7.9.1 @Required

@Required annotation 适用于 bean property setter 方法,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

这个 annotation 只是表明受影响的 bean property 必须在 configuration time 中填充,通过 bean 定义中的显式 property value 或通过自动装配填充。如果尚未填充受影响的 bean property,容器将抛出 exception;这允许急切和明确的失败,以后避免NullPointerException等。仍然建议您将 bean class 本身(例如)放入 init 方法中。即使在容器外部使用 class,这样做也会强制执行那些必需的 references 和值。

7.9.2 @Autowired

在下面的示例中,可以使用 JSR 330 的@Inject annotation 代替 Spring 的@Autowired annotation。有关详细信息,请参阅这里

您可以将@Autowired annotation 应用于构造函数:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

从 Spring Framework 4.3 开始,如果目标 bean 仅定义一个开头的构造函数,则不再需要在这样的构造函数上使用@Autowired annotation。但是,如果有几个构造器可用,则必须注释至少一个构造器以教导容器使用哪一个。

正如所料,您还可以将@Autowired annotation 应用于“传统”setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

您还可以将 annotation 应用于具有任意名称 and/or multiple arguments 的方法:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

您也可以将@Autowired应用于字段,甚至可以将它与构造函数混合使用:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

确保目标组件(e.g. MovieCatalogCustomerPreferenceDao)始终按照用于@Autowired -annotated 注入点的类型声明。否则,由于在运行时找不到类型 match,注入可能会失败。

对于通过 classpath 扫描找到的 XML-defined beans 或 component classes,容器通常会事先知道具体类型。但是,对于@Bean工厂方法,您需要确保声明的 return 类型具有足够的表现力。对于实现多个接口的组件或可能由 implementation 类型引用的组件,请考虑在工厂方法中声明最具体的 return 类型(至少与引用 bean 的注入点所需的具体类型一致)。

通过将 annotation 添加到需要该类型的 array 的字段或方法,也可以从ApplicationContext提供特定类型的所有 beans:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

这同样适用于类型集合:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

如果希望 array 或列表中的项目按特定 order 排序,则目标 beans 可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority annotation。否则,它们的 order 将遵循容器中相应目标 bean 定义的 registration order。

@Order annotation 可以在 target class level 上声明,也可以在@Bean方法上声明,每个 bean 定义可能是非常独立的(如果多个定义具有相同的 bean class)。 @Order值可能会影响注入点的优先级,但请注意它们不会影响 singleton startup order,这是由依赖关系和@DependsOn声明确定的正交关注点。

请注意,标准javax.annotation.Priority annotation 在@Bean level 中不可用,因为它无法在方法上声明。它的语义可以通过@Order值与@Primary组合在每个类型的单个 bean 上建模。

甚至键入的 Maps 也可以自动装配为 long,因为预期的 key 类型是String。 Map 值将包含期望类型的所有 beans,并且键将包含相应的 bean 名称:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

默认情况下,只要零候选 beans 可用,自动装配就会失败;默认行为是将带注释的方法,构造函数和字段视为指示所需的依赖项。可以更改此行为,如下所示。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

只能将一个带注释的构造函数 per-class 标记为必需,但可以注释多个 non-required 构造函数。在这种情况下,每个都被认为是候选者之一,Spring 使用最贪婪的构造函数,其依赖性可以得到满足,即具有最大数量的 arguments 的构造函数。

建议@Autowired的必需属性优于@Required annotation。 required 属性表示自动装配不需要 property,如果无法自动装配 property,则忽略 property。另一方面,@Required更强大,因为它强制执行由容器支持的任何方式设置的 property。如果没有注入 value,则引发相应的 exception。

或者,您可以通过 Java 8 的java.util.Optional表达特定依赖项的 non-required 性质:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

您还可以将@Autowired用于 well-known 可解析依赖项的接口:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。这些接口及其扩展接口(如ConfigurableApplicationContextResourcePatternResolver)将自动解析,无需特殊设置。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired@Inject@Resource@Value 注释由 Spring BeanPostProcessor implementations 处理,这反过来意味着您无法在自己的BeanPostProcessorBeanFactoryPostProcessor类型(如果有)中应用这些注释。必须通过 XML 或使用 Spring @Bean方法显式“连接”这些类型。

7.9.3 Fine-tuning annotation-based 使用 @Primary 自动装配

由于按类型自动装配可能会导致多个候选项,因此通常需要对选择 process 进行更多控制。实现此目的的一种方法是使用 Spring 的@Primary annotation。 @Primary表示当多个 beans 是自动连接到 single-valued 依赖项的候选者时,应该优先选择特定的 bean。如果候选者中只存在一个“主要”bean,则它将是自动连接的 value。

假设我们有以下 configuration,它将firstMovieCatalog定义为主MovieCatalog

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

通过这样的 configuration,以下MovieRecommender将与firstMovieCatalog一起自动装配。

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相应的 bean 定义如下所示。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

7.9.4 Fine-tuning annotation-based 使用限定符自动装配

当可以确定一个主要候选者时,@Primary是一种有效的方式,可以通过多个实例使用类型自动装配。当需要更多地控制选择 process 时,可以使用 Spring 的@Qualifier annotation。您可以将限定符值与特定的 arguments 相关联,缩小类型匹配集,以便为每个参数选择特定的 bean。在最简单的情况下,这可以是一个简单的描述性 value:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

@Qualifier annotation 也可以在单个构造函数 arguments 或方法参数上指定:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

相应的 bean 定义如下所示。带有限定符 value“main”的 bean 与使用相同 value 限定的构造函数参数连接。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

对于后备 match, bean name 被视为默认限定符 value。因此,您可以使用 id“main”而不是嵌套的限定符元素定义 bean,从而得到相同的匹配结果。但是,虽然您可以使用此约定通过 name 引用特定的 beans,但@Autowired基本上是关于带有可选语义限定符的 type-driven 注入。这意味着限定符值,即使使用 bean name 回退,在类型匹配集中总是具有缩小的语义;它们在语义上不表达对唯一 bean id 的 reference。好的限定符值是“main”或“EMEA”或“persistent”,表示独立于 bean id的特定 component 的特征,如果匿名 bean 定义(如前面的 example 中的定义),则可能是 auto-generated。

限定符也适用于类型集合,如上所述,例如,Set<MovieCatalog>。在这种情况下,根据声明的限定符匹配的所有 beans 都作为集合注入。这意味着限定符不必是唯一的;它们只是简单地构成过滤标准。对于 example,您可以使用相同的限定符 value“action”定义多个MovieCatalog beans,所有这些都将注入Set<MovieCatalog>注释@Qualifier("action")

允许在 type-matching 候选者中针对目标 bean 名称选择限定符值甚至不需要在注入点处使用@Qualifier annotation。如果没有其他解析指示符(e.g. 限定符或主要标记),对于 non-unique 依赖情况,Spring 将匹配目标 bean 名称的注入点 name(i.e.字段 name 或参数 name)并选择 same-named 候选者,如果有的话。

也就是说,如果你打算用 name 表达 annotation-driven 注入,不要主要使用@Autowired,即使能够在 type-matching 候选者中选择 bean name。而是使用 JSR-250 @Resource annotation,它在语义上定义为通过其唯一的 name 标识特定的目标 component,声明的类型与匹配的 process 无关。 @Autowired具有相当不同的语义:按类型选择候选 beans 后,指定的 String 限定符 value 将仅在 type-selected 候选者中被考虑,e.g. 将“帐户”限定符与使用相同限定符标签标记的 beans 匹配。

对于本身定义为 collection/map 或 array 类型的 beans,@Resource是一个很好的解决方案,引用特定的集合或 array bean 由 unique name。也就是说,从 4.3 开始,collection/map 和 array 类型也可以通过 Spring 的@Autowired类型匹配算法匹配,因为 long,因为元素类型信息保存在@Bean return 类型签名或集合继承层次结构中。在这种情况下,限定符值可用于在 same-typed 集合中进行选择,如上一段所述。

从 4.3 开始,@Autowired也会考虑自 reference for injection,i.e。 references 返回当前注入的 bean。请注意,自我注射是一种后备;对其他组件的常规依赖性始终具有优先权。从这个意义上说,自我引用并不参与常规的候选人选择,因此尤其不是主要的;相反,它们总是最低优先级。在实践中,仅使用 self references 作为最后的手段,e.g. 通过 bean 的 transactional 代理调用同一实例上的其他方法:在这种情况下,考虑将受影响的方法分解为单独的委托 bean。或者,使用@Resource可以通过其唯一的 name 获取代理回到当前 bean。

@Autowired适用于字段,构造函数和 multi-argument 方法,允许通过参数 level 中的限定符注释缩小范围。相比之下,@Resource仅支持字段,bean property setter 方法只支持一个参数。因此,如果您的注射目标是构造函数或 multi-argument 方法,请坚持使用限定符。

您可以创建自己的自定义限定符注释。只需定义 annotation 并在定义中提供@Qualifier annotation:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

然后,您可以在自动装配的字段和参数上提供自定义限定符:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

接下来,提供候选 bean 定义的信息。您可以将<qualifier/>标记添加为<bean/>标记的 sub-elements,然后将typevalue指定为匹配您的自定义限定符注释。该类型与 annotation 的 fully-qualified class name 匹配。或者,为方便起见,如果不存在冲突名称的风险,您可以使用 short class name。两种方法都在以下示例中进行了演示。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

Section 7.10,“ Classpath 扫描和托管组件”中,您将看到 annotation-based 替代提供 XML 中的限定符元数据。具体来说,请参见第 7.10.8 节,“使用 annotations 提供限定符元数据”

在某些情况下,使用没有 value 的 annotation 可能就足够了。当 annotation 用于更通用的目的并且可以跨多种不同类型的依赖项应用时,这可能很有用。例如,您可以提供在没有 Internet 连接时搜索的脱机目录。首先定义简单的 annotation:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

然后将 annotation 添加到字段或 property 以自动装配:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}

现在 bean 定义只需要一个限定符type

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/>
    <!-- inject any dependencies required by this bean -->
</bean>

除了简单的value属性之外或者代替简单的value属性,您还可以定义接受命名属性的自定义限定符注释。如果随后在要自动装配的字段或参数上指定了多个属性值,则 bean 定义必须_匹配所有此类属性值才能被视为自动装配候选。作为示例,请考虑以下 annotation 定义:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

在这种情况下Format是 enum:

public enum Format {
    VHS, DVD, BLURAY
}

要自动装配的字段使用自定义限定符进行注释,并包含两个属性的值:genreformat

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

最后,bean 定义应包含匹配的限定符值。此 example 还演示了可以使用 bean 元属性而不是<qualifier/> sub-elements。如果可用,<qualifier/>及其属性优先,但如果不存在这样的限定符,则自动装配机制将回退到<meta/>标记内提供的值,如下面 example 中的最后两个 bean 定义。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

7.9.5 使用泛型作为自动装配限定符

除了@Qualifier annotation 之外,还可以使用 Java 泛型类型作为隐式的限定形式。例如,假设您有以下 configuration:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假设上面的 beans 实现了一个通用接口,i.e。 Store<String>Store<Integer>,你可以@Autowire Store接口,泛型将用作限定符:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

自动装配 Lists,Maps 和 Arrays 时,通用限定符也适用:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

7.9.6 CustomAutowireConfigurer

CustomAutowireConfigurer 上是一个BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注释类型,即使它们没有使用 Spring 的@Qualifier 注释注释。

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver通过以下方式确定 autowire 候选人:

  • 每个 bean 定义的autowire-candidate value

  • <beans/>元素上可用的任何default-autowire-candidates pattern(s)

  • 是否存在@Qualifier 注释和CustomAutowireConfigurer注册的任何自定义注释

当多个 beans 有资格作为 autowire 候选者时,“primary”的确定如下:如果候选者中只有一个 bean 定义将primary属性设置为true,则将选择它。

7.9.7 @Resource

Spring 还支持使用字段上的 JSR-250 @Resource annotation 或 bean property setter 方法进行注入。这是 Java EE 5 和 6 中的 common pattern,适用于 JSF 1.2 managed beans 或 JAX-WS 2.0 endpoints 中的 example。 Spring 也为 Spring-managed objects 支持此 pattern。

@Resource采用 name 属性,默认情况下 Spring 将 value 解释为要注入的 bean name。换句话说,它遵循 by-name 语义,如_示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果未明确指定 name,则默认 name 派生自字段 name 或 setter 方法。如果是字段,则需要字段 name;在 setter 方法的情况下,它采用 bean property name。所以下面的 example 将 bean 与 name“movieFinder”注入其 setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

_an 注释提供的 name 被CommonAnnotationBeanPostProcessor解析为如果您明确配置 Spring 的SimpleJndiBeanFactory,则可以通过 JNDI 解析名称。但是,建议您依赖于默认行为,只需使用 Spring 的 JNDI 查找功能来保留间接的 level。

@Resource用法的唯一情况下,没有指定明确的 name,并且类似于@Autowired@Resource找到主要类型 match 而不是特定的名为 bean 并解析 well-known 可解析的依赖项:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource接口。

因此,在下面的示例中,customerPreferenceDao字段首先查找名为 customerPreferenceDao 的 bean,然后返回类型CustomerPreferenceDao的主类型 match。根据已知的可解析依赖类型ApplicationContext注入“context”字段。

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

7.9.8 @PostConstruct 和 @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource annotation,还识别 JSR-250 生命周期注释。在 Spring 2.5 中引入,对这些注释的支持提供了初始化回调破坏回调中描述的另一种替代方法。如果在 Spring ApplicationContext中注册,则在生命周期的同一点调用带有其中一个注释的方法,作为相应的 Spring 生命周期接口方法或显式声明的回调方法。在下面的 example 中,缓存在初始化时将是 pre-populated 并在销毁时清除。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

有关组合各种生命周期机制的效果的详细信息,请参阅“组合生命周期机制”一节

7.10 Classpath 扫描和托管组件

本章中的大多数示例都使用 XML 来指定_Spiguration 元数据,该元数据在 Spring 容器中生成每个BeanDefinition。上一节(第 7.9 节,“Annotation-based 容器配置”)演示了如何通过 source-level annotations 提供大量 configuration 元数据。但是,即使在这些示例中,“base”bean 定义也在 XML 文件中明确定义,而 annotations 仅驱动依赖项注入。本节介绍通过扫描 classpath 隐式检测候选组件的选项。候选组件是 classes,它们与过滤条件匹配,并且在容器中注册了相应的 bean 定义。这消除了使用 XML 执行 bean 注册的需要;相反,您可以使用 annotations(用于 example @Component),AspectJ 类型表达式或您自己的自定义过滤条件来选择哪些 classes 将在容器中注册 bean 定义。

从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多 features 都是核心 Spring Framework 的一部分。这允许您使用 Java 定义 beans 而不是使用传统的 XML files。有关如何使用这些新 features 的示例,请查看@Configuration@Bean@Import@DependsOn 注释。

7.10.1 @Component 以及进一步的刻板印象注释

@Repository annotation 是任何 class 的标记,它满足 repository(也称为 Data Access Object 或 DAO)的角色或构造型。该标记的用途是中描述的 exceptions 的自动转换。

Spring 提供了进一步的构造型注释:@Component@Service@Controller@Component是任何 Spring-managed component 的通用构造型。对于更具体的用例,@Repository@Service@Controller@Component的特化,例如,分别在持久性,服务和表示层中。因此,您可以使用@Component注释 component classes,但是通过使用@Repository@Service@Controller注释它们,您的 class 更适合通过工具处理或与方面关联。例如,这些构造型注释成为切入点的理想目标。在 Spring Framework 的未来版本中,@Repository@Service@Controller也可能带有额外的语义。因此,如果您在服务层使用@Component@Service之间进行选择,@Service显然是更好的选择。同样,如上所述,已经支持@Repository作为持久层中自动 exception 转换的标记。

7.10.2 Meta-annotations

Spring 提供的许多注释可以在您自己的 code 中用作 meta-annotations。 meta-annotation 只是一个 annotation,可以应用于另一个 annotation。对于 example,上面提到的@Service annotation 是 meta-annotated 和@Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

Meta-annotations 也可以组合起来创建组合注释。例如,Spring MVC 的@RestController annotation 由@Controller@ResponseBody组成。

此外,组合的 annotations 可以选择从 meta-annotations 重新声明属性以允许用户自定义。当您只想公开 meta-annotation 属性的子集时,这可能特别有用。对于 example,Spring 的@SessionScope annotation 将范围 name 硬编码为session,但仍允许自定义proxyMode

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然后可以使用@SessionScope而不声明proxyMode,如下所示:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

或者使用的重写 value 如下:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

有关详细信息,请参阅Spring Annotation Programming Model

7.10.3 自动检测 classes 并注册 bean 定义

Spring 可以自动检测原型 classes 并使用ApplicationContext注册相应的BeanDefinition。对于 example,以下两个 classes 符合此类自动检测的条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要自动检测这些 classes 并注册相应的 beans,您需要将@ComponentScan添加到@Configuration class,其中basePackages属性是两个 classes 的 common parent 包。 (或者,您可以指定 comma/semicolon/space-separated 列表,其中包含每个 class.)的 parent 包

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

为简洁起见,上面可能使用了 annotation 的value属性 i.e。 @ComponentScan("org.example")

以下是使用 XML 的替代方法

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

使用<context:component-scan>隐式启用<context:annotation-config>的功能。使用<context:component-scan>时通常不需要包含<context:annotation-config>元素。

扫描 classpath 包需要在 classpath 中存在相应的目录条目。使用 Ant build JAR 时,请确保不要激活 JAR 任务的 files-only 开关。此外,classpath 目录可能不会在某些环境中基于安全性 policies 公开,e.g. JDK 1.7.0_45 及更高版本上的独立应用程序(在清单中需要'Trusted-Library'设置;请参阅http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

此外,使用 component-scan 元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件是自动检测并连接在一起的 - 所有这些都没有在 XML 中提供任何 bean configuration 元数据。

您可以通过将属性与false的_val包含在一起来禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册。

7.10.4 使用过滤器自定义扫描

默认情况下,使用@Component@Repository@Service@Controller注释的 classes 或使用@Component注释的自定义注释是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。将它们添加为@ComponentScan annotation 的 includeFilters 或 excludeFilters 参数(或component-scan元素的 include-filter 或 exclude-filter sub-elements)。每个过滤器元素都需要typeexpression属性。以下 table 描述了过滤选项。

表格 1_.过滤器类型

过滤器类型Example 表达式描述
annotation(默认)org.example.SomeAnnotation目标组件中 level 类型的注释。
分配org.example.SomeClass目标组件可分配给(extend/implement)的 class(或接口)。
AspectJorg.example..*Service+要由目标组件匹配的 AspectJ 类型表达式。
正则表达式org\.example\.Default.*要由目标组件 class 名称匹配的正则表达式。
习惯org.example.MyTypeFilterorg.springframework.core.type .TypeFilter接口的自定义 implementation。

以下 example 显示 configuration 忽略所有@Repository 注释并使用“stub”repositories。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

以及使用 XML 的等价物

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

您还可以通过在 annotation 上设置useDefaultFilters=false或将use-default-filters="false"作为<component-scan/>元素的属性来禁用默认过滤器。这实际上会禁用自动检测@Component@Repository@Service@Controller@Configuration注释的 classes。

7.10.5 在组件中定义 bean 元数据

Spring 组件还可以将 bean 定义元数据提供给容器。您可以使用与@Configuration annotated classes 中定义 bean 元数据相同的@Bean annotation 来执行此操作。这是一个简单的 example:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

这个 class 是 Spring component,其doWork()方法中包含 application-specific code。但是,它还提供 bean 定义,其具有引用方法publicInstance()的工厂方法。 @Bean annotation 标识工厂方法和其他 bean 定义 properties,例如通过@Qualifier annotation 的限定符 value。可以指定的其他方法 level annotations 是@Scope@Lazy和自定义限定符注释。

除了 component 初始化的作用外,@Lazy annotation 还可以放在标有@Autowired@Inject的注入点上。在此 context 中,它会导致注入 lazy-resolution 代理。

如前所述,支持自动装配的字段和方法,并支持自动装配@Bean方法:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

example 将String方法参数country自动装配到另一个名为privateInstance的 bean 上age property 的 value。 Spring Expression Language 元素通过符号#{ <expression> }定义 property 的 value。对于@Value annotations,表达式解析器预先配置为在解析表达式文本时查找 bean 名称。

从 Spring Framework 4.3 开始,您还可以在 order 中声明类型InjectionPoint(或其更具体的子类DependencyDescriptor)的工厂方法参数,以访问触发创建当前 bean 的请求注入点。请注意,这仅适用于 bean 实例的实际创建,而不适用于现有实例的注入。因此,这种 feature 对原型范围的 beans 最有意义。对于其他作用域,工厂方法只会看到触发在给定作用域中创建新 bean 实例的注入点:对于 example,触发创建惰性 singleton bean 的依赖项。在这种情况下,使用提供的注入点元数据和语义关注。

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

常规 Spring component 中的@Bean方法的处理方式与 Spring @Configuration class 中的对应方式不同。不同之处在于,CGLIB 不会增强@Component classes 以拦截方法和字段的调用。 CGLIB 代理是调用@Configuration classes 中@Bean方法中的方法或字段创建 bean 元数据 references 到协作 objects 的方法;这些方法不是用普通的 Java 语义调用的,而是通过 order 中的容器来提供 Spring beans 的常规生命周期管理和代理,即使在通过程序 calls 到@Bean方法引用其他 beans 时也是如此。相反,在普通@Component class 中调用@Bean方法中的方法或字段具有标准 Java 语义,不应用特殊的 CGLIB 处理或其他约束。

您可以将@Bean方法声明为static,允许在不包含_conating 包含 configuration class 作为实例的情况下调用它们。这在定义 post-processor beans,e.g 时特别有意义。类型BeanFactoryPostProcessorBeanPostProcessor,因为这样的 beans 将在容器生命周期的早期初始化,并且应避免在此时触发 configuration 的其他部分。

请注意,calls to static @Bean方法永远不会被容器拦截,甚至在@Configuration classes 中也不会被拦截(见上文)。这是由于技术限制:CGLIB 子类化只能覆盖 non-static 方法。因此,直接调用另一个@Bean方法将具有标准 Java 语义,从而导致直接从工厂方法本身返回独立实例。

@Bean方法的 Java 语言可见性对 Spring 容器中生成的 bean 定义没有立即影响。您可以根据需要在非@Configuration classes 和任何地方的静态方法中自由声明工厂方法。但是,@Configuration classes 中的常规@Bean方法需要可以覆盖,i.e。它们不得声明为privatefinal

@Bean方法也将在给定 component 或 configuration class 的 base classes 上发现,以及在 component 或 configuration class 实现的接口中声明的 Java 8 默认方法上发现。这使得在编写复杂的配置安排时具有很大的灵活性,甚至可以通过 Spring 4.2 的 Java 8 默认方法实现多重继承。

最后,请注意,单个 class 可以为同一个 bean 保存多个@Bean方法,作为多个工厂方法的安排,具体取决于运行时的可用依赖项。这与在其他 configuration 场景中选择“最贪婪”构造函数或工厂方法的算法相同:将在构造 time 中选择具有最多可满足依赖项的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。

7.10.6 命名自动检测的组件

当 component 作为 scan process 的一部分自动检测时,其 bean name 由该扫描程序已知的BeanNameGenerator策略生成。默认情况下,任何包含 name value的 Spring 构造型 annotation(@Component@Repository@Service@Controller)都会将 name 提供给相应的 bean 定义。

如果这样的 annotation 不包含 name value或任何其他检测到的 component(例如自定义过滤器发现的那些),则默认的 bean name generator 将返回未大写的 non-qualified class name。对于 example,如果检测到以下 component classes,则名称将为myMovieListermovieFinderImpl

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果您不想依赖默认的 bean-naming 策略,则可以提供自定义 bean-naming 策略。首先,实现BeanNameGenerator接口,并确保包含默认的 no-arg 构造函数。然后,在配置扫描程序时提供 fully-qualified class name:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

作为一般规则,考虑使用 annotation 指定 name,只要其他组件可以对其进行显式 references。另一方面,只要容器负责接线,auto-generated 名称就足够了。

7.10.7 为自动检测的组件提供范围

与 Spring-managed 组件一样,自动检测组件的默认范围和最常见范围是singleton。但是,有时您需要一个可以通过@Scope annotation 指定的不同范围。只需在 annotation 中提供范围的 name:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

@Scope annotations 仅在具体的 bean class(对于带注释的组件)或工厂方法(对于@Bean方法)上进行了内省。与 XML bean 定义相比,没有 bean 定义继承的概念,class level 中的继承层次结构与元数据目的无关。

有关 web-specific 范围的详细信息,例如 Spring context 中的“request”/“ session”,请参阅第 7.5.4 节,“请求,session,global session,application 和 WebSocket 范围”。就像这些范围的 pre-built 注释一样,您也可以使用 Spring 的 meta-annotation 方法编写自己的范围注释:e.g. 带有@Scope("prototype")的自定义注释 meta-annotated,可能还声明了自定义 scoped-proxy 模式。

要为范围解析提供自定义策略而不是依赖于 annotation-based 方法,请实现ScopeMetadataResolver接口,并确保包含默认的 no-arg 构造函数。然后,在配置扫描程序时提供 fully-qualified class name:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

使用某些 non-singleton 范围时,可能需要为范围的 objects 生成代理。推理在名为“Scoped beans as dependencies”的部分中描述。为此,component-scan 元素上有 scoped-proxy 属性。三个可能的值是:no,interfaces 和 targetClass。对于 example,以下 configuration 将生成标准 JDK 动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

7.10.8 使用 annotations 提供限定符元数据

@Qualifier annotation 在第 7.9.4 节,“Fine-tuning annotation-based 使用限定符自动装配”中讨论。该部分中的示例演示了在解析自动线候选时使用@Qualifier annotation 和自定义限定符注释来提供 fine-grained 控件。因为这些示例基于 XML bean 定义,所以使用 XML 中bean元素的qualifiermeta sub-elements 在候选 bean 定义上提供限定符元数据。当依赖 classpath 扫描来自动检测组件时,可以在候选 class 上为 type-level 注释提供限定符元数据。以下三个示例演示了此技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

与大多数 annotation-based 替代方案一样,请记住 annotation 元数据绑定到 class 定义本身,而使用 XML 允许多个相同类型的 beans 提供其限定符元数据的变体,因为提供的元数据是 per-instance 而不是 per-class。

7.11 使用 JSR 330 Standard Annotations

从 Spring 3.0 开始,Spring 支持 JSR-330 standard annotations(依赖注入)。这些注释的扫描方式与 Spring annotations 相同。您只需要在 classpath 中包含相关的 jars。

如果使用 Maven,则javax.inject artifact 在标准 Maven repository(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)中可用。您可以将以下依赖项添加到文件 pom.xml:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

7.11.1 依赖注入 @Inject 和 @Named

而不是@Autowired@javax.inject.Inject可以如下使用:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        ...
    }
}

@Autowired一样,可以在字段 level,方法 level 和 constructor-argument level 中使用@Inject。此外,您可以将注入点声明为Provider,允许 on-demand 访问较短范围的 beans 或通过Provider.get()调用延迟访问其他 beans。作为上述 example 的变体:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        ...
    }
}

如果要对应注入的依赖项使用限定的 name,则应使用@Named annotation,如下所示:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

@Autowired一样,@Inject也可以与java.util.Optional@Nullable一起使用。这更适用于此,因为@Inject没有required属性。

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

7.11.2 @Named 和@ManagedBean: @Component annotation 的标准等价物

而不是@Component@javax.inject.Namedjavax.annotation.ManagedBean可以如下使用:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

在没有为 component 指定 name 的情况下使用@Component非常常见。 @Named可以以类似的方式使用:

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

使用@Named@ManagedBean时,可以使用 component 扫描,其方式与使用 Spring annotations 时完全相同:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

@Component相反,JSR-330 @Named和 JSR-250 ManagedBean 注释不可组合。请使用 Spring 的构造型 model 进行 building custom component annotations。

7.11.3 JSR-330 standard annotations 的限制

使用标准注释时,重要的是要知道某些重要的 features 不可用,如下面的 table 所示:

表格 1_.Spring component model 元素与 JSR-330 变体

弹簧javax.inject.*javax.inject restrictions/comments
@Autowired@Inject@Inject没有'required'属性;可以与 Java 8 的Optional一起使用。
@Component@Named/@ManagedBeanJSR-330 不提供可组合的 model,只是一种识别命名组件的方法。
@Scope(“singleton”)@SingletonJSR-330 默认范围就像 Spring 的prototype。但是,为了使其与 Spring 的一般默认值保持一致,Spring 容器中声明的 JSR-330 bean 默认为singleton。在 order 中使用singleton以外的范围,您应该使用 Spring 的@Scope annotation。 javax.inject还提供@Scope 注释。然而,这个仅用于创建自己的注释。
@Qualifier@Qualifier/@Named对于 building 自定义限定符,javax.inject.Qualifier只是 meta-annotation。具体的 String 限定符(如 Spring 的带有 value 的@Qualifier)可以通过javax.inject.Named关联。
@Value-没有等价物
@Required-没有等价物
@Lazy-没有等价物
ObjectFactory提供商javax.inject.Provider是 Spring 的ObjectFactory的直接替代,只是使用更短的get()方法 name。它也可以与 Spring 的@Autowired或 non-annotated 构造函数和 setter 方法结合使用。

7.12 Java-based container configuration

7.12.1 基本概念: @Bean 和 @Configuration

Spring 新的 Java-configuration 支持中的中心 artifacts 是@Configuration -annotated classes 和@Bean -annotated 方法。

@Bean annotation 用于指示方法实例化,配置和初始化由 Spring IoC 容器管理的新 object。对于那些熟悉 Spring 的<beans/> XML configuration 的人来说,@Bean annotation 与<bean/>元素扮演的角色相同。您可以将@Bean带注释的方法与任何 Spring @Component一起使用,但是,它们最常用于@Configuration beans。

使用@Configuration注释 class 表示其主要目的是作为 bean 定义的源。此外,@Configuration classes 允许通过简单地调用同一 class 中的其他@Bean方法来定义 inter-bean 依赖项。最简单的@Configuration class 如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

上面的AppConfig class 类似于以下 Spring <beans/> XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

完整的 @Configuration vs'lite' @Bean 模式?

当_c方法在 classes 中声明未使用@Configuration注释时,它们被称为在'lite'模式下处理。在@Component或甚至普通的旧 class 中声明的 Bean 方法将被视为'lite',其中包含 class 和@Bean方法的主要目的不同,只是那里的一种奖励。对于 example,服务组件可以通过每个适用的 component class 上的附加@Bean方法将 management 视图公开给容器。在这种情况下,@Bean方法是一种简单的 general-purpose 工厂方法机制。

与完整的@Configuration不同,lite @Bean方法不能声明 inter-bean 依赖项。相反,它们在包含 component 的内部 state 上运行,并且可选地在它们可能声明的 arguments 上运行。因此,这种@Bean方法不应该引用其他方法;每个这样的方法实际上只是一个特定 bean reference 的工厂方法,没有任何特殊的运行时语义。这里的正 side-effect 是没有 CGLIB 子类必须在运行时应用,所以在 class 设计方面没有限制(i.e.包含 class 可能仍然是final等)。

在 common 场景中,@Bean方法将在@Configuration classes 中声明,确保始终使用“完整”模式,因此 cross-method references 将被重定向到容器的生命周期 management。这将防止通过常规 Java 调用意外地调用相同的@Bean方法,这有助于减少在“精简”模式下操作时难以跟踪的细微错误。


@Bean@Configuration 注释将在下面的部分中进行深入讨论。首先,我们将介绍使用 Java-based configuration 创建 spring 容器的各种方法。

7.12.2 使用 AnnotationConfigApplicationContext 实例化 Spring 容器

以下部分记录 Spring 的AnnotationConfigApplicationContext,Spring 3.0 中的新内容。这个多功能的ApplicationContext implementation 不仅能够接受@Configuration classes 作为输入,还能接受使用 JSR-330 元数据注释的普通@Component classes 和 classes。

@Configuration classes 作为输入提供时,@Configuration class 本身被注册为 bean 定义,class 中所有声明的@Bean方法也被注册为 bean 定义。

当提供@Component和 JSR-330 classes 时,它们被注册为 bean 定义,并且假设在必要时在那些 classes 中使用诸如@Autowired@Inject的 DI 元数据。

结构简单

与实例化ClassPathXmlApplicationContext时 Spring XML files 用作输入的方式大致相同,@Configuration classes 可以在实例化AnnotationConfigApplicationContext时用作输入。这允许完全 XML-free 使用 Spring 容器:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

如上所述,AnnotationConfigApplicationContext不仅限于使用@Configuration classes。任何@Component或 JSR-330 带注释的 class 都可以作为输入提供给构造函数。例如:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

以上假设MyServiceImplDependency1Dependency2使用 Spring 依赖注入注释,如@Autowired

使用 register(Class <?>以编程方式构建容器...)

可以使用 no-arg 构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法进行配置。当以编程方式 building AnnotationConfigApplicationContext时,此方法特别有用。

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

使用 scan(String 启用 component 扫描...)

要启用 component 扫描,只需按如下方式注释@Configuration class:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}

有经验的 Spring 用户将熟悉与 Spring 的context:命名空间等效的 XML 声明

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在上面的 example 中,将扫描com.acme包,查找任何@Component -annotated classes,并且这些 classes 将在容器中注册为 Spring bean 定义。 AnnotationConfigApplicationContext公开scan(String…)方法以允许相同的 component-scanning 功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住@Configuration classes 是meta-annotated@Component,因此它们是 component-scanning 的候选者!在上面的 example 中,假设AppConfigcom.acme包(或下面的任何包)中声明,它将在调用scan()期间被拾取,并且在refresh()之后,它的所有@Bean方法将被处理并在容器中注册为 bean 定义。

使用 AnnotationConfigWebApplicationContext 支持 web applications

AnnotationConfigWebApplicationContextWebApplicationContext变体可用于AnnotationConfigWebApplicationContext。在配置 Spring ContextLoaderListener servlet listener,Spring MVC DispatcherServlet等时,可以使用此 implementation。以下是配置典型 Spring MVC web application 的web.xml片段。注意使用contextClass context-param 和 init-param:

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

7.12.3 使用 @Bean 注释

@Bean是 method-level annotation,是 XML <bean/>元素的直接模拟。 annotation 支持<bean/>提供的一些属性,例如:init-methoddestroy-method自动装配name

您可以在@Configuration -annotated 或@Component -annotated class 中使用@Bean annotation。

声明一个 bean

要声明 bean,只需使用@Bean annotation 注释方法。您可以使用此方法在指定为方法的 return value 的类型的ApplicationContext中注册 bean 定义。默认情况下, bean name 将与方法 name 相同。以下是@Bean方法声明的简单示例:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

前面的 configuration 与以下 Spring XML 完全等效:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都在ApplicationContext中创建一个名为transferService的 bean,绑定到TransferServiceImpl类型的 object 实例:

transferService -> com.acme.TransferServiceImpl

您也可以使用接口(或 base class)return 类型声明@Bean方法:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

但是,这会将高级类型预测的可见性限制为指定的接口类型(TransferService),然后,只有在实例化受影响的 singleton bean 后,容器才知道完整类型(TransferServiceImpl)。 Non-lazy singleton beans 根据它们的声明 order 进行实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个 component 何时尝试@

如果您始终通过声明的服务接口引用您的类型,则@Bean return 类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其 implementation 类型引用的组件,更安全地声明最具体的 return 类型(至少与引用 bean 的注入点所需的具体相同)。

Bean 依赖项

@Bean带注释的方法可以有任意数量的参数来描述 build bean 所需的依赖关系。例如,如果我们的TransferService需要AccountRepository,我们可以通过方法参数实现该依赖:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

解析机制与 constructor-based 依赖注入非常相似,有关详细信息,请参阅相关部分

接收生命周期回调

使用@Bean annotation 定义的任何 classes 都支持常规生命周期回调,并且可以使用 JSR-250 中的@PostConstruct@PreDestroy 注释,有关详细信息,请参阅JSR-250 注释

完全支持常规 Spring 生命周期回调。如果 bean 实现InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。

还完全支持*Aware接口的标准集合,例如实现 BeanFactoryAwareBeanNameAwareMessageSourceAware了 ApplicationContextAware等。

@Bean annotation 支持指定任意初始化和销毁回调方法,就像bean元素上 Spring XML 的init-methoddestroy-method属性一样:

public class Foo {

    public void init() {
        // initialization logic
    }
}

public class Bar {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

默认情况下,使用具有 public closeshutdown方法的 Java 配置定义的 beans 会自动使用销毁回调登记。如果您有一个公共closeshutdown方法,并且您不希望在容器关闭时调用它,只需将@Bean(destroyMethod="")添加到 bean 定义以禁用默认的(inferred)模式。

对于通过 JNDI 获取的资源,您可能希望默认执行此操作,因为其生命周期在 application 之外进行管理。特别是,确保始终为DataSource执行此操作,因为已知它在 Java EE application 服务器上存在问题。

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,使用@Bean方法,您通常会选择使用编程 JNDI 查找:使用 Spring 的JndiTemplate/JndiLocatorDelegate帮助程序或直接 JNDI InitialContext用法,但不能使用JndiObjectFactoryBean变体强制您将 return 类型声明为FactoryBean类型而不是实际的目标类型,使其更难用于其他@Bean方法中的 cross-reference calls,这些方法打算在这里引用所提供的资源。

当然,在上述Foo的情况下,在构造期间直接调用init()方法同样有效:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...
}

当您直接使用 Java 工作时,您可以使用 objects 执行任何您喜欢的操作,并且不必总是依赖于容器生命周期!

指定 bean 范围

使用 @Scope annotation

您可以指定使用@Bean annotation 定义的 beans 应具有特定范围。您可以使用Bean 范围部分中指定的任何标准范围。

默认范围是singleton,但您可以使用@Scope annotation 覆盖它:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
@Scope 和 scoped-proxy

Spring 提供了一种通过范围代理处理作用域依赖项的便捷方式。使用 XML configuration 时创建此类代理的最简单方法是<aop:scoped-proxy/>元素。使用 @Scope annotation 在 Java 中配置 beans 提供了与 proxyMode 属性的等效支持。默认值为无代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES

如果您使用 Java 从 XML reference 文档(参见前面的链接)port 使用 scoped proxy example 到_11_,它将如下所示:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

自定义 bean 命名

默认情况下,configuration classes 使用@Bean方法的 name 作为生成的 bean 的 name。但是,可以使用name属性覆盖此功能。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }
}

Bean 别名

第 7.3.1 节,“命名 beans”中所讨论的,有时需要给出一个 bean 多个名称,也称为 bean 别名。为此,@Bean annotation 的name属性接受 String array。

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

Bean 描述

有时提供 bean 的更详细的文本描述是有帮助的。当 beans 暴露(可能通过 JMX)用于监视目的时,这可能特别有用。

要向@Bean添加描述,可以使用@Description annotation:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

7.12.4 使用 @Configuration 注释

@Configuration是 class-level annotation,表示 object 是 bean 定义的来源。 @Configuration classes 通过 public @Bean annotated 方法声明 beans。 Calls 到@Configuration classes 上的@Bean方法也可用于定义 inter-bean 依赖项。有关一般介绍,请参阅第 7.12.1 节,“基本概念: @Bean 和@Configuration”

注入 inter-bean 依赖项

@Beans 彼此依赖时,表达该依赖关系就像让一个 bean 方法调用另一个一样简单:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

在上面的 example 中,foo bean 通过构造函数注入接收到bar的 reference。

这种声明 inter-bean 依赖项的方法仅在@Configuration class 中声明@Bean方法时才有效。您不能使用普通@Component classes 声明 inter-bean 依赖项。

查找方法注入

如前所述,查找方法注入是一种您应该很少使用的高级 feature。在 singleton-scoped bean 依赖于 prototype-scoped bean 的情况下,它很有用。使用 Java 进行此类型的 configuration 提供了实现此 pattern 的自然方法。

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

使用 Java-configuration 支持,您可以创建CommandManager的子类,其中抽象createCommand()方法被覆盖,以便查找新的(原型)命令 object:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

有关 Java-based configuration 如何在内部工作的更多信息

以下 example 显示了一个被调用两次的@Bean注释方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()clientService1()中被调用一次,在clientService2()中被调用一次。由于此方法创建ClientDaoImpl的新实例并返回它,因此通常需要 2 个实例(每个服务一个)。这肯定会有问题:在 Spring 中,实例化的 beans 默认具有singleton范围。这就是魔术所在:所有@Configuration class 都在 startup-time 的子类中使用CGLIB。在子类中,child 方法首先检查容器是否有任何缓存(作用域)beans,然后 calls parent 方法并创建一个新实例。请注意,从 Spring 3.2 开始,不再需要将 CGLIB 添加到 classpath,因为 CGLIB classes 已在org.springframework.cglib下重新打包并直接包含在 spring-core JAR 中。

根据 bean 的范围,行为可能会有所不同。我们在这里谈论单身人士。

由于 CGLIB 在 startup-time 处动态添加 features,因此存在一些限制,特别是 configuration classes 不能是 final。但是,从 4.3 开始,configuration classes 上允许使用任何构造函数,包括使用@Autowired或单个 non-default 构造函数声明进行默认注入。

如果您希望避免任何 CGLIB-imposed 限制,请考虑在非@Configuration classes,e.g 上声明@Bean方法。在普通的@Component classes 上。然后,@Bean方法之间的 Cross-method calls 将不会被截获,因此您必须完全依赖于构造函数或方法 level 的依赖注入。

7.12.5 编写 Java-based 配置

使用 @Import annotation

就像在 Spring XML files 中使用<import/>元素来帮助模块化配置一样,@Import annotation 允许从另一个 configuration class 中加载@Bean定义:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在,在实例化 context 时,不需要同时指定ConfigA.classConfigB.class,只需要显式提供ConfigB

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个 class,而不是要求开发人员在构造期间记住可能大量的@Configuration classes。

从 Spring Framework 4.2 开始,@Import也支持对常规 component classes 的 references,类似于AnnotationConfigApplicationContext.register方法。如果您想避免 component 扫描,使用一些 configuration classes 作为显式定义所有组件的入口点,这将非常有用。

注入导入的 @Bean 定义的依赖项

上面的示例有效,但很简单。在大多数实际场景中,beans 将在 configuration classes 之间相互依赖。使用 XML 时,这本身并不是问题,因为不涉及编译器,可以简单地声明ref="someBean"并相信 Spring 将在容器初始化期间将其解决。当然,当使用@Configuration classes 时,Java 编译器会在 configuration model 上设置约束,因为对其他 beans 的引用必须是有效的 Java 语法。

幸运的是,解决这个问题很简单。作为我们已经讨论过@Bean方法可以有任意数量的参数来描述 bean 依赖项。让我们考虑一个更多的 real-world 场景,其中有几个@Configuration classes,每个都取决于其他人声明的 beans:

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有另一种方法可以达到相同的效果。请记住,@Configuration classes 最终只是容器中的另一个 bean:这意味着他们可以像其他任何 bean 一样利用@Autowired@Value注入等!

确保您以这种方式注入的依赖项仅为最简单的依赖项。在 context 初始化期间很早就处理了@Configuration classes 并强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能采用 parameter-based 注入,如上面的 example 中所示。

另外,通过@Bean特别注意BeanPostProcessorBeanFactoryPostProcessor定义。那些应该通常被声明为static @Bean方法,而不是触发它们包含 configuration class 的实例化。否则,@Autowired@Value将无法在 configuration class 本身上运行,因为它太早被创建为 bean 实例。

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

仅在 Spring Framework 4.3 时支持@Configuration classes 中的构造函数注入。另请注意,如果 target bean 仅定义了一个构造函数,则无需指定@Autowired;在上面的 example 中,@Autowired构造函数不需要@Autowired

在上面的场景中,使用@Autowired可以很好地工作并提供所需的模块性,但确定声明自动装配的 bean 定义的确切位置仍然有些模棱两可。对于 example,作为一个查看ServiceConfig的开发人员,您如何确切地知道@Autowired AccountRepository bean 的声明位置?它在 code 中并不明确,这可能就好了。请记住,Spring 工具套件提供的工具可以呈现图表,显示所有内容的连线方式 - 这可能就是您所需要的。此外,您的 Java IDE 可以轻松找到AccountRepository类型的所有声明和用法,并将快速显示方法的位置 return 该类型。

如果这种歧义是不可接受的,并且您希望从 IDE 中直接从一个@Configuration class 导航到另一个@Configuration class,请考虑自动装配 configuration classes 本身:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

在上面的情况中,定义AccountRepository的地方是完全明确的。但是,ServiceConfig现在与RepositoryConfig紧密耦合;这是权衡。通过使用 interface-based 或 abstract class-based @Configuration classes,可以在某种程度上减轻这种紧密耦合。考虑以下:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在ServiceConfig与具体的DefaultRepositoryConfig松散耦合,而 built-in IDE 工具仍然有用:开发人员很容易获得RepositoryConfig __mplement 的类型层次结构。通过这种方式,导航@Configuration classes 及其依赖项与导航 interface-based code 的常用 process 没有什么不同。

如果您想影响某些 beans 的启动创建 order,请考虑将其中一些声明为@Lazy(在首次访问时创建而不是在启动时创建)或在某些其他 beans 上声明为@DependsOn(确保在之前创建特定的其他 beans 当前的 bean,超出后者直接依赖所暗示的含义。

有条件地包括 @Configuration classes 或 @Bean 方法

基于某个任意系统 state,有条件地启用或禁用完整的@Configuration class,甚至单独的@Bean方法通常很有用。一个 common example 是使用@Profile annotation 只有在 Spring Environment中启用了特定的 profile 时才激活_beof(详见第 7.13.1 节,“ Bean definition profiles”)。

@Profile annotation 实际上是使用一个名为@Conditional的更灵活的 annotation 实现的。 @Conditional annotation 指示在注册@Bean之前应查阅的特定org.springframework.context.annotation.Condition __mplement。

接口的实现仅提供返回truefalsematches(…)方法。对于 example,这是用于@Profile的实际Condition implementation:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

有关详细信息,请参阅@Conditional javadocs

结合 Java 和 XML configuration

Spring 的@Configuration class 支持并非旨在成为 Spring XML 的 100%完全替代品。某些工具(如 Spring XML 命名空间)仍然是配置容器的理想方式。在 XML 方便或必要的情况下,您可以选择:使用“XML-centric”方式实例化容器,使用 example,ClassPathXmlApplicationContext,或使用AnnotationConfigApplicationContext以“Java-centric”方式实例化容器,并根据需要使用@ImportResource annotation import XML。

XML-centric 使用 @Configuration classes

最好从 XML 引导 Spring 容器,并以 ad-hoc 方式包含@Configuration classes。例如,在使用 Spring XML 的大型现有代码库中,在 as-needed 基础上创建@Configuration classes 并将其包含在现有 XML files 中会更容易。您可以在下面找到在这种“XML-centric”情况下使用@Configuration classes 的选项。

请记住,@Configuration classes 最终只是容器中的 bean 定义。在这个 example 中,我们创建一个名为AppConfig@Configuration class,并将其作为<bean/>定义包含在system-test-config.xml中。因为<context:annotation-config/>已打开,所以容器将识别@Configuration annotation 并_正确处理AppConfig中声明的@Bean方法。

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

system-test-config.xml

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

在上面的system-test-config.xml中,AppConfig <bean/>不声明id元素。虽然这样做是可以接受的,但是没有其他 bean 会引用它,并且不太可能通过 name 从容器中显式获取它。与DataSource bean 类似 - 它只是按类型自动装配,因此不严格要求显式 bean id

因为@Configuration是带有@Component的 meta-annotated,@Configuration -annotated classes 自动成为 component 扫描的候选者。使用与上面相同的方案,我们可以重新定义system-test-config.xml以利用 component-scanning。请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>,因为<context:component-scan/>启用相同的功能。

system-test-config.xml

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Configuration class-centric 使用 @ImportResource 的 XML

在 application 中,@Configuration classes 是配置容器的主要机制,它仍然可能需要使用至少一些 XML。在这些场景中,只需使用@ImportResource并仅根据需要定义尽可能多的 XML。这样做可以实现配置容器的“Java-centric”方法,并将 XML 保持在最低限度。

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

7.13 环境抽象

环境是集成在容器中的抽象,它为 application 环境的两个 key 方面建模:profilesproperties

profile 是 bean 定义的命名逻辑 group,仅当给定的 profile 为 active 时才向容器注册。 Beans 可以分配给 profile,无论是用 XML 定义还是通过 annotations 定义。与 profiles 相关的Environment object 的作用是确定哪些 profiles(如果有)当前是 active,以及哪些 profiles(如果有)默认情况下应该 active。

Properties 在几乎所有 applications 中都发挥着重要作用,可能源自各种来源:properties files,JVM 系统 properties,系统环境变量,JNDI,servlet context 参数,ad-hoc Properties objects,Maps 等。与 properties 相关的Environment object 的作用是为用户提供方便的服务接口,用于配置 property 源和从中解析 properties。

7.13.1 Bean 定义 profiles

Bean 定义 profiles 是核心容器中的一种机制,允许在不同的环境中注册不同的 beans。环境这个词对不同的用户来说意味着不同的东西,这个 feature 可以帮助许多用例,包括:

  • 在开发中使用 in-memory 数据源,在 QA 或 production 中查找来自 JNDI 的相同数据源

  • 仅在将 application 部署到 performance 环境时注册监视基础结构

  • 为客户 A 和客户 B 部署注册 beans 的自定义 implementations

让我们考虑一个需要DataSource的实际 application 中的第一个用例。在测试环境中,configuration 可能如下所示:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

现在让我们考虑如何将此 application 部署到 QA 或 production 环境中,假设 application 的数据源将在 production application 服务器的 JNDI 目录中注册。我们的dataSource bean 现在看起来像这样:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

问题是如何根据当前环境在使用这两种变体之间切换。在 time 时,Spring 用户已经设计了许多方法来完成这项工作,通常依赖于系统环境变量和包含${placeholder}标记的 XML <import/> statements 的组合,这些标记根据环境变量的 value 解析为正确的 configuration 文件路径。 Bean 定义 profiles 是一个核心容器 feature,它提供了解决此问题的方法。

如果我们推广 environment-specific bean 定义之上的 example 用例,我们最终需要在某些上下文中注册某些 bean 定义,而不是在其他上下文中。你可以说你想在情况 A 中注册某个 profile 的 bean 定义,在情况 B 中注册一个不同的 profile。让我们首先看看如何更新我们的 configuration 以反映这种需求。

@Profile

@Profile annotation 允许您指示当一个或多个指定的 profiles 为 active 时,component 符合注册条件。使用上面的 example,我们可以 rewrite dataSource configuration 如下:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

如前所述,使用@Bean方法,您通常会选择使用编程式 JNDI 查找:使用 Spring 的JndiTemplate/JndiLocatorDelegate帮助程序或上面显示的直接 JNDI InitialContext用法,而不是JndiObjectFactoryBean变体会强制您将 return 类型声明为FactoryBean型。

可以用作meta-annotation,用于创建自定义组合注释。以下 example 定义了一个自定义@Production annotation,可用作@Profile("production")的 drop-in 替换:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

如果@Configuration class 标记为@Profile,则除非一个或多个指定的 profiles 为 active,否则将绕过与该 class 关联的所有@Bean方法和@Import _notnotations。如果或@Configuration class 标有@Profile({"p1", "p2"}),那么 class 将不是 registered/processed,除非 profiles'p1'and/or'p2'已被激活。如果给定的 profile 以 NOT operator(!)作为前缀,则如果 profile 不是 active,则将注册带注释的元素。例如,给定@Profile({"p1", "!p2"}),如果 profile'p1'为 active 或 profile'p2'不是 active,则会发生注册。

@Profile也可以在方法 level 中声明,以便只包含 configuration class,e.g 的一个特定 bean。对于特定 bean 的替代变体:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development")
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production")
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

使用@Profile on @Bean方法时,可能会应用特殊方案:对于同一 Java 方法 name 的重载@Bean方法(类似于构造函数重载),需要在所有重载方法上一致地声明@Profile条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,@Profile不能用于选择具有特定参数签名的重载方法而不是另一个;同一 bean 的所有工厂方法之间的分辨率遵循创建 time 时 Spring 的构造函数解析算法。

如果要使用不同的 profile 条件定义备用 beans,请使用通过@Bean name 属性指向相同 bean name 的不同 Java 方法名称,如上面的 example 所示。如果参数签名都是相同的(e.g. 所有变体都有 no-arg 工厂方法),这是首先在有效的 Java class 中表示这种排列的唯一方法(因为只有一个方法可以特别是 name 和参数签名)。

XML bean 定义 profiles

XML 对应物是<beans>元素的profile属性。我们上面的 sample configuration 可以在两个 XML files 中重写,如下所示:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

也可以避免在同一文件中拆分和嵌套<beans/>元素:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

spring-bean.xsd已被约束为仅允许此类元素作为文件中的最后一个元素。这应该有助于提供灵活性,而不会在 XML files 中引起混乱。

激活 profile

现在我们已经更新了 configuration,我们仍然需要指示 Spring profile 是 active。如果我们现在开始使用 sample application,我们会看到抛出NoSuchBeanDefinitionException,因为容器找不到名为dataSource的 Spring bean。

激活 profile 可以通过多种方式完成,但最简单的方法是以编程方式对Environment API 进行编程,该 API 可通过ApplicationContext获得:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

此外,profiles 也可以通过spring.profiles.active property 以声明方式激活,spring.profiles.active property 可以通过系统环境变量,web.xml中的 JVM 系统 properties,servlet context 参数指定,甚至可以作为 JNDI 中的条目(参见第 7.13.2 节,“PropertySource 抽象”)指定。在 integration 测试中,active profiles 可以通过spring-test模块中的@ActiveProfiles annotation 声明(参见名为“Context configuration with environment profiles”的部分)。

请注意,profiles 不是“either-or”命题;可以一次激活多个 profiles。以编程方式,只需为setActiveProfiles()方法提供多个 profile 名称,该方法接受String… varargs:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

声明性地,spring.profiles.active可以接受 comma-separated 列表的 profile 名称:

-Dspring.profiles.active="profile1,profile2"

默认 profile

默认的 profile 表示默认启用的 profile。考虑以下:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

如果没有 profile 是 active,则会创建上面的dataSource;这可以看作是为一个或多个 beans 提供默认定义的一种方法。如果启用了任何 profile,则默认的 profile 将不适用。

可以使用Environment上的setDefaultProfiles()或使用spring.profiles.default property 声明性地更改默认 profile 的 name。

7.13.2 PropertySource 抽象

Spring 的Environment抽象提供了 property 源的可配置层次结构上的搜索操作。要完整解释,请考虑以下事项:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,我们看到 high-level 方式询问 Spring 是否为当前环境定义了foo property。要回答这个问题,Environment object 会对一组PropertySource objects 执行搜索。 PropertySource是对 key-value 对的任何源的简单抽象,Spring 的StandardEnvironment配置有两个 PropertySource objects - 一个表示 JVM 系统 properties(一个 la System.getProperties())的集合,另一个表示系统环境变量集合(la System.getenv()) 。

这些默认的 property 源存在于StandardEnvironment,用于独立的 applications。 StandardServletEnvironment填充了其他默认的 property 源,包括 servlet config 和 servlet context 参数。 StandardPortletEnvironment同样可以访问 portlet config 和 portlet context 参数作为 property 源。两者都可以选择启用JndiPropertySource。有关详细信息,请参阅 javadocs。

具体地说,当使用StandardEnvironment时,如果在运行时存在foo system property 或foo环境变量,则的调用将 return true。

执行的搜索是分层的。默认情况下,system properties 优先于环境变量,因此如果在调用env.getProperty("foo")期间恰好在两个位置都设置了foo property,则系统 property value 将“win”并优先返回环境变量。请注意,property 值不会被合并,而是被前面的条目完全覆盖。

对于 common StandardServletEnvironment,完整层次结构如下所示,顶部有 highest-precedence 个条目:

  • ServletConfig 参数(如果适用,e.g. 在DispatcherServlet context 的情况下)

  • ServletContext 参数(web.xml context-param 条目)

  • JNDI 环境变量(“java:comp/env/”条目)

  • JVM 系统 properties(“-D”command-line arguments)

  • JVM 系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许您有一个自定义的 properties 源,您希望将其集成到此搜索中。没问题 - 只需实现并实例化你自己的PropertySource并将其添加到当前EnvironmentPropertySources集合中:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的 code 中,MyPropertySource在搜索中添加了最高优先级。如果它包含foo property,它将在任何其他PropertySource中的任何foo property 之前检测并返回。 MutablePropertySources API 公开了许多方法,允许精确操作 property 源集。

7.13.3 @PropertySource

@PropertySource annotation 提供了一种方便的声明式机制,用于将PropertySource添加到 Spring 的Environment

给定包含 key/value 对testbean.name=myTestBean的文件“app.properties”,以下@Configuration class 使用@PropertySource,以便调用testBean.getName()将 return“myTestBean”。

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

存在于@PropertySource资源位置的任何${…}占位符将根据已针对环境注册的 property 源集合进行解析。例如:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设已经注册的 property 源中有一个“my.placeholder”,e.g. 系统 properties 或环境变量,占位符将被解析为相应的 value。如果不是,则“default/path”将用作默认值。如果未指定缺省值且无法解析 property,则将抛出IllegalArgumentException

根据 Java 8 约定,@PropertySource annotation 是可重复的。但是,所有这些@PropertySource 注解都需要在同一个 level 中声明:直接在 configuration class 上或在同一个 customannotation 中的 meta-annotations。不推荐混合使用直接注释和 meta-annotations,因为直接注释将有效地覆盖 meta-annotations。

7.13.4 statements 中的占位符解析

从历史上看,元素中占位符的 value 只能针对 JVM 系统 properties 或环境变量进行解析。情况不再如此。因为环境抽象集成在整个容器中,所以很容易通过它来解析占位符。这意味着您可以以任何您喜欢的方式配置解决方案 process:更改搜索系统 properties 和环境变量的优先级,或者完全删除它们;根据需要将自己的 property 源添加到混合中。

具体而言,无论customer property 的定义位置如何,以下语句都可以正常工作,因为它在Environment中可用 long:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

7.14 注册 LoadTimeWeaver

_Sp用于在 classes 加载到 Java 虚拟机(JVM)时动态转换 class。

要启用 load-time 编织,请将@EnableLoadTimeWeaving添加到@Configuration classes 中的一个:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者,对于 XML configuration,使用context:load-time-weaver元素:

<beans>
    <context:load-time-weaver/>
</beans>

一旦配置为ApplicationContextApplicationContext中的任何 bean 都可以实现LoadTimeWeaverAware,从而接收 load-time weaver 实例的 reference。这与Spring 的 JPA 支持结合使用特别有用,其中 load-time 编织可能是 JPA class 转换所必需的。有关更多详细信息,请参阅LocalContainerEntityManagerFactoryBean javadocs。有关 AspectJ load-time 编织的更多信息,请参阅第 11.8.4 节,“在 Spring Framework 中使用 AspectJ 编织 Load-time”

7.15 ApplicationContext 的其他功能

正如章节介绍中所讨论的,org.springframework.beans.factory包提供了管理和操作 beans 的基本功能,包括以编程方式。除了扩展其他接口以提供更多 application framework-oriented 样式的附加功能外,org.springframework.context包还添加了扩展BeanFactory接口的ApplicationContext接口。许多人以完全声明的方式使用ApplicationContext,甚至不以编程方式创建它,而是依靠支持 class(如ContextLoader)来自动实例化ApplicationContext作为 Java EE web application 的正常启动 process 的一部分。

为了在更多 framework-oriented 样式中增强BeanFactory功能,context 包还提供以下功能:

  • 通过MessageSource接口访问 i18n-style 中的消息。

  • 通过ResourceLoader接口访问 URL 和 files 等资源。

  • Event 发布到 beans 实现ApplicationListener接口,通过使用ApplicationEventPublisher接口。

  • Loading 多个(分层)上下文,允许每个上下文通过HierarchicalBeanFactory接口聚焦在一个特定层上,例如 application 的 web 层。

7.15.1 使用 MessageSource 进行国际化

ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化(i18n)功能。 Spring 还提供了接口HierarchicalMessageSource,它可以分层次地解析消息。这些接口共同为 Spring 效果消息解析奠定了基础。这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法。如果未找到指定 locale 的消息,则使用默认消息。传入的任何 arguments 都使用标准 library 提供的MessageFormat功能成为替换值。

  • String getMessage(String code, Object[] args, Locale loc):与前一个方法基本相同,但有一点不同:不能指定默认消息;如果找不到该消息,则抛出NoSuchMessageException

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有 properties 也包装在一个名为MessageSourceResolvable的 class 中,您可以使用此方法。

加载ApplicationContext时,它会自动搜索 context 中定义的MessageSource bean。 bean 必须具有 name messageSource。如果找到这样的 bean,则将前面方法的所有 calls 委托给消息源。如果没有找到消息源,则ApplicationContext会尝试查找包含 bean 且具有相同 name 的 parent。如果是,则使用 bean 作为MessageSource。如果ApplicationContext找不到任何消息源,则在 order 中实例化一个空DelegatingMessageSource,以便能够接受 calls 到上面定义的方法。

Spring 提供两个MessageSource implementations,ResourceBundleMessageSourceStaticMessageSource。两者都在 order 中实现HierarchicalMessageSource来执行嵌套消息传递。 StaticMessageSource很少使用,但提供了以编程方式向源添加消息。 ResourceBundleMessageSource显示在以下 example 中:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

在 example 中,假设您在 classpath 中定义了三个名为formatexceptionswindows的资源包。任何解决消息的请求都将以 JDK 标准的方式处理,通过 ResourceBundles 解析消息。出于 example 的目的,假设上述两个资源包 files 的内容是......

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

执行MessageSource功能的程序显示在下一个 example 中。请记住,所有ApplicationContext __mplement 都是MessageSource __mplementation,因此可以强制转换为MessageSource接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

上述程序产生的结果将是......

Alligators rock!

总而言之,MessageSource在名为beans.xml的文件中定义,该文件存在于 classpath 的根目录中。 messageSource bean 定义通过basenames property 引用了许多资源包。在列表中传递给basenames property 的三个 files 在 classpath 的根目录下以 files 形式存在,分别称为format.propertiesexceptions.propertieswindows.properties

下一个 example 显示传递给消息查找的 arguments;这些 arguments 将转换为 Strings 并插入到查找消息中的占位符中。

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.foo.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}

调用execute()方法得到的结果将是......

The userDao argument is required.

关于国际化(i18n),Spring 的各种MessageSource __mplement 遵循与标准 JDK ResourceBundle相同的 locale 解决方案和回退规则。简而言之,继续前面定义的 example messageSource,如果要解决针对 British(en-GB)locale 的消息,您将分别创建名为format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties的 files。

通常,locale 解析由 application 的周围环境管理。在此 example 中,将手动指定将解析(英国)消息的 locale。

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

上述程序 running 的结果输出将是......

Ebagum lad, the 'userDao' argument is required, I say, required.

您还可以使用MessageSourceAware接口获取已定义的任何MessageSource的 reference。实现MessageSourceAware接口的ApplicationContext中定义的任何 bean 在创建和配置 bean 时都会注入 application context 的MessageSource

作为ResourceBundleMessageSource的替代,Spring 提供ReloadableResourceBundleMessageSource class。此变体支持相同的包文件格式,但比基于标准 JDK 的ResourceBundleMessageSource implementation 更灵活。特别是,它允许从任何 Spring 资源位置(不仅仅是 classpath)读取 files,并支持 bundle property files 的热重新加载(同时有效地在它们之间缓存它们)。查看ReloadableResourceBundleMessageSource javadocs 了解详细信息。

7.15.2 标准和自定义 events

通过ApplicationEvent class 和ApplicationListener接口提供ApplicationContext中的 Event 处理。如果实现ApplicationListener接口的 bean 被部署到 context 中,那么每 time被发布到ApplicationContext,bean 被通知。从本质上讲,这是标准的 Observer 设计 pattern。

从 Spring 4.2 开始,event 基础结构得到了显着改进,并且提供了annotation-based model以及发布任意 event 的能力,这是一个不一定从ApplicationEvent延伸的 object。当这样的 object 发布时,我们将它包装在 event 中。

Spring 提供以下标准 events:

表格 1_.Built-in 活动

事件说明
ContextRefreshedEvent初始化或刷新时发布,例如,使用ConfigurableApplicationContext接口上的refresh()方法。这里的“已初始化”表示所有 beans 都已加载,post-processor beans 被检测并激活,单例为 pre-instantiated,ApplicationContextobject 可供使用。如果 context 尚未关闭_long,则可以多次触发刷新,前提是所选的ApplicationContext实际上支持这种“热”刷新。对于 example,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
ContextStartedEvent使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。这里的“已启动”意味着所有Lifecycle beans 都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动 beans,但它也可用于启动尚未为自动启动配置的组件,例如,尚未在初始化时启动的组件。
ContextStoppedEvent使用ConfigurableApplicationContext接口上的stop()方法在ApplicationContext停止时发布。这里的“停止”意味着所有Lifecycle beans 都会收到明确的停止信号。可以通过start()调用重新启动已停止的 context。
ContextClosedEventApplicationContext关闭时发布,使用ConfigurableApplicationContext接口上的close()方法。这里的“封闭”意味着所有 singleton beans 都被销毁了。封闭的 context 到达其生命的终点;它无法刷新或重新启动。
RequestHandledEventweb-specific event 告诉所有 beans 已经为 HTTP 请求提供服务。请求完成后发布此 event。此 event 仅适用于使用 Spring 的DispatcherServlet的 web applications。

您还可以创建和发布自己的自定义 events。这个 example 演示了一个简单的 class,它扩展了 Spring 的ApplicationEvent base class:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

要发布自定义ApplicationEvent,请在ApplicationEventPublisher上调用publishEvent()方法。通常,这是通过 creating class 实现ApplicationEventPublisherAware并将其注册为 Spring bean 来完成的。以下 example 演示了这样一个 class:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在 configuration time,Spring 容器将检测EmailService实现ApplicationEventPublisherAware并自动调用setApplicationEventPublisher()。实际上,传入的参数将是 Spring 容器本身;你只是通过ApplicationEventPublisher接口与 application context 进行交互。

要接收自定义ApplicationEvent,请创建一个实现ApplicationListener的 class 并将其注册为 Spring bean。以下 example 演示了这样一个 class:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意,ApplicationListener通常使用自定义 event 类型BlackListEvent进行参数化。这意味着onApplicationEvent()方法可以保持 type-safe,从而避免任何向下转换的需要。您可以根据需要注册尽可能多的 event listeners,但请注意,默认情况下 event listeners 会同步接收 events。这意味着publishEvent()方法会阻塞,直到所有 listeners 都已完成 event 的处理。这种同步和 single-threaded 方法的一个优点是,当 listener 接收 event 时,如果 transaction context 可用,它将在发布者的 transaction context 内运行。如果需要另一个 event 发布策略,请参阅 Spring 的ApplicationEventMulticaster接口的 javadoc。

以下 example 显示了用于注册和配置上述每个 classes 的 bean 定义:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>[emailprotected]</value>
            <value>[emailprotected]</value>
            <value>[emailprotected]</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="[emailprotected]"/>
</bean>

总而言之,当调用emailService bean 的sendEmail()方法时,如果有任何应该被列入黑名单的电子邮件,则会发布类型为BlackListEvent的自定义 event。 blackListNotifier bean 注册为ApplicationListener,因此接收BlackListEvent,此时它可以通知相关方。

Spring 的事件机制是为在同一 application context 中 Spring beans 之间的简单通信而设计的。但是,对于更复杂的企业集成需求,separately-maintained Spring Integration项目为构建 well-known Spring 编程模型的 building 轻量级pattern-oriented,event-driven 架构提供了完全支持。

Annotation-based event listeners

从 Spring 4.2 开始,event listener 可以通过EventListener annotation 在托管 bean 的任何公共方法上注册。 BlackListNotifier可以重写如下:

public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

正如您在上面所看到的,方法签名再次声明它侦听的 event 类型,但是这个 time 具有灵活的 name 并且没有实现特定的 listener 接口。 event 类型也可以通过泛型缩小为 long,因为实际 event 类型在其 implementation 层次结构中解析了泛型参数。

如果你的方法应该监听几个 events,或者你想要在没有参数的情况下定义它,那么 event type(s 也可以在 annotation 本身上指定:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

还可以通过 annotation 的condition属性添加额外的运行时过滤,该属性定义应该 match 以实际调用特定 event 的方法。

例如,如果 event 的content属性等于foo,则可以重写我们的通知程序以仅调用:

@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式都针对专用的 context 进行求值。下一个 table 列出 context 可用的项目,以便可以将它们用于条件 event 处理:

表格 1_.Event SpEL 可用元数据

名称地点描述
事件root object实际ApplicationEvent#root.event
Arguments arrayroot objectarguments(as array)用于调用目标#root.args[0]
参数 nameevaluation context任何方法 arguments 的 Name。如果由于某种原因名称不可用(e.g. 无调试信息),则参数名称也可在#a<#arg>下获得,其中#arg 代表参数索引(从 0 开始)。#blEvent#a0(也可以使用#p0#p<#arg>表示法作为别名)。

请注意,#root.event允许您访问底层 event,即使您的方法签名实际上引用了已发布的任意 object。

如果您需要发布 event 作为处理另一个的结果,只需将方法签名更改为 return 应该发布的 event,例如:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

异步 listeners不支持此 feature。

这个新方法将为上述方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果你需要发布几个 events,只需。

异步 Listeners

如果您想要一个特定的 listener 异步 process events,只需重用定期 @Async 支持

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

使用异步 events 时请注意以下限制:

  • 如果 event listener 抛出Exception,它将不会传播给调用者,请检查AsyncUncaughtExceptionHandler以获取更多详细信息。

  • 这样的 event listener 无法发送回复。如果您需要作为处理结果发送另一个 event,inject ApplicationEventPublisher手动发送 event。

Ordering listeners

如果需要在另一个之前调用 listener,只需将@Order annotation 添加到方法声明中:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

Generic events

您还可以使用泛型来进一步定义 event 的结构。考虑一个EntityCreatedEvent<T>,其中T是创建的实际实体的类型。您可以创建以下 listener 定义以仅接收EntityCreatedEventEntityCreatedEvent

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由于类型擦除,这只有在被触发的 event 解析 event listener 在其上过滤的泛型 parameter(s(类似于class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })时才有效。

在某些情况下,如果所有 events 遵循相同的结构(这应该是上面的 event 的情况),这可能变得相当繁琐。在这种情况下,您可以实现ResolvableTypeProvider来引导 framework 超出运行时环境提供的范围:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(),
                ResolvableType.forInstance(getSource()));
    }
}

这不仅适用于ApplicationEvent,而且适用于您作为 event 发送的任意 object。

7.15.3 方便地访问 low-level 资源

为了最佳地使用和理解 application 上下文,用户通常应该熟悉 Spring 的Resource抽象,如第 8 章,资源章节所述。

application context 是ResourceLoader,可用于加载ResourceResource本质上是 JDK class java.net.URL的更多 feature rich version,事实上,的_implement 实现了java.net.URL的实例。 Resource可以透明的方式从几乎任何位置获取 low-level 资源,包括 classpath,文件系统位置,任何可用标准 URL 描述的位置,以及其他一些变体。如果资源位置 string 是一个没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际的 application context 类型。

您可以配置部署到 application context 中的 bean 来实现特殊的回调接口ResourceLoaderAware,在初始化 time 时自动回调,application context 本身作为ResourceLoader传入。您还可以公开Resource类型的 properties,以用于访问静态资源;它们将像任何其他 properties 一样注入其中。您可以将Resource properties 指定为简单的 String paths,并依赖 context 自动注册的特殊 JavaBean PropertyEditor,以便在部署 bean 时将这些文本 strings 转换为实际的Resource object。

提供给ApplicationContext构造函数的位置路径或_path 实际上是资源 strings,并且以简单形式适当地处理特定的 context implementation。 ClassPathXmlApplicationContext将简单的位置路径视为 classpath 位置。您还可以使用带有特殊前缀的 location paths(resource strings)来强制从 classpath 或 URL 中加载定义,而不管实际的 context 类型如何。

7.15.4 web applications 的便捷 ApplicationContext 实例化

您可以使用 for example 以声明方式创建ApplicationContext实例。当然,您也可以使用ApplicationContext implementations 之一以编程方式创建ApplicationContext实例。

您可以使用ContextLoaderListener注册ApplicationContext,如下所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

listener 检查contextConfigLocation参数。如果参数不存在,则 listener 使用/WEB-INF/applicationContext.xml作为默认值。当参数确实存在时,listener 使用预定义的分隔符(逗号,分号和空格)分隔 String,并将值用作将搜索 application contexts 的位置。也支持 Ant-style 路径模式。名称以“Context.xml”结尾的所有 files 的示例为/WEB-INF/*Context.xml,驻留在“WEB-INF”目录中,/WEB-INF/**/*Context.xml表示“WEB-INF”的任何子目录中的所有此类 files。

7.15.5 将 Spring ApplicationContext 部署为 Java EE RAR 文件

可以将 Spring ApplicationContext 部署为 RAR 文件,将 context 及其所有必需的 bean classes 和 library JAR 封装在 Java EE RAR 部署单元中。这相当于引导一个独立的 ApplicationContext,它只是托管在 Java EE 环境中,能够访问 Java EE 服务器设施。 RAR 部署是部署无头 WAR 文件的场景的更自然的替代方案,实际上是一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中引导 Spring ApplicationContext。

RAR 部署非常适用于不需要 HTTP 入口点但仅包含消息 endpoints 和预定作业的 application 上下文。 Beans 在这样的 context 中可以使用 application 服务器资源,例如 JTA transaction manager 和 JNDI-bound JDBC DataSources 和 JMS ConnectionFactory 实例,也可以通过 Spring 的标准 transaction management 以及 JNDI 和 JMX 支持工具注册平台的 JMX 服务器。 Application 组件还可以通过 Spring 的TaskExecutor抽象与 application 服务器的 JCA WorkManager 进行交互。

查看SpringContextResourceAdapter class 的 javadoc 以获取 RAR 部署中涉及的 configuration 详细信息。

对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:将所有 application classes 打包到 RAR 文件中,该文件是具有不同文件扩展名的标准 JAR 文件。将所有必需的 library JAR 添加到 RAR 存档的根目录中。添加“META-INF/ra.xml”部署描述符(如SpringContextResourceAdapter s javadoc 中所示)和相应的 Spring XML bean definition file(s(通常为“META-INF/applicationContext.xml”),并将生成的 RAR 文件放入 application 服务器的部署目录中。

这种 RAR 部署单位通常是 self-contained;它们不会将组件暴露给外部世界,甚至不会暴露给同一个应用程序的其他模块。与 RAR-based ApplicationContext 的交互通常通过与其他模块共享的 JMS 目标进行。例如,RAR-based ApplicationContext 也可以调度一些作业,对文件系统中的新文件作出反应(或类似)。如果需要允许来自外部的同步访问,则可以 example export RMI endpoints,当然可以由同一台机器上的其他 application 模块使用。

7.16 BeanFactory

BeanFactory API 为 Spring 的 IoC 功能提供了基础。它的特定 contracts 主要用于与 Spring 和相关 third-party 框架的其他部分进行整合,其DefaultListableBeanFactory implementation 是 higher-level GenericApplicationContext容器中的 key 委托。

BeanFactory和相关接口(如BeanFactoryAwareInitializingBeanDisposableBean)是其他 framework 组件的重要集成点:不需要任何注释甚至反射,它们允许容器与其组件之间的非常有效的交互。 Application-level beans 可以使用相同的回调接口,但通常更喜欢通过 annotations 或通过编程 configuration 进行声明性依赖注入。

请注意,核心BeanFactory API level 及其DefaultListableBeanFactory implementation 不会对 configuration 格式或要使用的任何 component annotations 进行假设。所有这些风格都通过诸如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor之类的 extensions 进行,在共享BeanDefinition objects 上作为核心元数据表示进行操作。这就是 Spring 容器如此灵活和可扩展的本质。

以下部分解释了BeanFactoryApplicationContext容器级别之间的差异以及对引导的影响。

7.16.1 BeanFactory 或 ApplicationContext?

使用ApplicationContext,除非你有充分的理由不这样做,GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的 common implementations。这些是 Spring 的核心容器的主要入口点,适用于所有 common 目的:配置 files,触发 classpath 扫描,以编程方式注册 bean 定义和带注释的 classes。

因为ApplicationContext包含BeanFactory的所有功能,所以通常建议使用BeanFactory,除了需要完全控制 bean 处理的场景。在诸如GenericApplicationContext implementation 之类的ApplicationContext中,将按惯例检测到几种 beans(i.e.由 bean name 或 bean 类型),特别是 post-processors,而普通的DefaultListableBeanFactory与任何特殊的 beans 无关。

对于许多扩展容器 features,例如 annotation 处理和 AOP 代理,BeanPostProcessor 扩展点是必不可少的。如果仅使用普通DefaultListableBeanFactory,则默认情况下不会检测到并激活此类 post-processors。这种情况可能令人困惑,因为你的 bean configuration 没有任何问题;它更像是在这种情况下需要通过附加设置完全自举的容器。

以下 table lists features 由BeanFactoryApplicationContext接口和 implementations 提供。

表格 1_.Feature Matrix

特征BeanFactoryApplicationContext
Bean instantiation/wiring
集成生命周期管理没有
自动BeanPostProcessor注册没有
自动BeanFactoryPostProcessor注册没有
方便的MessageSource访问(用于内化)没有
Built-in ApplicationEvent出版机制没有

要使用DefaultListableBeanFactory显式注册 bean post-processor,您需要以编程方式调用addBeanPostProcessor

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory

要将BeanFactoryPostProcessor应用于普通DefaultListableBeanFactory,您需要调用其postProcessBeanFactory方法:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都不方便,这就是为什么各种ApplicationContext变体比 Spring-backed applications 中的普通DefaultListableBeanFactory更受欢迎,尤其是在典型企业设置中依赖BeanFactoryPostProcessor s 和BeanPostProcessor s 来扩展容器功能时。

AnnotationConfigApplicationContext已经注册了所有 common annotation post-processors,并且可以通过 configuration annotations(如@EnableTransactionManagement)在封面下方引入其他处理器。在 Spring 的 annotation-based configuration model 的抽象 level 中, bean post-processors 的概念变成了仅仅是内部容器细节。

7.16.2 Glue code 和邪恶 singleton

最好以 dependency-injection(DI)样式编写大多数 application code,其中 code 由 Spring IoC 容器提供,在创建容器时由容器提供自己的依赖项,并且完全不知道容器。但是,对于有时需要将其他 code 绑定在一起的 code 的小胶层,有时需要 singleton(或 quasi-singleton)样式访问 Spring IoC 容器。对于 example,third-party code 可能会尝试直接构造新的 objects(Class.forName() style),而无法从 Spring IoC container.If 中获取这些 objects @ @ @ @ 访问 Spring IoC 容器以获得一个真正的 object 委托,然后仍然实现了大部分 code(object 从容器中出来)的控制反转。因此,大多数 code 仍然没有意识到容器或它是如何被访问的,并且仍然与其他 code 解耦,带来所有后续的好处。 EJB 也可以使用这种 stub/proxy 方法委托从 Spring IoC 容器中检索的普通 Java implementation object。虽然 Spring IoC 容器本身在理想情况下不必是 singleton,但在 memory 使用或初始化时间(在 Spring IoC 容器中使用 beans,例如 Hibernate SessionFactory)使每个 bean 使用它自己的时候,可能是不现实的, non-singleton Spring IoC 容器。

在服务定位器样式中查找 application context 有时是访问共享 Spring-managed 组件的唯一选项,例如在 EJB 2.1 环境中,或者当您希望跨 WAR files 将单个 ApplicationContext 作为 parent 共享到 WebApplicationContexts 时。在这种情况下,您应该考虑使用此Spring 团队博客文章中描述的实用程序 class ContextSingletonBeanFactoryLocator定位器。


[1]背景

[2]第 7.4.1 节,“依赖注入”

校对:
Updated at: 7 months ago
III. 核心技术Table of content8. 资源