7. IoC 容器

7.1 Spring IoC 容器和 bean 简介

本章介绍了控制反转(IoC)[1]原理的 Spring 框架实现。 IoC 也称为“依赖注入”(DI)。这是一个过程,在此过程中,对象仅通过构造函数参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系,即与它们一起使用的其他对象。然后,容器在创建 Bean 时“注入”那些依赖项。此过程从根本上来说是相反的,因此名称为控件反转(IoC),它是通过使用类的直接构造或* Service Locator *模式之类的机制来控制其依赖项的实例化或位置的。

org.springframework.beansorg.springframework.context软件包是 Spring Framework 的 IoC 容器的基础。 BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContextBeanFactory的子接口。它使与 Spring 的 AOP 功能的集成更加容易。消息资源处理(用于国际化),事件发布;以及特定于应用程序层的上下文,例如用于 Web 应用程序的WebApplicationContext

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。 ApplicationContextBeanFactory的完整超集,并且在本章中仅在 Spring 的 IoC 容器描述中使用。有关使用BeanFactory而不是ApplicationContext,的更多信息,请参考第 7.16 节“ BeanFactory”

在 Spring 中,构成应用程序主干并由 Spring IoC 容器Management 的对象称为* beans *。 Bean 是由 Spring IoC 容器实例化,组装和以其他方式 Management 的对象。否则,bean 仅仅是应用程序中许多对象之一。 Bean 及其之间的“依赖关系”反映在容器使用的“配置元数据”中。

7.2 容器概述

接口org.springframework.context.ApplicationContext代表 Spring IoC 容器,并负责实例化,配置和组装上述 bean。容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以 XML,Java 注解或 Java 代码表示。它使您能够表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。

Spring 提供了ApplicationContext接口的几种实现方式。在独立应用程序中,通常创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。尽管 XML 是定义配置元数据的传统格式,但是您可以通过提供少量 XML 配置来声明性地支持这些附加元数据格式,从而指示容器使用 JavaComments 或代码作为元数据格式。

在大多数应用场景中,不需要实例化用户代码即可实例化一个 Spring IoC 容器的一个或多个实例。例如,在 Web 应用程序场景中,应用程序web.xml文件中的简单八行(约)样板 WebDescriptorsXML 通常就足够了(请参见第 7.15.4 节“ Web 应用程序的便捷 ApplicationContext 实例化”)。如果您使用的是Spring 工具套件 Eclipse 驱动的开发环境,则只需单击几下鼠标或击键即可轻松创建此样板配置。

下图是 Spring 工作方式的高级视图。您的应用程序类与配置元数据结合在一起,以便在创建和初始化ApplicationContext之后,您将具有完全配置且可执行的系统或应用程序。

图 7.1. Spring IoC 容器

container magic

7.2.1 配置元数据

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

传统上,配置元数据以简单直观的 XML 格式提供,这是本章大部分内容用来传达 Spring IoC 容器的关键概念和功能的内容。

Note

基于 XML 的元数据不是唯一允许的配置元数据形式。 Spring IoC 容器本身“完全”与实际写入此配置元数据的格式脱钩。如今,许多开发人员为他们的 Spring 应用程序选择Java-based configuration

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

  • Annotation-based configuration:Spring 2.5 引入了对基于 Comments 的配置元数据的支持。

  • Java-based configuration:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能成为了核心 Spring Framework 的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参见@Configuration@Bean@Import@DependsOn注解。

Spring 配置由容器必须 Management 的至少一个(通常是一个以上)bean 定义组成。基于 XML 的配置元数据显示了在<beans/>顶级元素中配置为<bean/>元素的这些 bean。 Java 配置通常在@Configuration类中使用@BeanComments 的方法。

这些 bean 定义对应于组成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),表示对象(例如 Struts Action实例),基础结构对象(例如 Hibernate SessionFactories,JMS Queues等)。通常,不在容器中配置细粒度的域对象,因为创建和加载域对象通常是 DAO 和业务逻辑的职责。但是,您可以使用 Spring 与 AspectJ 的集成来配置在 IoC 容器的控制范围之外创建的对象。参见使用 AspectJ 与 Spring 依赖注入域对象

以下示例显示了基于 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="..." 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属性是用于标识单个 bean 定义的字符串。 class属性定义 Bean 的类型,并使用完全限定的类名。 id 属性的值是指协作对象。在此示例中未显示用于引用协作对象的 XML。有关更多信息,请参见Dependencies

7.2.2 实例化容器

实例化 Spring IoC 容器很简单。提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,它们使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH等)加载配置元数据。

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

Note

在了解了 Spring 的 IoC 容器之后,您可能想了解有关 Spring 的Resource抽象的信息,如第 8 章,资源所述,它提供了一种方便的机制,用于从 URI 语法中定义的位置读取 InputStream。特别地,Resource路径用于构建第 8.7 节“应用程序上下文和资源路径”中描述的应用程序上下文。

以下示例显示了服务层对象(services.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">

    <!-- 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>

以下示例显示了数据访问对象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>

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

编写基于 XML 的配置元数据

使 bean 定义跨越多个 XML 文件可能很有用。通常,每个单独的 XML 配置文件都代表体系结构中的逻辑层或模块。

您可以使用应用程序上下文构造函数从所有这些 XML 片段中加载 bean 定义。如上一节中所示,此构造函数具有多个Resource位置。或者,使用一个或多个<import/>元素从另一个文件中加载 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>

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

Note

可以但不建议使用相对的“ ../”路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖关系。特别是,不建议在“Classpath:” URL(例如,“Classpath:../ services.xml”)中使用此引用,在这些 URL 中,运行时解析过程会选择“最近”Classpath 根,然后查看其父目录。Classpath 配置的更改可能导致选择其他错误的目录。

您始终可以使用标准资源位置而不是相对路径:例如,“ file:C:/config/services.xml”或“ classpath:/config/services.xml”。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常,最好为此类绝对位置保留一个间接寻址,例如,通过在运行时针对 JVM 系统属性解析的“ ${…}”占位符。

import 伪指令是 bean 名称空间本身提供的功能。 Spring 提供的一系列 XML 名称空间提供了除普通 bean 定义之外的其他配置功能,例如“上下文”和“ util”命名空间。

Groovy Bean 定义 DSL

作为外部化配置元数据的另一个示例,Bean 定义也可以在 Spring 的 Groovy Bean 定义 DSL 中表达,如 Grails 框架所知。通常,此类配置将存在于具有以下结构的“ .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
        }
    }
}

这种配置样式在很大程度上等同于 XML bean 定义,甚至支持 Spring 的 XML 配置名称空间。它还允许通过“ importBeans”指令导入 XML bean 定义文件。

7.2.3 使用容器

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

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 配置时,引导看起来非常相似,只是一个不同的上下文实现类,该类可以识别 Groovy(但也了解 XML Bean 定义):

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

最灵活的变体是GenericApplicationContext与 Reader 代表结合使用,例如带有XmlBeanDefinitionReader的 XML 文件:

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

或使用GroovyBeanDefinitionReader用于 Groovy 文件:

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

如果需要,可以在相同的ApplicationContext上混合和匹配此类读取器委托,以从不同的配置源读取 bean 定义。

然后,您可以使用getBean来检索 bean 的实例。 ApplicationContext接口还有其他几种检索 bean 的方法,但是理想情况下,您的应用程序代码永远不要使用它们。确实,您的应用程序代码根本不应该调用getBean()方法,因此完全不依赖 Spring API。例如,Spring 与 Web 框架的集成为各种 Web 框架组件(例如控制器和 JSFManagement 的 Bean)提供了依赖注入,从而允许您通过元数据(例如,自动装配 Comments)声明对特定 Bean 的依赖。

7.3 Bean 概述

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

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

  • *包限定的类名称:*通常是所定义的 Bean 的实际实现类。

  • Bean 行为配置元素,用于声明 Bean 在容器中的行为(作用域,生命周期回调等)。

  • 引用该 bean 完成其工作所需的其他 bean;这些引用也称为* collaborators dependencies *。

  • 要在新创建的对象中设置的其他配置设置,例如,在 Management 连接池的 bean 中使用的连接数,或该池的大小限制。

该元数据转换为构成每个 bean 定义的一组属性。

表 7.1. Bean 定义

PropertyExplained in…
class第 7.3.2 节“实例化 bean”
name第 7.3.1 节“命名 bean”
scope第 7.5 节“ Bean 范围”
constructor arguments第 7.4.1 节“依赖注入”
properties第 7.4.1 节“依赖注入”
autowiring mode第 7.4.5 节“自动装配合作者”
lazy-initialization mode第 7.4.4 节“延迟初始化的 bean”
initialization method称为“初始化回调”的部分
destruction method称为“销毁回调”的部分

除了包含有关如何创建特定 Bean 的信息的 Bean 定义外,ApplicationContext实现还允许用户注册在容器外部创建的现有对象。这是通过方法getBeanFactory()访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 BeanFactory 实现DefaultListableBeanFactoryDefaultListableBeanFactory通过registerSingleton(..)registerBeanDefinition(..)方法支持此注册。但是,典型的应用程序只能与通过元数据 bean 定义定义的 bean 一起使用。

Note

Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自省步骤中正确地推理它们。尽管在某种程度上支持覆盖现有元数据和现有单例实例,但未正式支持在运行时(与对工厂的实时访问同时)对新 bean 的注册,并且可能导致 bean 容器中的并发访问异常和/或状态不一致。

7.3.1 命名 bean

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

在基于 XML 的配置元数据中,您使用id和/或name属性来指定 Bean 标识符。 id属性允许您精确指定一个 ID。按照惯例,这些名称是字母数字(“ myBean”,“ fooService”等),但也可能包含特殊字符。如果要向 bean 引入其他别名,还可以在name属性中指定它们,并用逗号(,),分号(;)或空格分隔。作为历史记录,在 Spring 3.1 之前的版本中,id属性定义为xsd:ID类型,该类型限制了可能的字符。从 3.1 开始,它被定义为xsd:string类型。请注意,bean id的唯一性仍由容器强制执行,尽管不再由 XML 解析器执行。

您不需要提供 bean 的名称或 ID。如果未明确提供名称或 ID,则容器将为该 bean 生成一个唯一的名称。但是,如果要按名称引用该 bean,则必须通过使用ref元素或Service Locator样式查找来提供该名称。不提供名称的动机与使用inner beansautowiring collaborators有关。

Bean Naming Conventions

约定是在命名 bean 时将标准 Java 约定用于实例字段名称。也就是说,bean 名称以小写字母开头,并且此后以驼峰大小写。此类名称的示例将是(不带引号)'accountManager''accountService''userDao''loginController'等。

一致地命名 Bean 使您的配置更易于阅读和理解,如果您使用的是 Spring AOP,则在将建议应用于按名称相关的一组 Bean 时,它会很有帮助。

Note

通过在 Classpath 中进行组件扫描,Spring 遵循上述规则为未命名的组件生成 Bean 名称:从本质上讲,采用简单的类名称并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与java.beans.Introspector.decapitalize(Spring 在此处使用的)定义的规则相同。

在 bean 定义之外别名一个 bean

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

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

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

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

例如,子系统 A 的配置元数据可以通过名称subsystemA-dataSource引用数据源。子系统 B 的配置元数据可以通过名称subsystemB-dataSource引用数据源。组成使用这两个子系统的主应用程序时,主应用程序使用myApp-dataSource的名称引用数据源。要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:

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

现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并且可以保证不与任何其他定义冲突(有效地创建名称空间),但是它们引用的是同一 bean。

Java-configuration

如果使用 Java 配置,则可以使用@Bean注解提供别名,有关详细信息,请参见第 7.12.3 节“使用@Bean 注解”

7.3.2 实例化 bean

Bean 定义本质上是创建一个或多个对象的方法。询问时,容器将查看命名 bean 的配方,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。

如果使用基于 XML 的配置元数据,则可以在<bean/>元素的class属性中指定要实例化的对象的类型(或类)。通常,此class属性(在内部是BeanDefinition实例上的Class属性)是必需的。 (有关异常,请参阅名为“使用实例工厂方法实例化”的部分第 7.7 节“ Bean 定义继承”。)您可以通过以下两种方式之一使用Class属性:

  • 通常,在容器本身通过反射性地调用其构造函数直接创建 Bean 的情况下,指定要构造的 Bean 类,这在某种程度上等效于使用new运算符的 Java 代码。

  • 要指定包含将被调用以创建对象的static工厂方法的实际类,在不太常见的情况下,容器将在类上调用static * factory *方法来创建 Bean。从static工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。

Note

内部类名. 如果要为static嵌套类配置 bean 定义,则必须使用嵌套类的* binary *名称。

例如,如果您在com.example包中有一个名为Foo的类,并且此Foo类具有一个名为Barstatic嵌套类,则 Bean 定义上'class'属性的值将为…

com.example.Foo$Bar

请注意,名称中使用了$字符以将嵌套的类名与外部类名分开。

使用构造函数实例化

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

Spring IoC 容器几乎可以 Management 您想要 Management 的任何类。它不仅限于 Managementtrue 的 JavaBean。大多数 Spring 用户更喜欢实际的 JavaBean,它仅具有默认(无参数)构造函数,并具有根据容器中的属性建模的适当的 setter 和 getter。您还可以在容器中具有更多奇特的非 bean 样式类。例如,如果您需要使用绝对不符合 JavaBean 规范的旧式连接池,则 Spring 也可以对其进行 Management。

使用基于 XML 的配置元数据,您可以如下指定 bean 类:

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

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

有关向构造函数提供参数(如果需要)并在构造对象之后设置对象实例属性的机制的详细信息,请参见Injecting Dependencies

使用静态工厂方法实例化

定义使用静态工厂方法创建的 bean 时,可以使用class属性指定包含static工厂方法的类,并使用名为factory-method的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有稍后描述的可选参数)并返回一个活动对象,该对象随后将被视为通过构造函数创建的。这种 bean 定义的一种用法是在旧代码中调用static工厂。

以下 bean 定义指定将通过调用工厂方法来创建 bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此示例中,createInstance()方法必须是* static *方法。

<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;
    }
}

有关为工厂方法提供(可选)参数并在从工厂返回对象后设置对象实例属性的机制的详细信息,请参见依赖关系和配置详细

使用实例工厂方法实例化

与通过静态工厂方法实例化类似,使用实例工厂方法实例化从容器中调用现有 bean 的非静态方法以创建新 bean。要使用此机制,请将class属性留空,并在factory-bean属性中,在当前(或父级/祖先)容器中指定包含要创建该对象的实例方法的 Bean 的名称。使用factory-method属性设置工厂方法本身的名称。

<!-- 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;
    }
}

一个工厂类也可以容纳一个以上的工厂方法,如下所示:

<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)进行 Management 和配置。参见依赖关系和配置详细

Note

在 Spring 文档中,* factory bean *是指在 Spring 容器中配置的 Bean,它将通过instancestatic工厂方法创建对象。相反,FactoryBean(注意大小写)是指特定于 Spring 的FactoryBean

7.4 Dependencies

典型的企业应用程序不包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的 Bean 定义到实现对象协作以实现目标的完全实现的应用程序。

7.4.1 依赖注入

依赖注入(DI)是一个过程,在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造对象实例后在对象实例上设置的属性来定义其依赖关系,即与它们一起使用的其他对象或从工厂方法返回。然后,容器在创建 Bean 时“注入”那些依赖项。此过程从根本上讲是相反的,因此,通过使用类的直接构造或服务定位符模式,bean 本身控制其依赖项的实例化或位置的名称控制反转(IoC)。

使用 DI 原理,代码更简洁,当为对象提供依赖项时,去耦会更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。这样,您的类就变得更易于测试,尤其是当依赖项依赖于接口或抽象 Base Class 时,它们允许在单元测试中使用存根或模拟实现。

DI 存在两个主要变体基于构造函数的依赖注入基于 Setter 的依赖注入

基于构造函数的依赖项注入

基于容器的 DI 是通过容器调用具有多个参数(每个参数代表一个依赖项)的构造函数来完成的。调用带有特定参数的static工厂方法来构造 Bean 几乎是等效的,并且本次讨论将构造函数和static工厂方法的参数视为类似。以下示例显示了只能通过构造函数注入进行依赖项注入的类。注意,这个类没有什么特别的,它是一个 POJO,它不依赖于容器特定的接口,Base Class 或 Comments。

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 定义的构造函数参数中不存在潜在的歧义,则在实例化 Bean 时,在 Bean 定义中定义构造函数参数的 Sequences 就是将这些参数提供给适当的构造函数的 Sequences。考虑以下类别:

package x.y;

public class Foo {

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

假设BarBaz类没有通过继承关联,则不存在潜在的歧义。因此,以下配置可以正常工作,并且您无需在<constructor-arg/>元素中显式指定构造函数参数索引和/或类型。

<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 时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用简单类型(例如<value>true</value>)时,Spring 无法确定值的类型,因此在没有帮助的情况下无法按类型进行匹配。考虑以下类别:

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属性可以显式指定构造函数参数的索引。例如:

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

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

您还可以使用构造函数参数名称来消除歧义:

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

请记住,要开箱即用,必须在启用调试标志的情况下编译代码,以便 Spring 可以从构造函数中查找参数名称。如果您无法(或不想)使用调试标志来编译代码,则可以使用@ConstructorProperties JDKComments 显式命名构造函数参数。然后,该示例类必须如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

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

基于 Setter 的依赖项注入

基于 Setter 的 DI 是通过在调用无参数构造函数或无参数static工厂方法以实例化您的 bean 之后,在 bean 上调用 setter 方法来完成的。

以下示例显示了只能使用纯 setter 注入方式进行依赖项注入的类。此类是常规的 Java。它是一个 POJO,不依赖于特定于容器的接口,Base Class 或 Comments。

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支持其 Management 的 bean 的基于构造函数和基于 setter 的 DI。在已经通过构造函数方法注入了某些依赖项之后,它还支持基于 setter 的 DI。您可以以BeanDefinition的形式配置依赖项,并与PropertyEditor实例结合使用以将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户并不直接(即以编程方式)使用这些类,而是使用 XML bean定义,带 Comments 的组件(即以@Component@Controller等标记的类)或基于 Java 的@Configuration类中的@Bean方法。然后将这些源在内部转换为BeanDefinition的实例,并用于加载整个 Spring IoC 容器实例。

Constructor-based or setter-based DI?

由于您可以混合使用基于构造函数的 DI 和基于 setter 的 DI,因此,将构造函数用于强制依赖项和将 setter 方法或配置方法用于可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用@Required注解可用于使属性成为必需的依赖项。

Spring 团队通常提倡构造函数注入,因为它使人们能够将应用程序组件实现为不可变对象并确保所需的依赖项不是null。此外,构造函数注入的组件始终以完全初始化的状态返回到 Client 端(调用)代码。附带说明一下,大量的构造函数参数是“不好的代码味道”,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点分离问题。

Setter 注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是,setter 方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBeans进行 Management 是塞特注入的引人注目的用例。

使用最适合特定类的 DI 风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何 setter 方法,则构造函数注入可能是 DI 的唯一可用形式。

依赖关系解决流程

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

  • ApplicationContext用描述所有 bean 的配置元数据创建和初始化。可以通过 XML,Java 代码或 Comments 指定配置元数据。

  • 对于每个 bean,其依赖项都以属性,构造函数参数或 static-factory 方法的参数(如果您使用的是常规构造函数)的形式表示。这些依赖项将在“实际创建 bean”时提供给 bean。

  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。

  • 每个值为值的属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如intlongStringboolean等。

在创建容器时,Spring 容器会验证每个 bean 的配置。但是,直到实际创建 bean 之前,bean 属性本身才被设置。创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的 Bean。范围在第 7.5 节“ Bean 范围”中定义。否则,仅在请求时才创建 Bean。创建和分配 bean 的依赖关系及其依赖关系(依此类推)时,创建 bean 可能会导致创建一个 bean 图。请注意,这些依赖项之间的分辨率不匹配可能会显示得较晚,即在首次创建受影响的 bean 时。

Circular dependencies

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。

例如:A 类通过构造函数注入需要 B 类的实例,而 B 类通过构造函数注入需要 A 类的实例。如果为将类 A 和 B 相互注入而配置了 bean,则 Spring IoC 容器会在运行时检测到此循环引用,并抛出BeanCurrentlyInCreationException

一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用 setter 注入。换句话说,尽管不建议这样做,但是您可以使用 setter 注入配置循环依赖关系。

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

通常,您可以信任 Spring 做正确的事。它在容器加载时检测配置问题,例如对不存在的 Bean 的引用和循环依赖项。在实际创建 Bean 时,Spring 设置属性并尽可能晚地解决依赖关系。这意味着,如果创建对象或其依赖项之一存在问题,则正确加载的 Spring 容器以后可以在您请求对象时生成异常。例如,由于缺少属性或无效属性,bean 引发异常。某些配置问题的这种潜在的延迟可见性是为什么默认情况下ApplicationContext实现会实例化单例 bean。在实际需要它们之前,要花一些前期时间和内存来创建它们,您会在创建ApplicationContext时发现配置问题,而不是稍后。您仍然可以覆盖此默认行为,以便单例 bean 可以延迟初始化而不是预先初始化。

如果不存在循环依赖关系,则在将一个或多个协作 Bean 注入到从属 Bean 中时,将“全部”配置每个协作 Bean,然后再注入到从属 Bean 中。这意味着,如果 bean A 依赖于 bean B,则 Spring IoC 容器会在调用 bean A 的 setter 方法之前完全配置 beanB。换句话说,bean 被实例化(如果不是预先实例化的单例),则它被实例化。设置了依赖项,并调用了相关的生命周期方法(例如配置的 init 方法InitializingBean 回调方法)。

依赖项注入示例

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。 Spring XML 配置文件的一小部分指定了一些 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;
    }
}

在前面的示例中,声明了 setter 以与 XML 文件中指定的属性匹配。以下示例使用基于构造函数的 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 定义中指定的构造函数参数将用作ExampleBean的构造函数的参数。

现在考虑该示例的一个变体,在该变体中,不使用构造函数,而是告诉 Spring 调用static工厂方法以返回对象的实例:

<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;
    }
}

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

7.4.2 详细的依赖关系和配置

如上一节所述,您可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用,也可以定义为内联定义的值。为此,Spring 的基于 XML 的配置元数据在其<property/><constructor-arg/>元素中支持子元素类型。

直值(基元,字符串等)

<property/>元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。 Spring 的conversion service用于将这些值从String转换为属性或参数的实际类型。

<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>

以下示例使用p-namespace进行更简洁的 XML 配置。

<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 更简洁。但是,除非在创建 bean 定义时使用支持自动属性完成的 IDE(例如IntelliJ IDEASpring 工具套件(STS)),否则在运行时而不是设计时会发现错别字。强烈建议您使用此类 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元素只是将容器中另一个 bean 的* id *(字符串值-不是引用)传递给<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标签可以使容器在部署时验证所引用的命名 Bean 实际上存在。在第二个变体中,不对传递给client bean 的targetName属性的值执行验证。仅在实际实例化client bean 时才发现拼写错误(极有可能导致致命的结果)。如果client bean 是prototype bean,则只能在部署容器很长时间之后发现此错字和所产生的异常。

Note

idref元素上的local属性在 4.0 bean xsd 中不再受支持,因为它不再提供常规bean引用上的值。升级到 4.0 模式时,只需将现有的idref local引用更改为idref bean即可。

<idref/>元素带来价值的一个常见地方(至少在 Spring 2.0 之前的版本中)是ProxyFactoryBean bean 定义中的AOP interceptors配置。指定拦截器名称时使用<idref/>元素可防止您拼写错误的拦截器 ID。

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

ref元素是<constructor-arg/><property/>定义元素内的最后一个元素。在这里,您将 bean 的指定属性的值设置为对容器 Management 的另一个 bean(协作者)的引用。引用的 bean 是其属性将被设置的 bean 的依赖项,并且在设置属性之前根据需要对其进行初始化。 (如果协作者是单例 bean,则它可能已经由容器初始化了.)所有引用最终都是对另一个对象的引用。范围和验证取决于您是否通过beanlocal,parent属性指定另一个对象的 ID /名称。

通过<ref/>标签的bean属性指定目标 bean 是最通用的形式,并且允许创建对同一容器或父容器中任何 bean 的引用,而不管它是否在同一 XML 文件中。 bean属性的值可以与目标 bean 的id属性相同,也可以与目标 bean 的name属性中的值之一相同。

<ref bean="someBean"/>

通过parent属性指定目标 bean 将创建对当前容器的父容器中的 bean 的引用。 parent属性的值可以与目标 Bean 的id属性相同,也可以与目标 Bean 的name属性中的值相同,并且目标 Bean 必须位于当前容器的父容器中。主要在具有容器层次结构且要使用代理将现有 bean 封装在父容器中时使用此 bean 参考变量,该代理将具有与父 bean 相同的名称。

<!-- 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>

Note

ref元素上的local属性在 4.0 bean xsd 中不再受支持,因为它不再提供常规bean引用上的值。升级到 4.0 模式时,只需将现有的ref local引用更改为ref bean即可。

Inner beans

<property/><constructor-arg/>元素内的<bean/>元素定义了所谓的* 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 或名称;如果指定,则容器不使用该值作为标识符。容器在创建时也将忽略scope标志:内部 bean 始终都是匿名的,而总是使用外部 bean 创建。不能将内部 Bean 注入到协作 Bean 中,而不是将其注入到封闭的 Bean 中,或者独立访问它们。

作为特殊情况,可以从自定义范围接收破坏回调,例如对于单例 bean 中包含的请求范围的内部 bean:内部 bean 实例的创建将与其包含的 bean 绑定在一起,但是销毁回调使它可以参与请求范围的生命周期。这不是常见的情况。内部 bean 通常只共享其包含 bean 的作用域。

Collections

<list/><set/><map/><props/>元素中,分别设置 Java Collection类型ListSetMapProperties的属性和参数。

<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 键或值的值或设置值也可以是以下任意元素:

bean | ref | idref | list | set | map | props | value | null
Collection merging

Spring 容器还支持集合的“合并”。应用程序开发人员可以定义父样式的<list/><map/><set/><props/>元素,并使子样式的<list/><map/><set/><props/>元素继承并覆盖父集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素将覆盖父集合中指定的值。

关于合并的本节讨论了父子 bean 机制。不熟悉父级和子级 bean 定义的 Reader 可能希望在 continue 之前阅读relevant section.

下面的示例演示了集合合并:

<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属性的<props/>元素上使用了merge=true属性。当child bean 被容器解析并实例化时,生成的实例具有adminEmails Properties集合,其中包含子代adminEmails集合与父代adminEmails集合合并的结果。

[emailprotected]
[emailprotected]
[emailprotected]

Properties集合的值集继承了父<props/>的所有属性元素,而子support值的值覆盖父集合中的值。

此合并行为类似地适用于<list/><map/><set/>集合类型。在<list/>元素的特定情况下,将保留与List集合类型相关联的语义,即ordered值集合的概念;父级的值先于子级列表的所有值。对于MapSetProperties集合类型,不存在排序。因此,对于容器内部使用的关联MapSetProperties实现类型基础的集合类型,没有排序语义有效。

集合合并的限制

您不能合并不同的集合类型(例如MapList),并且如果尝试这样做,则会引发适当的Exceptionmerge属性必须在下面的继承的子定义中指定;在父集合定义上指定merge属性是多余的,不会导致所需的合并。

Strongly-typed collection

随着 Java 5 中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明Collection类型,使其只能包含String个元素(例如)。如果您正在使用 Spring 将强类型的Collection依赖注入到 bean 中,则可以利用 Spring 的类型转换支持,以便在将强类型的Collection实例的元素添加到适当的类型之前将其转换为适当的类型。 Collection

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属性时,可以通过反射获得有关强类型Map<String, Float>的元素类型的泛型信息。因此,Spring 的类型转换基础结构将各种值元素识别为Float类型,并将字符串值9.99, 2.753.99转换为实际的Float类型。

空字符串值和空字符串

Spring 将属性等的空参数视为空Strings。以下基于 XML 的配置元数据片段将 email 属性设置为空的String值(“”)。

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

前面的示例等效于以下 Java 代码:

exampleBean.setEmail("");

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

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

上面的配置等效于以下 Java 代码:

exampleBean.setEmail(null);

带 p-命名空间的 XML 快捷方式

p 名称空间使您可以使用bean元素的属性而不是嵌套的<property/>元素来描述属性值和/或协作 bean。

Spring 支持基于 XML Schema 定义的可扩展配置格式with namespaces。本章讨论的beans配置格式在 XML Schema 文档中定义。但是,p 命名空间未在 XSD 文件中定义,仅存在于 Spring 的核心中。

下面的示例显示两个解析为相同结果的 XML 代码段:第一个使用标准 XML 格式,第二个使用 p-命名空间。

<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>

该示例显示了 p 命名空间中的一个属性,该属性在 bean 定义中称为 email。这告诉 Spring 包含一个属性声明。如前所述,p 名称空间没有架构定义,因此可以将属性名称设置为属性名称。

下一个示例包括另外两个 bean 定义,它们都引用了另一个 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>

如您所见,此示例不仅包含使用 p-namespace 的属性值,而且还使用特殊格式来声明属性引用。第一个 bean 定义使用<property name="spouse" ref="jane"/>创建从 bean john到 Bean jane的引用,而第二个 bean 定义使用p:spouse-ref="jane"作为属性来执行完全相同的操作。在这种情况下,spouse是属性名称,而-ref部分表示这不是一个直接值,而是对另一个 bean 的引用。

Note

p 命名空间不如标准 XML 格式灵活。例如,用于声明属性引用的格式与以Ref结尾的属性发生冲突,而标准 XML 格式则没有。我们建议您谨慎选择方法,并与团队成员进行交流,以免产生同时使用这三种方法的 XML 文档。

具有 c-namespace 的 XML 快捷方式

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

让我们回顾一下称为“基于构造函数的依赖注入”部分中带有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 引用,跟踪-ref)以其名称设置构造函数参数。同样,即使它不是在 XSD 模式中定义的,也需要声明(但它存在于 Spring 内核中)。

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

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

Note

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

实际上,构造函数解析mechanism在匹配参数方面非常有效,因此,除非确实需要,否则我们建议在整个配置中使用名称符号。

复合属性名称

设置 bean 属性时,可以使用复合属性名称或嵌套属性名称,只要路径中除最终属性名称之外的所有组件都不是null即可。考虑以下 bean 定义。

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

foo bean 具有fred属性,该属性具有bob属性,该属性具有sammy属性,并且最终的sammy属性被设置为值123。为了使它起作用,在构造 bean 或抛出NullPointerException之后,foofred属性和fredbob属性一定不能为null

7.4.3 使用依赖

如果一个 bean 是另一个 bean 的依赖项,则通常意味着将一个 bean 设置为另一个 bean 的属性。通常,您可以使用基于 XML 的配置元数据中的<ref/> element完成此操作。但是,有时 bean 之间的依赖性不太直接。例如,需要触发类中的静态初始化器,例如数据库驱动程序注册。 depends-on属性可以在初始化使用此元素的 bean 之前显式强制初始化一个或多个 bean。以下示例使用depends-on属性表示对单个 bean 的依赖关系:

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

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

<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" />

Note

bean 定义中的depends-on属性既可以指定初始化时间依赖性,也可以仅在singleton bean 的情况下指定对应的销毁时间依赖性。与给定 bean 定义depends-on关系的从属 bean 首先被销毁,然后再销毁给定 bean 本身。因此,depends-on也可以控制关闭 Sequences。

7.4.4 延迟初始化的 bean

默认情况下,作为初始化过程的一部分,ApplicationContext实现会急于创建和配置所有singleton bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。如果此行为是“不希望的”,则可以通过将 bean 定义标记为延迟初始化来防止对 singleton bean 的预先实例化。延迟初始化的 bean 告诉 IoC 容器在首次请求时而不是在启动时创建一个 bean 实例。

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

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

如果ApplicationContext使用了前面的配置,则在启动ApplicationContext时不会急于预先实例化名为lazy的 bean,而会急切地预先not.lazy bean。

但是,当延迟初始化的 bean 是非延迟初始化的单例 bean 的依赖项时,ApplicationContext在启动时会创建延迟初始化的 bean,因为它必须满足单例的依赖关系。延迟初始化的 bean 被注入到其他未延迟初始化的单例 bean 中。

您还可以通过使用<beans/>元素上的default-lazy-init属性在容器级别控制延迟初始化。例如:

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

7.4.5 自动装配合作者

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

  • 自动装配可以大大减少指定属性或构造函数参数的需要。 (在此方面,其他机制(例如 Bean 模板在本章其他地方讨论)也很有价值。)

  • 随着对象的 Developing,自动装配可以更新配置。例如,如果您需要向类中添加一个依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,而不必担心当代码库变得更稳定时切换到显式接线的选择。

使用基于 XML 的配置元数据[2]时,可以使用<bean/>元素的autowire属性为 bean 定义指定自动装配模式。自动装配功能具有四种模式。您指定自动装配* per * bean,从而可以选择要自动装配的对象。

表 7.2. 自动装配模式

ModeExplanation
no(默认)无自动装配。 Bean 引用必须通过ref元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName按属性名称自动布线。 Spring 寻找与需要自动装配的属性同名的 bean。例如,如果将一个 bean 定义设置为按名称自动装配,并且包含一个* master 属性(即,它具有一个 setMaster(..)*方法),那么 Spring 将查找一个名为master的 bean 定义,并使用它来设置属性。
byType如果容器中恰好存在一个该属性类型的 bean,则允许自动装配该属性。如果存在多个错误,则会引发致命异常,这表明您可能不对该 bean 使用* byType *自动装配。如果没有匹配的 bean,则什么也没有发生。该属性未设置。
constructor与* byType *类似,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个 bean,则将引发致命错误。

使用* byType constructor 自动装配模式,您可以装配数组和类型集合。在这种情况下,将提供与预期类型匹配的容器中的所有自动装配线候选者,以满足相关性。如果期望的键类型为String,则可以自动为强类型的 Maps 连线。自动装配的 Maps 值将由与期望类型匹配的所有 bean 实例组成,并且 Maps 键将包含相应的 bean 名称。

您可以将自动装配行为与依赖检查结合起来,后者在自动装配完成后执行。

自动装配的局限性和缺点

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

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

  • propertyconstructor-arg设置中的显式依赖项始终会覆盖自动装配。您不能自动连接所谓的* simple *属性,例如基元,StringsClasses(以及此类简单属性的数组)。此限制是设计使然。

  • 自动装配不如显式接线精确。尽管如上表所述,Spring 会谨慎地避免猜测,以免产生可能导致意外结果的歧义,但不再显式地记录 SpringManagement 对象之间的关系。

  • 接线信息可能不适用于可能从 Spring 容器生成文档的工具。

  • 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组,集合或 Map,这不一定是问题。但是,对于期望单个值的依赖项,不会任意解决此歧义。如果没有唯一的 bean 定义可用,则引发异常。

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

  • 放弃自动布线,转而使用明确的布线。

  • 如下一节所述,通过将 Bean 定义的autowire-candidate属性设置为false来避免自动装配。

  • 通过将其<bean/>元素的primary属性设置为true,将单个 bean 定义指定为* primary *候选对象。

  • 第 7.9 节“基于 Comments 的容器配置”中所述,通过基于 Comments 的配置实现更细粒度的控件。

从自动装配中排除 bean

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

Note

autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响名称的显式引用,即使指定的 bean 未标记为自动装配候选,名称也将得到解析。因此,如果名称匹配,按名称自动装配仍将注入 Bean。

您还可以基于与 Bean 名称的模式匹配来限制自动装配候选。顶级<beans/>元素在其default-autowire-candidates属性内接受一个或多个模式。例如,要将自动装配候选状态限制为名称以* Repository 结尾的任何 bean,请提供 Repository 值。要提供多种模式,请在以逗号分隔的列表中定义它们。 Bean 定义autowire-candidate属性的truefalse的显式值始终优先,并且对于此类 Bean,模式匹配规则不适用。

这些技术对于您不希望通过自动装配将其注入其他 bean 的 bean 非常有用。这并不意味着排除的 bean 本身不能使用自动装配进行配置。相反,bean 本身不是自动装配其他 bean 的候选对象。

7.4.6 方法注入

在大多数应用场景中,容器中的大多数 bean 是singletons。当单例 Bean 需要与另一个单例 Bean 协作时,或者非单例 Bean 需要与另一个非单例 Bean 协作时,通常可以通过将一个 Bean 定义为另一个 Bean 的属性来处理依赖性。当 bean 的生命周期不同时会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,也许在 A 的每个方法调用上都使用。容器只创建一次单例 bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法为 bean A 提供一个新的 bean B 实例。

一个解决方案是放弃某些控制反转。您可以通过实现ApplicationContextAware接口来使 bean A 知道容器,并在每次 bean A 需要它时对容器进行 getBean(“ B”)调用询问(通常是新的)bean B 实例。以下是此方法的示例:

// 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;
    }
}

前面的内容是不理想的,因为业务代码知道并耦合到 Spring 框架。方法注入是 Spring IoC 容器的一项高级功能,它允许以干净的方式处理此用例。

Note

您可以在此博客条目中阅读有关方法注入动机的更多信息。

查找方法注入

查找方法注入是容器覆盖* container 受管 bean *上的方法的能力,以返回容器中另一个命名 bean 的查找结果。如上节所述,查找通常涉及原型 bean。 Spring 框架通过使用从 CGLIB 库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

Note

  • 为了使此动态子类起作用,Spring Bean 容器将子类化的类不能为final,并且要覆盖的方法也不能为final

  • 对具有abstract方法的类进行单元测试需要您自己对该类进行子类化,并提供abstract方法的存根实现。

  • 组件扫描也需要具体的方法,这需要具体的类别。

  • 另一个关键限制是,查找方法不适用于工厂方法,特别是不适用于配置类中的@Bean方法,因为在这种情况下容器不负责创建实例,因此无法在其上创建运行时生成的子类。苍蝇。

查看前面的代码片段中的CommandManager类,您会发现 Spring 容器将动态覆盖createCommand()方法的实现。您的CommandManager类将没有任何 Spring 依赖项,如在重做的示例中所示:

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 端类(在本例中为CommandManager)中,要注入的方法需要以下形式的签名:

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

如果方法是abstract,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。例如:

<!-- 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 的新实例,标识为* commandManager *的 bean 就会调用其自己的方法createCommand()。如果确实需要myCommand bean,则必须小心将其部署为原型。如果它是singleton,则每次都返回myCommand bean 的相同实例。

或者,在基于 Comments 的组件模型中,您可以通过@LookupComments 声明一个查找方法:

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:

public abstract class CommandManager {

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

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,您通常会使用具体的存根实现来声明此类带 Comments 的查找方法,以使其与 Spring 的组件扫描规则兼容,在默认情况下,抽象类将被忽略。在显式注册或显式导入的 Bean 类的情况下,此限制不适用。

Tip

访问范围不同的目标 bean 的另一种方法是ObjectFactory/Provider注入点。签出称为“范围 bean 作为依赖项”的部分

有兴趣的 Reader 还可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)。

任意方法替换

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

借助基于 XML 的配置元数据,您可以使用replaced-method元素将现有的方法实现替换为已部署的 Bean。考虑下面的类,该类带有一个我们要重写的方法 computeValue:

public class MyValueCalculator {

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

    // some other methods...
}

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

/**
 * 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 ...;
    }
}

用于部署原始类并指定方法覆盖的 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/>元素来指示要覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要对参数签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有都匹配java.lang.String

java.lang.String
String
Str

由于参数的数量通常足以区分每个可能的选择,因此该快捷方式通过允许您仅键入将与参数类型匹配的最短字符串,可以节省大量 Importing。

7.5 Bean 范围

创建 bean 定义时,创建一个* recipe *来创建该 bean 定义所定义的类的实际实例。 bean 定义是配方的想法很重要,因为它意味着与类一样,您可以从一个配方中创建许多对象实例。

您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的“作用域”。这种方法功能强大且灵活,因为您可以通过配置选择创建的对象范围,而不必在 Java 类级别上烘烤对象的范围。可以将 Bean 定义为部署在多个作用域之一中:即开即用,Spring 框架支持七个作用域,其中只有当您使用可识别 Web 的ApplicationContext时,五个作用域才可用。

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

表 7.3. Bean 作用域

ScopeDescription
singleton(默认值)每个 Spring IoC 容器将单个 bean 定义范围限制到单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期;也就是说,每个 HTTP 请求都有一个自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
globalSession将单个 bean 定义的作用域限定为全局 HTTP Session的生命周期。通常仅在 Portlet 上下文中使用时才有效。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

Note

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

7.5.1 单例作用域

仅 Management 一个 singleton bean 的一个* shared *实例,并且所有对具有与该 bean 定义匹配的 id 的 bean 的请求都会导致该特定的 bean 实例由 Spring 容器返回。

换句话说,当您定义一个 bean 定义并且其作用域为单例时,Spring IoC 容器会创建该 bean 定义定义的对象的“一个”实例。该单个实例存储在此类单例 bean 的缓存中,并且该命名 bean 的“所有后续请求和引用”返回缓存的对象。

singleton

Spring 的 Singleton bean 的概念与“ Gang of Four(GOF)模式”一书中定义的 Singleton 模式不同。 GoF Singleton 对对象的范围进行硬编码,以便每个 ClassLoader 可以创建一个,并且只能创建一个特定类的实例。最好将 Spring 单例的范围描述为每个容器和每个 bean 。这意味着,如果您在单个 Spring 容器中为特定类定义一个 bean,则 Spring 容器将创建该 bean 定义所定义的类的一个并且仅一个实例。 *单例作用域是 Spring *中的默认作用域。要将 bean 定义为 XML 中的单例,您可以编写例如:

<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 发出请求时,bean 部署的非单一原型范围都会导致创建一个新 bean 实例。也就是说,将 Bean 注入到另一个 Bean 中,或者您可以通过容器上的getBean()方法调用来请求它。通常,将原型作用域用于所有有状态 Bean,将单例作用域用于 StatelessBean。

下图说明了 Spring 原型范围。 数据访问对象(DAO)通常不配置为原型,因为典型的 DAO 不保留任何对话状态;对于该作者而言,重用单例图的核心更加容易.

prototype

以下示例将 bean 定义为 XML 原型:

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

与其他作用域相比,Spring 不 Management 原型 bean 的完整生命周期:容器实例化,配置或组装原型对象,然后将其交给 Client 端,而没有该原型实例的进一步记录。因此,尽管对所有对象都调用了“初始化”生命周期回调方法,而不管其作用域如何,但对于原型而言,不会调用已配置的“销毁”生命周期回调方法。Client 端代码必须清除原型作用域的对象,并释放原型 Bean 持有的昂贵资源。要使 Spring 容器释放由原型作用域的 bean 占用的资源,请尝试使用自定义bean post-processor,该自变量_1 包含对需要清理的 bean 的引用。

在某些方面,Spring 容器在原型作用域 bean 方面的角色是 Java new运算符的替代。超过该时间点的所有生命周期 Management 必须由 Client 端处理。 (有关 Spring 容器中 bean 的生命周期的详细信息,请参见第 7.6.1 节“生命周期回调”。)

7.5.3 具有原型 Bean 依赖项的 Singleton Bean

当您使用对原型 bean 有依赖性的单例作用域 Bean 时,请注意依赖性在实例化时已解决。因此,如果将依赖项原型的 bean 依赖项注入到单例范围的 bean 中,则将实例化新的原型 bean,然后将依赖项注入到单例 bean 中。原型实例是曾经提供给单例范围的 bean 的唯一实例。

但是,假设您希望单例作用域的 bean 在运行时重复获取原型作用域的 bean 的新实例。您不能将原型作用域的 bean 依赖项注入到您的单例 bean 中,因为当 Spring 容器实例化单例 bean 并解析和注入其依赖项时,该注入仅* once *发生一次。如果在运行时多次需要一个原型 bean 的新实例,请参见第 7.4.6 节“方法注入”

7.5.4 请求,会话,全局会话,应用程序和 WebSocket 范围

requestsessionglobalSessionapplicationwebsocket范围仅在使用网络感知的 Spring ApplicationContext实现(例如XmlWebApplicationContext)时可用。如果将这些作用域与常规的 Spring IoC 容器(例如ClassPathXmlApplicationContext)一起使用,则会抛出IllegalStateException,抱怨未知的 bean 作用域。

初始 Web 配置

为了在requestsessionglobalSessionapplicationwebsocket级别(网络范围的 Bean)上支持 Bean 的作用域,在定义 Bean 之前,需要一些较小的初始配置。 (对于标准范围singletonprototype不需要此初始设置.)

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

如果实际上是在 Spring DispatcherServletDispatcherPortlet处理的请求中访问 Spring Web MVC 中的作用域 Bean,则不需要特殊设置:DispatcherServletDispatcherPortlet已经公开了所有相关状态。

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

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

另外,如果您的监听器设置存在问题,请考虑使用 Spring 的RequestContextFilter。过滤器 Map 取决于周围的 Web 应用程序配置,因此您必须适当地对其进行更改。

<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 请求对象绑定到正在为该请求提供服务的Thread上。这使得在请求链和会话范围内的 Bean 可以在调用链的更下游使用。

Request scope

考虑以下 XML 配置来定义 bean:

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

Spring 容器通过为每个 HTTP 请求使用loginAction bean 定义来创建LoginAction bean 的新实例。也就是说,loginAction bean 的作用域是 HTTP 请求级别。您可以根据需要更改创建实例的内部状态,因为从相同的loginAction bean 定义创建的其他实例将看不到状态的这些更改;因此,您可以更改实例的内部状态。它们特定于单个请求。当请求完成处理时,将限制作用于该请求的 Bean。

使用 Comments 驱动的组件或 Java Config 时,可以使用@RequestScopeComments 将组件分配给request范围。

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

Session scope

考虑以下 XML 配置来定义 bean:

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

在单个 HTTP Session的生存期内,Spring 容器通过使用userPreferences bean 定义来创建UserPreferences bean 的新实例。换句话说,userPreferences bean 的作用域实际上是 HTTP Session级别。与request-scoped bean 一样,您可以根据需要任意更改所创建实例的内部状态,因为其他 HTTP Session实例(也使用从相同userPreferences bean 定义创建的实例)不会看到这些状态变化,因为它们特定于单个 HTTP Session。当最终丢弃 HTTP Session时,也将丢弃作用于该特定 HTTP Session的 bean。

使用 Comments 驱动的组件或 Java Config 时,可以使用@SessionScopeComments 将组件分配给session范围。

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

全局会话范围

考虑以下 bean 定义:

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

globalSession范围类似于标准 HTTP Session范围(described above),并且仅在基于 Portlet 的 Web 应用程序的上下文中适用。 Portlet 规范定义了全局Session的概念,该概念在构成单个 Portlet Web 应用程序的所有 Portlet 之间共享。在globalSession范围内定义的 Bean 的作用域(或绑定)于全局 portlet Session的生存期。

如果您编写标准的基于 Servlet 的 Web 应用程序,并且将一个或多个 bean 定义为具有globalSession范围,则将使用标准 HTTP Session范围,并且不会引发任何错误。

Application scope

考虑以下 XML 配置来定义 bean:

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

Spring 容器通过对整个 Web 应用程序使用appPreferences bean 定义来创建AppPreferences bean 的新实例。也就是说,appPreferences bean 的作用域为ServletContext级别,并存储为常规ServletContext属性。这有点类似于 Spring 单例 bean,但是有两个重要的区别:它是每个ServletContext而不是每个 Spring'ApplicationContext'(在任何给定的 Web 应用程序中可能都有多个),并且实际上是公开的,因此可见为ServletContext属性。

使用 Comments 驱动的组件或 Java Config 时,可以使用@ApplicationScopeComments 将组件分配给application范围。

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

范围 bean 作为依赖项

Spring IoC 容器不仅 Management 对象(bean)的实例化,而且还 Management 协作者(或依赖项)的连接。如果要将 HTTP 请求范围的 Bean 注入(例如)到另一个范围较长的 Bean 中,则可以选择注入 AOP 代理代替该范围的 Bean。也就是说,您需要注入一个代理对象,该对象公开与范围对象相同的公共接口,但也可以从相关范围(例如 HTTP 请求)中检索实际目标对象,并将方法调用委托给该真实对象。

Note

您还可以在范围为singleton的 bean 之间使用<aop:scoped-proxy/>,然后引用将通过可序列化的中间代理,因此可以在反序列化时重新获得目标单例 bean。

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

同样,作用域代理不是以生命周期安全的方式从较短的作用域访问 bean 的唯一方法。您也可以简单地将注入点(即构造器/ setter 参数或自动装配的字段)声明为ObjectFactory<MyTargetBean>,从而允许getObject()调用在需要时按需检索当前实例,而无需保留该实例或将其单独存储。

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

JSR-330 的这种变体称为Provider,每次检索尝试都使用Provider<MyTargetBean>声明和相应的get()调用。有关 JSR-330 总体的更多详细信息,请参见here

以下示例中的配置仅一行,但是了解其背后的“原因”和“方式”非常重要。

<?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>

要创建这样的代理,请将子<aop:scoped-proxy/>元素插入到作用域 bean 定义中(请参见名为“选择要创建的代理类型”的部分第 41 章,基于 XML Schema 的配置)。为什么在requestsessionglobalSession和自定义范围级别定义的 bean 定义需要<aop:scoped-proxy/>元素?让我们检查以下单例 bean 定义,并将其与您需要为上述范围定义的内容进行对比(请注意,下面的userPreferences bean 定义本身是* incomplete *)。

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

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

在前面的示例中,单例 bean userManager注入了对 HTTP Session范围的 bean userPreferences的引用。这里的重点是userManager bean 是单例的:每个容器将被“完全实例化”一次,并且它的依赖项(在这种情况下,仅一个userPreferences bean)也只会注入一次。这意味着userManager bean 将仅对完全相同的userPreferences对象进行操作,即最初注入该对象的对象。

这不是将寿命较短的作用域 bean 注入寿命较长的作用域 bean 时想要的行为,例如,将 HTTP Session范围的协作 bean 作为依赖项注入到 singleton bean 中。相反,您只需要一个userManager对象,并且在 HTTP Session的生命周期内,您需要一个特定于该 HTTP SessionuserPreferences对象。因此,容器创建了一个对象,该对象公开了与UserPreferences类完全相同的公共接口(理想情况下是 UserPreferences实例的对象),该对象可以从作用域机制(HTTP 请求,Session等)中获取实际的UserPreferences对象。容器将此代理对象注入到userManager bean 中,而后者不知道此UserPreferences引用是代理。在此示例中,当UserManager实例在注入依赖项的UserPreferences对象上调用方法时,实际上是在代理上调用方法。然后,代理从 HTTP Session(在这种情况下)获取实际UserPreferences对象,并将方法调用委托给检索到的实际UserPreferences对象。

因此,在将request-session-globalSession-scoped Bean 注入到协作对象中时,需要以下正确且完整的配置:

<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 的类代理。

Note

CGLIB 代理仅拦截公共方法调用!不要在这样的代理上调用非公共方法。它们不会被委派给实际的作用域目标对象。

另外,您可以通过将<aop:scoped-proxy/>元素的proxy-target-class属性的值指定为false来配置 Spring 容器为此类作用域的 bean 创建基于标准 JDK 接口的代理。使用基于 JDK 接口的代理意味着您无需在应用程序 Classpath 中使用其他库来实现这种代理。但是,这也意味着作用域 bean 的类必须实现至少一个接口,并且注入作用域 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>

有关选择基于类或基于接口的代理的更多详细信息,请参见第 11.6 节“代理机制”

7.5.5 自定义范围

bean 的作用域机制是可扩展的。您可以定义自己的范围,甚至重新定义现有范围,尽管后者被认为是不好的做法,并且您不能*覆盖内置的singletonprototype范围。

创建自定义范围

要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope接口,本节对此进行了介绍。有关如何实现自己的作用域的想法,请参见 Spring Framework 本身提供的Scope实现和Scope javadocs,它们详细说明了需要实现的方法。

Scope接口有四种方法可以从作用域中获取对象,将它们从作用域中删除,然后允许销毁它们。

以下方法从基础范围返回对象。例如,会话范围实现返回会话范围的 Bean(如果不存在,则该方法在将其绑定到会话以供将来参考之后,将返回该 Bean 的新实例)。

Object get(String name, ObjectFactory objectFactory)

以下方法将对象从基础范围中删除。例如,会话范围实现从基本会话中删除了会话范围的 Bean。应该返回该对象,但是如果找不到具有指定名称的对象,则可以返回 null。

Object remove(String name)

以下方法注册范围被销毁或范围中的指定对象销毁时范围应执行的回调。有关销毁回调的更多信息,请参考 javadocs 或 Spring 范围实现。

void registerDestructionCallback(String name, Runnable destructionCallback)

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

String getConversationId()

使用自定义范围

在编写并测试一个或多个自定义Scope实现之后,您需要使 Spring 容器意识到您的新作用域。以下方法是在 Spring 容器中注册新的Scope的中心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过 BeanFactory 属性随 Spring 附带的大多数具体ApplicationContext实现中使用。

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

假设您编写了自定义Scope实现,然后按如下所示进行注册。

Note

以下示例使用 Spring 附带的SimpleThreadScope,但默认情况下未注册。这些说明与您自己的自定义Scope实现相同。

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

然后,您创建符合自定义Scope范围规则的 bean 定义:

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

使用自定义Scope实现,您不仅可以通过程序注册该范围。您也可以使用CustomScopeConfigurer类声明性地进行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>

Note

当您将<aop:scoped-proxy/>放在FactoryBean实现中时,作用域是工厂 bean 本身,而不是getObject()返回的对象。

7.6 自定义 bean 的性质

7.6.1 生命周期回调

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

Tip

通常,在现代 Spring 应用程序中,JSR-250 @PostConstruct@PreDestroyComments 被认为是接收生命周期回调的最佳实践。使用这些 Comments 意味着您的 bean 没有耦合到特定于 Spring 的接口。有关详细信息,请参见第 7.9.8 节“ @PostConstruct 和@PreDestroy”

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

在内部,Spring 框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring 并不提供现成的功能,则您可以自己实现BeanPostProcessor。有关更多信息,请参见第 7.8 节“容器扩展点”

除了初始化和销毁回调,SpringManagement 的对象还可以实现Lifecycle接口,以便这些对象可以在容器自身生命周期的驱动下参与启动和关闭过程。

本节介绍了生命周期回调接口。

Initialization callbacks

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

void afterPropertiesSet() throws Exception;

建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到 Spring。或者,使用@PostConstruct注解或指定 POJO 初始化方法。对于基于 XML 的配置元数据,可以使用init-method属性来指定具有无效无参数签名的方法的名称。通过 Java config,您可以使用@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
    }
}

但不会将代码耦合到 Spring。

Destruction callbacks

实现org.springframework.beans.factory.DisposableBean接口后,当包含 bean 的容器被销毁时,bean 可以获取回调。 DisposableBean接口指定一个方法:

void destroy() throws Exception;

建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到 Spring。或者,使用@PreDestroy注解或指定 bean 定义支持的通用方法。对于基于 XML 的配置元数据,可以在<bean/>上使用destroy-method属性。通过 Java config,您可以使用@BeandestroyMethod属性,请参见名为“接收生命周期回调”的部分。例如,以下定义:

<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)
    }
}

但不会将代码耦合到 Spring。

Tip

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

默认的初始化和销毁方法

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

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

假设您的初始化回调方法名为init(),而销毁回调方法名为destroy()。您的类将与以下示例中的类相似。

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>

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

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

如果现有的 Bean 类已经具有按惯例命名的回调方法,则可以使用<bean/>本身的init-methoddestroy-method属性指定(在 XML 中)方法名称来覆盖默认值。

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

组合生命周期机制

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

Note

如果为 bean 配置了多个生命周期机制,并且为每种机制配置了不同的方法名称,则将按以下列出的 Sequences 执行每个已配置的方法。但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,对于初始化方法为init()),则该方法将执行一次,如上一节所述。

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

  • @PostConstructComments 的方法

  • InitializingBean回调接口定义的afterPropertiesSet()

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

销毁方法的调用 Sequences 相同:

  • @PreDestroyComments 的方法

  • DisposableBean回调接口定义的destroy()

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

启动和关闭回调

Lifecycle接口为具有生命周期要求的任何对象定义了基本方法(例如,启动和停止某些后台进程):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 SpringManagement 的对象都可以实现该接口。然后,当ApplicationContext本身接收到开始和停止 signal 时,例如对于运行时的停止/重新启动方案,它将把这些调用级联到在该上下文中定义的所有Lifecycle实现。它通过委派LifecycleProcessor来实现:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

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

Tip

请注意,常规org.springframework.context.Lifecycle接口只是用于显式启动/停止通知的简单约定,并不意味着在上下文刷新时自动启动。考虑实现org.springframework.context.SmartLifecycle,以便对特定 bean 的自动启动(包括启动阶段)进行细粒度的控制。另外,请注意,不能保证在销毁之前会发出停止通知:在常规关闭时,所有Lifecycle bean 都会在传播一般销毁回调之前首先收到停止通知;但是,在上下文生命周期中进行热刷新或中止刷新尝试时,仅将调用 destroy 方法。

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

public interface Phased {

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

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,相位最低的对象首先启动,而停止时,则遵循相反的 Sequences。因此,实现SmartLifecycle且其getPhase()方法返回Integer.MIN_VALUE的对象将是第一个启动且最后一个停止的对象。在频谱的另一端,相位值Integer.MAX_VALUE表示该对象应最后启动并首先停止(可能是因为它取决于正在运行的其他进程)。在考虑相位值时,知道未实现SmartLifecycle的任何“正常” Lifecycle对象的默认相位为 0 也很重要。因此,任何负相位值都将指示对象应在这些标准组件之前开始(并且停在它们之后),反之亦然。

如您所见,由SmartLifecycle定义的 stop 方法接受回调。在该实现的关闭过程完成之后,任何实现都必须(必须)调用该回调的run()方法。这将在必要时启用异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将 await 其超时值,以 await 每个阶段内的对象组调用该回调。默认的每阶段超时为 30 秒。您可以通过在上下文中定义一个名为“ lifecycleProcessor”的 bean 来覆盖默认的生命周期处理器实例。如果只想修改超时,则定义以下内容就足够了:

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

如上所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关机过程,就好像已明确调用stop()一样,但是它将在上下文关闭时发生。另一方面,“刷新”回调启用SmartLifecycle bean 的另一个功能。刷新上下文后(在所有对象都实例化和初始化之后),该回调将被调用,此时默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为“ true”,则该对象将在此时启动,而不是 await 上下文或其自己的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。 “相位”值以及任何“依赖”关系将以与上述相同的方式确定启动 Sequences。

在非 Web 应用程序中正常关闭 Spring IoC 容器

Note

本节仅适用于非 Web 应用程序。 Spring 的基于 Web 的ApplicationContext实现已经具有适当的代码,可以在相关 Web 应用程序关闭时正常关闭 Spring IoC 容器。

如果您在非 Web 应用程序环境中使用 Spring 的 IoC 容器;例如,在富 Client 端桌面环境中;您向 JVM 注册了一个关闭钩子。这样做可以确保正常关机,并在您的 Singleton bean 上调用相关的 destroy 方法,以便释放所有资源。当然,您仍然必须正确配置和实现这些 destroy 回调。

要注册关闭钩子,您可以调用在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接口的对象实例时,该实例将获得对该ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean 可以通过ApplicationContext接口,或者通过将引用转换为该接口的已知子类(例如ConfigurableApplicationContext),以编程方式操纵创建它们的ApplicationContext,从而公开了其他功能。一种用途是通过编程方式检索其他 bean。有时,此功能很有用;但是,通常应该避免使用它,因为它会将代码耦合到 Spring,并且不遵循“反转控制”样式,在这种样式中,将协作者作为属性提供给 bean。 ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件以及对MessageSource的访问。这些附加功能在第 7.15 节“ ApplicationContext 的附加功能”中进行了描述

从 Spring 2.5 开始,自动装配是获得对ApplicationContext的引用的另一种选择。 “传统” constructorbyType自动装配模式(如第 7.4.5 节“自动装配合作者”中所述)可以分别为构造函数参数或 setter 方法参数提供ApplicationContext类型的依赖项。为了获得更大的灵 Active,包括能够自动连接字段和使用多个参数方法,请使用基于 Comments 的新自动装配功能。如果这样做,则ApplicationContext将自动连接到期望使用ApplicationContext类型的字段,构造函数参数或方法参数中(如果有问题的字段,构造函数或方法带有@AutowiredComments)。有关更多信息,请参见第 7.9.2 节“ @Autowired”

ApplicationContext创建实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将获得对在其关联的对象定义中定义的名称的引用。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在填充常规 bean 属性之后但在初始化回调(例如InitializingBean * afterPropertiesSet *或自定义 init-method)之前调用该回调。

7.6.3 其他感知接口

除了上面讨论的ApplicationContextAwareBeanNameAware之外,Spring 还提供了一系列Aware接口,这些接口允许 Bean 向容器指示它们需要某种基础设施依赖性。下面总结了最重要的Aware接口-作为一般规则,该名称很好地表明了依赖项类型:

表 7.4. 感知接口

NameInjected DependencyExplained in…
ApplicationContextAware声明ApplicationContext第 7.6.2 节“ ApplicationContextAware 和 BeanNameAware”
ApplicationEventPublisherAware附件ApplicationContext的事件发布者第 7.15 节“ ApplicationContext 的附加功能”
BeanClassLoaderAware类加载器,用于加载 Bean 类。第 7.3.2 节“实例化 bean”
BeanFactoryAware声明BeanFactory第 7.6.2 节“ ApplicationContextAware 和 BeanNameAware”
BeanNameAware声明 bean 的名称第 7.6.2 节“ ApplicationContextAware 和 BeanNameAware”
BootstrapContextAware资源适配器BootstrapContext容器在其中运行。通常仅在支持 JCA 的ApplicationContext s 中可用第 32 章,JCA CCI
LoadTimeWeaverAware定义* weaver *以便在加载时处理类定义第 11.8.4 节“在 Spring 框架中使用 AspectJ 进行加载时编织”
MessageSourceAware解决消息的已配置策略(支持参数化和国际化)第 7.15 节“ ApplicationContext 的附加功能”
NotificationPublisherAwareSpring JMX 通知发布者第 31.7 节“通知”
PortletConfigAware当前PortletConfig容器在其中运行。仅在可感知网络的 Spring ApplicationContext中有效第 25 章,Portlet MVC 框架
PortletContextAware当前PortletContext容器在其中运行。仅在可感知网络的 Spring ApplicationContext中有效第 25 章,Portlet MVC 框架
ResourceLoaderAware配置的加载器,用于对资源的低级访问第 8 章,资源
ServletConfigAware当前ServletConfig容器在其中运行。仅在可感知网络的 Spring ApplicationContext中有效第 22 章,Web MVC 框架
ServletContextAware当前ServletContext容器在其中运行。仅在可感知网络的 Spring ApplicationContext中有效第 22 章,Web MVC 框架

再次注意,这些接口的使用将您的代码与 Spring API 绑定在一起,并且不遵循“控制反转”样式。因此,建议将它们用于需要以编程方式访问容器的基础结构 Bean。

7.7 Bean 定义继承

Bean 定义可以包含许多配置信息,包括构造函数参数,属性值以及特定于容器的信息,例如初始化方法,静态工厂方法名称等。子 bean 定义从父定义继承配置数据。子定义可以根据需要覆盖某些值,或添加其他值。使用父 bean 和子 bean 定义可以节省很多 Importing。实际上,这是一种模板形式。

如果您以编程方式使用ApplicationContext接口,则子 Bean 定义由ChildBeanDefinition类表示。大多数用户不在此级别上使用它们,而是使用ClassPathXmlApplicationContext之类的方式声明性地配置 bean 定义。当使用基于 XML 的配置元数据时,可以通过使用parent属性来指示子 bean 定义,并指定父 bean 作为此属性的值。

<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>

如果未指定子 bean 定义,则使用父定义中的 bean 类,但也可以覆盖它。在后一种情况下,子 bean 类必须与父类兼容,也就是说,它必须接受父类的属性值。

子 bean 定义从父对象继承范围,构造函数参数值,属性值和方法替代,并可以选择添加新值。您指定的任何范围,初始化方法,destroy 方法和/或static工厂方法设置都将覆盖相应的父设置。

其余设置总是从子定义中获取:取决于自动装配模式依赖检查单个惰性初始化

前面的示例通过使用abstract属性将父 bean 定义显式标记为抽象。如果父定义未指定类,则需要将父 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>

父 bean 不能单独实例化,因为它是不完整的,并且还被明确标记为abstract。当定义为abstract时,它只能用作纯模板 bean 定义,用作子定义的父定义。尝试单独使用这样的abstract父 bean,将其称为另一个 bean 的 ref 属性,或使用父 bean id 进行显式getBean()调用,都会返回错误。同样,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的 bean 定义。

Note

ApplicationContext默认情况下预先实例化所有单例。因此,重要的是(至少对于单例 bean),如果有一个(父)bean 定义仅打算用作模板,并且此定义指定了一个类,则必须确保设置* abstract 属性为 true *,否则应用程序上下文将实际(尝试)预先实例化abstract bean。

7.8 容器扩展点

通常,应用程序开发人员不需要将ApplicationContext实现类作为子类。相反,可以通过插入特殊集成接口的实现来扩展 Spring IoC 容器。接下来的几节描述了这些集成接口。

7.8.1 使用 BeanPostProcessor 自定义 bean

BeanPostProcessor接口定义了回调方法,您可以实施这些回调方法以提供自己的(或覆盖容器的默认值)实例化逻辑,依赖关系解析逻辑等。如果您想在 Spring 容器完成实例化,配置和初始化 bean 之后实现一些自定义逻辑,则可以插入一个或多个BeanPostProcessor实现。

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

Note

BeanPostProcessor对 bean(或对象)* instances 进行操作;也就是说,Spring IoC 容器实例化了一个 bean 实例,然后 then BeanPostProcessor s 进行工作。

BeanPostProcessor范围是每个容器。仅在使用容器层次结构时才有意义。如果在一个容器中定义BeanPostProcessor,它将后处理该容器中的 bean。换句话说,一个容器中定义的 Bean 不会被另一个容器中定义的BeanPostProcessor后处理,即使这两个容器是同一层次结构的一部分。

要更改实际的 bean 定义(即定义 bean 的* blueprint *),您需要使用第 7.8.2 节“使用 BeanFactoryPostProcessor 定制配置元数据”中描述的BeanFactoryPostProcessor

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

ApplicationContext 自动检测 *在配置元数据中定义的实现BeanPostProcessor接口的所有 bean。 ApplicationContext将这些 bean 注册为后处理器,以便稍后在 bean 创建时可以调用它们。 Bean 后处理器可以像其他任何 Bean 一样部署在容器中。

请注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,从而清楚地表明该 bean 的后处理器性质。否则,ApplicationContext将无法在完全创建之前按类型自动检测到它。由于BeanPostProcessor需要提早实例化以便应用于上下文中其他 bean 的初始化,因此这种提早类型检测至关重要。

Note

推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如上所述),但也可以使用addBeanPostProcessor方法针对ConfigurableBeanFactory以编程方式注册它们。当需要在注册之前评估条件逻辑时,甚至在跨层次结构的上下文中复制 bean 后处理器时,这将很有用。但是请注意,以编程方式添加的BeanPostProcessor不尊重Ordered接口*。在这里,“注册 Sequences”决定了执行 Sequences。还要注意,以编程方式注册的BeanPostProcessor总是在通过自动检测注册的BeanPostProcessor之前进行处理,而不考虑任何明确的 Sequences。

Note

实现BeanPostProcessor接口的类是* special *(特殊),并且容器对它们的处理方式有所不同。所有BeanPostProcessor和它们直接引用的 bean 在启动时都会实例化,作为ApplicationContext特殊启动阶段的一部分。接下来,以排序方式注册所有BeanPostProcessor,并将其应用于容器中的所有其他 bean。因为 AOP 自动代理是由BeanPostProcessor本身实现的,所以BeanPostProcessor或它们直接引用的 bean 都不适合进行自动代理,因此没有编织的方面。

对于任何此类 bean,您应该看到一条参考性日志消息:“ * Bean foo 不适合所有 BeanPostProcessor 接口处理(例如:不适合自动代理)*”。

请注意,如果您使用自动装配或@Resource(可能会退回到自动装配)将 Bean 连接到BeanPostProcessor中,则 Spring 在搜索类型匹配的依赖项候选对象时可能会访问意外的 Bean,因此使它们不符合自动代理或其他类型的条件。 Bean 后处理。例如,如果您有一个用@ResourceComments 的依赖项,其中字段/设置者名称不直接与 bean 的声明名称相对应,并且不使用 name 属性,那么 Spring 将访问其他 bean 以按类型匹配它们。

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

示例:Hello World,BeanPostProcessor 风格

第一个示例说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现调用容器创建的每个 bean 的toString()方法,并将结果字符串打印到系统控制台。

在自定义BeanPostProcessor实现类定义下面找到:

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是如何简单定义的。它甚至没有名称,因为它是 Bean,所以可以像其他任何 Bean 一样依赖注入。 (前面的配置还定义了一个由 Groovy 脚本支持的 bean.Spring 动态语言支持在标题为第 35 章,动态语言支持的一章中有详细介绍。)

以下简单的 Java 应用程序执行上述代码和配置:

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);
    }

}

前面的应用程序的输出类似于以下内容:

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

示例:RequiredAnnotationBeanPostProcessor

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展 Spring IoC 容器的常用方法。一个示例是 Spring 的RequiredAnnotationBeanPostProcessor-BeanPostProcessor实现,该实现随 Spring 发行版一起提供,该实现可确保对(标有(任意)Comments 的)bean 上的 JavaBean 属性进行实际(配置)依赖注入值。

7.8.2 使用 BeanFactoryPostProcessor 自定义配置元数据

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

您可以配置多个BeanFactoryPostProcessor,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor的执行 Sequences。但是,仅当BeanFactoryPostProcessor实现Ordered接口时才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,则也应该考虑实现Ordered接口。有关更多详细信息,请查阅BeanFactoryPostProcessorOrdered接口的 javadocs。

Note

如果要更改实际的 bean * instances *(即,从配置元数据创建的对象),则需要使用BeanPostProcessor(上面在第 7.8.1 节“使用 BeanPostProcessor 定制 bean”中进行了描述)。虽然在技术上可以在BeanFactoryPostProcessor内使用 Bean 实例(例如,使用BeanFactory.getBean()),但这样做会导致 Bean 实例化过早,从而违反了标准容器的生命周期。这可能会导致负面影响,例如绕过 bean 后处理。

此外,BeanFactoryPostProcessor的作用域是每个容器。仅在使用容器层次结构时才有意义。如果您在一个容器中定义BeanFactoryPostProcessor,它将应用于该容器中的 Bean 定义。一个容器中的 Bean 定义将不会由另一个容器中的BeanFactoryPostProcessor进行后处理,即使两个容器都属于同一层次结构也是如此。

ApplicationContext内部声明 Bean 工厂后处理器会自动执行,以便将更改应用于定义容器的配置元数据。 Spring 包含许多 sched 义的 bean 工厂后处理器,例如PropertyOverrideConfigurerPropertyPlaceholderConfigurer。自定义BeanFactoryPostProcessor也可以用于例如注册自定义属性编辑器。

ApplicationContext自动检测部署到其中的实现BeanFactoryPostProcessor接口的所有 bean。它在适当的时候将这些 bean 用作 bean 工厂的后处理器。您可以像部署其他任何 bean 一样部署这些后处理器 bean。

Note

BeanPostProcessor s 一样,您通常不希望将BeanFactoryPostProcessor s 配置为延迟初始化。如果没有其他 bean 引用Bean(Factory)PostProcessor,则该后处理器将完全不会被实例化。因此,将其标记为延迟初始化将被忽略,即使您在<beans />元素的声明中将default-lazy-init属性设置为true,也将急切地实例化Bean(Factory)PostProcessor

示例:类名替换 PropertyPlaceholderConfigurer

您可以使用PropertyPlaceholderConfigurer使用标准 Java Properties格式将 Bean 定义中的属性值外部化到单独的文件中。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库 URL 和密码,而无需为修改容器的主要 XML 定义文件而复杂或冒风险。

考虑以下基于 XML 的配置元数据片段,其中定义了带有占位符值的DataSource。该示例显示了从外部Properties文件配置的属性。在运行时,将PropertyPlaceholderConfigurer应用于元数据,它将替换数据源的某些属性。将要替换的值指定为${property-name}形式的* placeholders *,它遵循 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

因此,字符串${jdbc.username}在运行时被替换为值'sa',并且其他与属性文件中的键匹配的占位符值也是如此。 PropertyPlaceholderConfigurer检查 bean 定义的大多数属性和属性中的占位符。此外,可以自定义占位符前缀和后缀。

使用 Spring 2.5 中引入的context名称空间,可以使用专用配置元素配置属性占位符。可以在location属性中以逗号分隔列表的形式提供一个或多个位置。

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

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

  • 从不(0):从不检查系统属性

    • fallback *(1):检查系统属性(如果在指定的属性文件中不可解析)。这是默认值。
  • 覆盖(2):在尝试指定的属性文件之前,请先检查系统属性。这允许系统属性覆盖任何其他属性源。

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

Tip

您可以使用PropertyPlaceholderConfigurer来替换类名,这在您必须在运行时选择特定的实现类时有时很有用。例如:

<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}"/>

如果无法在运行时将该类解析为有效的类,则将要创建的 bean 的解析将失败,即在非延迟初始化 bean 的ApplicationContextpreInstantiateSingletons()阶段。

示例:PropertyOverrideConfigurer

PropertyOverrideConfigurer是另一个 bean 工厂的后处理器,类似于PropertyPlaceholderConfigurer,但是与后者不同,原始定义可以具有默认值,也可以没有 bean 属性的值。如果覆盖的Properties文件没有某个 bean 属性的条目,则使用默认的上下文定义。

请注意,bean 定义被覆盖,因此从 XML 定义文件中不能立即看出正在使用覆盖配置程序。如果有多个PropertyOverrideConfigurer实例为同一个 bean 属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。

属性文件配置行采用以下格式:

beanName.property=value

For example:

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

此示例文件可以与包含名为* dataSource 的 bean 的容器定义一起使用,该 bean 具有 driver url *属性。

只要路径的每个组成部分(最终属性被覆盖)之外的所有组成部分都已经为非空(可能是由构造函数初始化),则也支持复合属性名。在这个例子中

foo.fred.bob.sammy=123
  • foo bean 的fred属性的bob属性的sammy属性设置为标量值123

Note

指定的替代值始终为Literals值;它们不会转换为 bean 引用。当 XML bean 定义中的原始值指定 bean 引用时,此约定也适用。

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

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

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

为本身是 factory *的对象实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是可插入 Spring IoC 容器的实例化逻辑的点。如果您拥有复杂的初始化代码,而不是(可能)冗长的 XML,可以用 Java 更好地表达,则可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

FactoryBean界面提供了三种方法:

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

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

  • Class getObjectType():返回getObject()方法或null返回的对象类型(如果事先未知)。

FactoryBean概念和接口在 Spring 框架中的许多地方都使用过; Spring 本身提供了 50 多个FactoryBean接口的实现。

当您需要向容器请求一个实际的FactoryBean实例本身而不是它产生的 bean 时,请在调用ApplicationContextgetBean()方法时在该 bean 的 ID 前面加上“&”符号(&)。因此,对于 ID 为myBean的给定FactoryBean,在容器上调用getBean("myBean")会返回FactoryBean的乘积;而调用getBean("&myBean")会返回FactoryBean实例本身。

7.9 基于 Comments 的容器配置

Are annotations better than XML for configuring Spring?

基于 Comments 的配置的引入提出了一个问题,即这种方法是否比 XML“更好”。简短的答案是取决于。长的答案是每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于定义方式的不同,Comments 在声明中提供了很多上下文,从而使配置更短,更简洁。但是,XML 擅长连接组件而不接触其源代码或重新编译它们。一些开发人员更喜欢将布线放置在靠近源的位置,而另一些开发人员则认为带 Comments 的类不再是 POJO,而且,该配置变得分散且难以控制。

无论选择如何,Spring 都可以容纳两种样式,甚至可以将它们混合在一起。值得指出的是,通过JavaConfig选项,Spring 允许以非侵入方式使用 Comments,而无需接触目标组件的源代码,并且就工具而言,Spring 工具套件支持所有配置样式。

基于 Comments 的配置提供了 XML 设置的替代方法,该配置依赖字节码元数据来连接组件而不是尖括号声明。通过使用相关类,方法或字段声明上的 Comments,开发人员无需使用 XML 来描述 bean 的连接,而是将配置移入组件类本身。如名为“示例:RequiredAnnotationBeanPostProcessor”的部分中所述,结合使用BeanPostProcessor和 Comments 是扩展 Spring IoC 容器的常用方法。例如,Spring 2.0 引入了使用@RequiredComments 强制执行必需属性的可能性。 Spring 2.5 使遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。本质上,@Autowired注解提供了与第 7.4.5 节“自动装配合作者”中描述的功能相同的功能,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5 还添加了对 JSR-250 注解(如@PostConstruct@PreDestroy)的支持。 Spring 3.0 增加了对 javax.inject 包中包含的 JSR-330(Java 依赖注入)Comments 的支持,例如@Inject@Named。有关这些 Comments 的详细信息,请参见relevant section

Note

注解注入是在 XML 注入之前执行的,因此对于通过两种方法连接的属性,后一种配置将覆盖前者。

与往常一样,您可以将它们注册为单独的 bean 定义,但是也可以通过在基于 XML 的 Spring 配置中包含以下标记来隐式注册它们(请注意包含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>

(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessor以及上述RequiredAnnotationBeanPostProcessor。)

Note

<context:annotation-config/>仅在定义它的相同应用程序上下文中查找 bean 上的 Comments。这意味着,如果您将<context:annotation-config/>放在WebApplicationContext中而不是DispatcherServlet,则它仅检查控制器中的@Autowired bean,而不检查服务。有关更多信息,请参见第 22.2 节“ DispatcherServlet”

7.9.1 @Required

@RequiredComments 适用于 bean 属性设置器方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

该 Comments 仅表明必须在配置时通过 bean 定义中的显式属性值或通过自动装配来填充受影响的 bean 属性。如果受影响的 bean 属性尚未填充,则容器将引发异常;否则,容器将抛出异常。这样可以避免出现明显的故障,从而避免以后出现NullPointerException或类似情况。仍然建议您将 assert 放入 bean 类本身中,例如,放入 init 方法中。这样做会强制执行那些必需的引用和值,即使您在容器外部使用该类也是如此。

7.9.2 @Autowired

Note

在下面的示例中,可以使用 JSR 330 的@InjectComments 代替 Spring 的@AutowiredComments。有关更多详细信息,请参见here

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

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

Note

从 Spring Framework 4.3 开始,如果目标 bean 仅定义一个以其开头的构造函数,则不再需要在此类构造函数上使用@AutowiredComments。但是,如果有几个构造函数可用,则必须至少 Comments 一个,以告诉容器使用哪个构造函数。

如预期的那样,您还可以将@AutowiredComments 应用于“传统”设置方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

您还可以将 Comments 应用于具有任意名称和/或多个参数的方法:

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;
    }

    // ...
}

Tip

确保目标组件(例如MovieCatalogCustomerPreferenceDao)由用于@AutowiredComments 的注入点的类型一致地声明。否则,注入可能会由于在运行时找不到类型匹配而失败。

对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器通常预先知道具体的类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表现力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法中声明最具体的返回类型(至少根据引用您的 bean 的注入点的要求进行声明)。

通过将 Comments 添加到需要该类型数组的字段或方法中,也可以从ApplicationContext提供所有特定类型的 bean:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

同样适用于类型化的集合:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}

Tip

如果希望数组或列表中的项按特定 Sequences 排序,则目标 bean 可以实现org.springframework.core.Ordered接口或使用@Order或标准@PriorityComments。否则,它们的 Sequences 将遵循容器中相应目标 Bean 定义的注册 Sequences。

@Order注解可以在目标类级别上声明,也可以在@Bean方法上声明,每个 bean 定义可能非常个别(在具有相同 bean 类的多个定义的情况下)。 @Order值可能会影响注入点的优先级,但是请注意,它们不会影响单例启动 Sequences,这是由依赖关系和@DependsOn声明确定的正交关系。

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

只要预期的键类型为String,即使是键入的 Map 都可以自动连线。 Map 值将包含所有预期类型的 bean,并且键将包含相应的 bean 名称:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

Note

只能将“每个类一个带 Comments 的构造函数”标记为“必需”,但可以 Comments 多个非必需的构造函数。在这种情况下,每个候选对象都将被考虑在内,Spring 会使用* greediest *构造函数,该构造函数的依赖关系可以得到满足,即构造函数具有最多的参数。

建议在@RequiredComments 上使用@Autowired的* required *属性。 * required *属性指示该属性对于自动装配不是必需的,如果该属性不能自动装配,则将其忽略。另一方面,@Required更强大,因为它可以强制执行通过容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。

或者,您可以通过 Java 8 的java.util.Optional来表达特定依赖项的非必需性质:

public class SimpleMovieLister {

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

您也可以将@Autowired用于众所周知的可解决依赖项:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。这些接口及其扩展接口(例如ConfigurableApplicationContextResourcePatternResolver)将自动解析,而无需进行特殊设置。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

Note

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

7.9.3 使用@Primary 微调基于 Comments 的自动装配

由于按类型自动布线可能会导致多个候选对象,因此通常有必要对选择过程进行更多控制。实现此目的的一种方法是使用 Spring 的@PrimaryComments。 @Primary表示当多个 bean 可以自动连接到单值依赖项的候选对象时,应优先考虑特定的 bean。如果候选对象中仅存在一个“主” bean,它将是自动装配的值。

假设我们有以下配置将firstMovieCatalog定义为* primary * MovieCatalog

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

通过这种配置,以下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 使用限定符微调基于 Comments 的自动装配

当可以确定一个主要候选对象时,@Primary是在几种情况下按类型使用自动装配的有效方法。当需要对选择过程进行更多控制时,可以使用 Spring 的@QualifierComments。您可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便为每个参数选择特定的 bean。在最简单的情况下,这可以是简单的描述性值:

public class MovieRecommender {

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

    // ...
}

@QualifierComments 也可以在各个构造函数参数或方法参数上指定:

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 定义如下所示。具有限定符值“ main”的 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 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>

对于后备匹配,bean 名称被视为默认的限定符值。因此,您可以使用 id“ main”而不是嵌套的 qualifier 元素定义 bean,从而得到相同的匹配结果。但是,尽管您可以使用此约定按名称来引用特定的 bean,但是@Autowired基本上是关于带有可选语义限定符的类型驱动的注入。这意味着,即使带有 Bean 名称后退的限定符值,在类型匹配集合中也始终具有狭窄的语义。它们没有在语义上表示对唯一 bean id 的引用。良好的限定符值是“ main”或“ EMEA”或“ persistent”,表示特定组件的 Feature 与 bean id独立,如果在匿名 bean 定义的情况下(如前面示例中的定义),则可以自动生成该特定组件。

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

Tip

在类型匹配的候选对象中,让限定符值针对目标 Bean 名称进行选择,甚至不需要在注入点添加@QualifierComments。如果没有其他解析度 Metrics(例如,限定词或主标记),则对于非唯一依赖性情况,Spring 会将注入点名称(即字段名称或参数名称)与目标 Bean 名称进行匹配,并选择相同的-提名候选人(如果有)。

就是说,如果您打算按名称表示 Comments 驱动的注入,则即使可以通过类型匹配候选中的 bean 名称进行选择,也不要主要使用@Autowired。而是使用 JSR-250 @ResourceComments,该 Comments 的语义定义是通过其唯一名称来标识特定目标组件,而声明的类型与匹配过程无关。 @Autowired的语义有很大不同:按类型选择候选 Bean 之后,仅在那些类型选择的候选中(例如将“帐户”限定词与标有相同限定词标签的 bean 进行匹配。

对于本身定义为集合/Map 或数组类型的 bean,@Resource是一个很好的解决方案,它通过唯一名称引用特定的集合或数组 bean。也就是说,从 4.3 版本开始,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中,就可以通过 Spring 的@Autowired类型匹配算法来匹配集合/Map 和数组类型。在这种情况下,可以使用限定符值在同类型的集合中进行选择,如上一段所述。

从 4.3 开始,@Autowired还考虑了自我引用以进行注入,即回溯到当前已注入的 bean。注意,自我注入是一个后备;对其他组件的常规依赖始终优先。从这种意义上说,自我推荐不参与常规的候选人选择,因此特别是绝不是主要的;相反,它们总是以最低优先级结束。实际上,仅将自我引用用作最后的手段,例如通过 Bean 的事务代理在同一实例上调用其他方法的方法:在这种情况下,考虑将受影响的方法分解为单独的委托 Bean。或者,使用@Resource可以通过其唯一名称获取返回到当前 bean 的代理。

@Autowired适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符 Comments 的范围。相反,仅具有单个参数的字段和 bean 属性设置器方法支持@Resource。因此,如果注入目标是构造函数或多参数方法,请坚持使用限定符。

您可以创建自己的自定义限定符 Comments。只需定义一个 Comments 并在定义中提供@QualifierComments:

@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/>标记的子元素,然后指定typevalue以匹配您的自定义限定符 Comments。该类型与 Comments 的完全限定的类名匹配。或者,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面的示例演示了这两种方法。

<?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>

第 7.10 节“Classpath 扫描和托管组件”中,您将看到基于 Comments 的替代方法,以 XML 形式提供限定符元数据。具体来说,请参见第 7.10.8 节“用 Comments 提供限定符元数据”

在某些情况下,使用没有值的 Comments 可能就足够了。当 Comments 用于更一般的目的并且可以应用于几种不同类型的依赖项时,这可能很有用。例如,您可以提供离线目录,当没有 Internet 连接可用时将进行搜索。首先定义简单的 Comments:

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

}

然后将 Comments 添加到要自动连接的字段或属性:

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属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则 Bean 定义必须与所有“ *”这样的属性值匹配,才能被视为自动装配候选。例如,请考虑以下 Comments 定义:

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

    String genre();

    Format format();
}

在这种情况下,Format是一个枚举:

public enum Format {
    VHS, DVD, BLURAY
}

要自动连线的字段将使用自定义限定符进行 Comments,并包含两个属性的值: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 定义应包含匹配的限定符值。此示例还演示了可以使用 bean * meta *属性代替<qualifier/>子元素。如果可用,则<qualifier/>及其属性优先,但是如果不存在此类限定符,则自动装配机制将退回到<meta/>标记内提供的值,如以下示例中的最后两个 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注解之外,还可以将 Java 泛型类型用作限定的隐式形式。例如,假设您具有以下配置:

@Configuration
public class MyConfiguration {

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

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

假设上述 bean 实现了通用接口,即Store<String>Store<Integer>,则可以@Autowire Store接口,并且* generic *将用作限定符:

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

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

在自动装配列表,Map 和数组时,通用限定符也适用:

// 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

CustomAutowireConfigurerBeanFactoryPostProcessor,即使您未使用 Spring 的@QualifierComments 对您自己的自定义限定符 Comments 类型进行注册,也可以使它们注册。

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

AutowireCandidateResolver通过以下方式确定自动装配的候选对象:

  • 每个 bean 定义的autowire-candidate

  • <beans/>元素上可用的任何default-autowire-candidates模式

  • @QualifierComments 和在CustomAutowireConfigurer中注册的所有自定义 Comments 的存在

当多个 bean 符合自动装配候选条件时,确定“主要”的步骤如下:如果候选中的一个 bean 定义中恰好有一个primary属性设置为true,则将选择它。

7.9.7 @Resource

Spring 还支持在字段或 bean 属性设置器方法上使用 JSR-250 @Resource注解进行注入。这是 Java EE 5 和 6 中的常见模式,例如在 JSF 1.2 托管 Bean 或 JAX-WS 2.0 端点中。 Spring 也为 SpringManagement 的对象支持此模式。

@Resource具有名称属性,默认情况下,Spring 将该值解释为要注入的 Bean 名称。换句话说,它遵循* by-name *语义,如本示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果未明确指定名称,则默认名称是从字段名称或 setter 方法派生的。如果是字段,则以字段名称为准;如果使用 setter 方法,则使用 bean 属性名称。因此,以下示例将在其 setter 方法中注入名称为“ movieFinder”的 bean:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

Note

Comments 提供的名称由CommonAnnotationBeanPostProcessor知道的ApplicationContext解析为 bean 名称。如果您显式配置 Spring 的SimpleJndiBeanFactory,则可以通过 JNDI 解析名称。但是,建议您依靠默认行为,并仅使用 Spring 的 JNDI 查找功能来保留间接级别。

在未指定@Resource且未指定显式名称且与@Autowired类似的特殊情况下,@Resource查找主类型匹配而不是特定的命名 Bean,并解析众所周知的可解决依赖项:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource接口。

因此,在以下示例中,customerPreferenceDao字段首先查找名为 customerPreferenceDao 的 bean,然后回退到类型CustomerPreferenceDao的主类型匹配。基于已知的可解决依赖类型ApplicationContext注入“上下文”字段。

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

7.9.8 @PostConstruct 和@PreDestroy

CommonAnnotationBeanPostProcessor不仅可以识别@ResourceComments,还可以识别 JSR-250 * lifecycle *Comments。在 Spring 2.5 中引入了对这些 Comments 的支持,为initialization callbacksdestruction callbacks中描述的 Comments 提供了另一种选择。假设CommonAnnotationBeanPostProcessor已在 Spring ApplicationContext中注册,则在生命周期的同一点与相应的 Spring 生命周期接口方法或显式声明的回调方法一起调用带有这些注解之一的方法。在下面的示例中,缓存将在初始化时预先填充,并在销毁时清除。

public class CachingMovieLister {

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

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

Note

有关组合各种生命周期机制的效果的详细信息,请参见称为“组合生命周期机制”的部分

7.10Classpath 扫描和托管组件

本章中的大多数示例都使用 XML 来指定在 Spring 容器中生成每个BeanDefinition的配置元数据。上一节(第 7.9 节“基于 Comments 的容器配置”)演示了如何通过源级 Comments 提供许多配置元数据。但是,即使在这些示例中,“基本” bean 定义也已在 XML 文件中明确定义,而 Comments 仅驱动依赖项注入。本节介绍了通过扫描 Classpath 来隐式检测“候选组件”的选项。候选组件是与过滤条件匹配的类,并在容器中注册了相应的 Bean 定义。这消除了使用 XML 进行 bean 注册的需要。相反,您可以使用 Comments(例如@Component),AspectJ 类型表达式或您自己的自定义过滤条件来选择哪些类将在容器中注册 bean 定义。

Note

从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能是核心 Spring Framework 的一部分。这使您可以使用 Java 而不是使用传统的 XML 文件来定义 bean。请查看@Configuration@Bean@Import@DependsOn注解,以获取有关如何使用这些新功能的示例。

7.10.1 @Component 和其他构造型 Comments

@RepositoryComments 是充当存储库(也称为数据访问对象或 DAO)的角色或“原型”的任何类的标记。该标记的用途包括自动翻译 exception,如第 20.2.2 节“异常翻译”中所述。

Spring 提供了进一步的构造型 Comments:@Component@Service@Controller@Component是任何 SpringManagement 的组件的通用构造型。 @Repository@Service@Controller@Component的专业化,用于更具体的用例,例如分别在持久层,服务层和表示层中。因此,可以使用@ComponentComments 组件类,但可以使用@Repository@Service@ControllerComments 组件类,这些类更适合于工具处理或与方面相关联。例如,这些构造型 Comments 成为切入点的理想目标。 @Repository@Service@Controller也可能在 Spring 框架的将来版本中带有其他语义。因此,如果在服务层使用@Component@Service之间进行选择,则@Service显然是更好的选择。同样,如上所述,@Repository已被支持作为持久层中自动异常转换的标记。

7.10.2 Meta-annotations

Spring 提供的许多 Comments 都可以在您自己的代码中用作“元 Comments”。元 Comments 只是可以应用于另一个 Comments 的 Comments。例如,上面提到的@ServiceComments 使用@Component进行元 Comments:

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

    // ....
}

元 Comments 也可以组合以创建组合 Comments。例如,Spring MVC 的@RestControllerComments 由@Controller@ResponseBody组成*。

另外,组合 Comments 可以可选地从元 Comments 中重新声明属性,以允许用户自定义。当您只想公开元 Comments 属性的子集时,这可能特别有用。例如,Spring 的@SessionScopeComments 将作用域名称硬编码为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;

}

然后,无需声明proxyMode就可以使用@SessionScope,如下所示:

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

或使用proxyMode的替代值,如下所示:

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

有关更多详细信息,请咨询SpringComments 编程模型

7.10.3 自动检测类并注册 Bean 定义

Spring 可以自动检测构造型类,并向ApplicationContext注册相应的BeanDefinition。例如,以下两个类别有资格进行这种自动检测:

@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
}

要自动检测这些类并注册相应的 bean,需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是两个类的公共父包。 (或者,您可以指定一个逗号/分号/以空格分隔的列表,其中包括每个类的父包.)

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

Note

为简明起见,以上内容可能使用了 Comments 的value属性,即@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>

Tip

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

Note

扫描 Classpath 包需要在 Classpath 中存在相应的目录条目。使用 Ant 构建 JAR 时,请确保您不激活 JAR 任务的仅文件开关。同样,在某些环境下,例如基于安全策略,可能不会公开 Classpath 目录。 JDK 1.7.0_45 及更高版本上的独立应用程序(需要在 Lists 中设置“受信任的库”;请参阅http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

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

Note

您可以通过包含* annotation-config *属性(值为false)来禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册。

7.10.4 使用过滤器自定义扫描

默认情况下,唯一检测到的候选组件是用@Component@Repository@Service@ControllerComments 的类或本身用@ComponentComments 的自定义 Comments。但是,您可以仅通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan注解的* includeFilters excludeFilters 参数(或component-scan元素的 include-filter exclude-filter *子元素)。每个过滤器元素都需要typeexpression属性。下表描述了过滤选项。

表 7.5. 过滤器类型

Filter TypeExample ExpressionDescription
annotation (default)org.example.SomeAnnotation在目标组件的类型级别上存在的 Comments。
assignableorg.example.SomeClass目标组件可分配给的类(或接口)(扩展/实现)。
aspectjorg.example..*Service+目标组件要匹配的 AspectJ 类型表达式。
regexorg\.example\.Default.*要与目标组件类名称匹配的正则表达式。
customorg.example.MyTypeFilterorg.springframework.core.type .TypeFilter接口的自定义实现。

以下示例显示了忽略所有@Repository注解并使用“存根”存储库的配置。

@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>

Note

您还可以通过在 Comments 上设置useDefaultFilters=false或提供use-default-filters="false"作为<component-scan/>元素的属性来禁用默认过滤器。这实际上将禁用对带有@Component@Repository@Service@Controller@ConfigurationComments 的类的自动检测。

7.10.5 在组件中定义 bean 元数据

Spring 组件还可以将 bean 定义元数据贡献给容器。您可以使用与@Configuration带 Comments 的类中的 Bean 元数据定义相同的@BeanComments 来执行此操作。这是一个简单的示例:

@Component
public class FactoryMethodComponent {

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

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

此类是 Spring 组件,其doWork()方法中包含特定于应用程序的代码。但是,它也提供了具有工厂方法的 bean 定义,该工厂方法引用方法publicInstance()@BeanComments 标识工厂方法和其他 bean 定义属性,例如通过@QualifierComments 的限定符值。可以指定的其他方法级别 Comments 是@Scope@Lazy和自定义限定符 Comments。

Tip

除了其对组件初始化的作用外,@LazyComments 还可以放置在标有@Autowired@Inject的注入点上。在这种情况下,它导致注入了惰性解析代理。

如前所述,支持自动连线的字段和方法,另外还支持自动连线@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);
    }
}

该示例将String方法参数country自动连接到另一个名为privateInstance的 bean 上age属性的值。 Spring Expression Language 元素通过符号#{ <expression> }定义属性的值。对于@ValueComments,表达式解析器已预先配置为在解析表达式文本时查找 bean 名称。

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

@Component
public class FactoryMethodComponent {

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

常规 Spring 组件中的@Bean方法的处理方式与 Spring @Configuration类中相应方法的处理方式不同。不同之处在于,CGLIB 并未增强@Component类来拦截方法和字段的调用。 CGLIB 代理是调用@Configuration类中@Bean方法中的方法或字段中的字段的方法,用于创建 Bean 元数据引用来协作对象。这样的方法不是用普通的 Java 语义调用的,而是通过容器来提供常规的生命周期 Management 和 Spring bean 的代理,即使通过编程调用@Bean方法引用其他 bean 时也是如此。相反,在普通的@Component类内调用@Bean方法中的方法或字段具有*标准 Java 语义,而无需特殊的 CGLIB 处理或其他约束。

Note

您可以将@Bean方法声明为static,从而允许在不将其包含的配置类创建为实例的情况下调用它们。当定义后处理器 bean 时,这特别有意义。类型BeanFactoryPostProcessorBeanPostProcessor,因为此类 bean 将在容器生命周期的早期进行初始化,并且应避免在那时触发配置的其他部分。

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

@Bean方法的 Java 语言可见性不会对 Spring 容器中的最终 bean 定义产生直接影响。您可以随意声明自己的工厂方法,以适合非@Configuration类,也可以在任何地方声明静态方法。但是,@Configuration类中的常规@Bean方法必须是可重写的,即,不能将它们声明为privatefinal

@Bean方法还将在给定组件或配置类的 Base Class 上以及在组件或配置类实现的接口中声明的 Java 8 默认方法上发现。这为组合复杂的配置安排提供了很大的灵 Active,从 Spring 4.2 开始,通过 Java 8 默认方法甚至可以实现多重继承。

最后,请注意,单个类可以为同一个 bean 保留多个@Bean方法,这是根据运行时可用依赖项而使用的多个工厂方法的安排。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时将选择具有最大可满足依赖关系数量的变量,类似于容器在多个@Autowired构造函数之间进行选择的方式。

7.10.6 命名自动检测的组件

在扫描过程中自动检测到某个组件时,其 bean 名称由该扫描器已知的BeanNameGenerator策略生成。默认情况下,任何包含* name * value的 Spring 构造型 Comments(@Component@Repository@Service@Controller)都会将该名称提供给相应的 bean 定义。

如果这样的 Comments 不包含* name * value或任何其他检测到的组件(例如,由自定义过滤器发现的组件),则缺省 bean 名称生成器将返回未使用大写字母的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieListermovieFinderImpl

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

Note

如果不想依赖默认的 Bean 命名策略,则可以提供自定义 Bean 命名策略。首先,实现BeanNameGenerator接口,并确保包括默认的 no-arg 构造函数。然后,在配置扫描仪时提供标准的类名称:

@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>

通常,请考虑在其他组件可能对其进行显式引用时,使用注解指定名称。另一方面,只要容器负责接线,自动生成的名称就足够了。

7.10.7 提供自动检测组件的作用域

通常,与 SpringManagement 的组件一样,自动检测到的组件的默认且最常见的范围是singleton。但是,有时您需要一个不同的范围,可以通过@ScopeComments 指定该范围。只需在 Comments 中提供范围的名称即可:

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

Note

@ScopeComments 仅在具体的 bean 类(对于带 Comments 的组件)或工厂方法(对于@Bean方法)上进行内省。与 XML bean 定义相反,没有 bean 定义继承的概念,并且在类级别的继承层次结构与元数据目的无关。

有关特定于 Web 的范围的详细信息,例如 Spring 上下文中的“ request” /“ session”,请参见第 7.5.4 节“请求,会话,全局会话,应用程序和 WebSocket 范围”。像这些范围的预构建 Comments 一样,您也可以使用 Spring 的元 Comments 方法来编写自己的作用域 Comments:使用@Scope("prototype")进行元 Comments 的自定义 Comments,也可能会声明自定义范围代理模式。

Note

要提供用于范围解析的自定义策略,而不是依赖于基于 Comments 的方法,请实现ScopeMetadataResolver接口,并确保包含默认的无参数构造函数。然后,在配置扫描仪时提供标准的类名称:

@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>

使用某些非单作用域时,可能有必要为作用域对象生成代理。推理在称为“范围 bean 作为依赖项”的部分中描述。为此,组件扫描元素上提供了* scoped-proxy *属性。三个可能的值是:no,interfaces 和 targetClass。例如,以下配置将产生标准的 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 提供带有 Comments 的限定符元数据

第 7.9.4 节“使用限定符微调基于 Comments 的自动装配”中讨论了@QualifierComments。该部分中的示例演示了@Qualifier注解和自定义限定符注解的使用,以在解析自动装配候选时提供细粒度的控制。由于这些示例基于 XML Bean 定义,因此使用 XML 中bean元素的qualifiermeta子元素在候选 Bean 定义上提供了限定符元数据。当依靠 Classpath 扫描来自动检测组件时,您可以在候选类上为限定符元数据提供类型级别的 Comments。下面的三个示例演示了此技术:

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

Note

与大多数基于 Comments 的替代方法一样,请记住,Comments 元数据绑定到类定义本身,而 XML 的使用允许相同类型的多个 bean 提供其限定符元数据的变体,因为该元数据是按实例而不是按类提供。

7.11 使用 JSR 330 标准 Comments

从 Spring 3.0 开始,Spring 提供对 JSR-330 标准 Comments(依赖注入)的支持。这些 Comments 的扫描方式与 SpringComments 的扫描方式相同。您只需要在 Classpath 中有相关的 jar。

Note

如果使用的是 Maven,则javax.inject工件在标准 Maven 存储库(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一样,可以在字段级别,方法级别和构造函数参数级别使用@Inject。此外,您可以将注入点声明为Provider,以允许按需访问范围更短的 bean,或者通过Provider.get()调用来懒惰地访问其他 bean。作为上述示例的变体:

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(...);
        ...
    }
}

如果要为应该注入的依赖项使用限定名称,则应使用@Named注解,如下所示:

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:@ComponentComments 的标准等效项

代替@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是很常见的。 @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时,可以使用与使用 SpringComments 完全相同的方式来使用组件扫描:

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

Note

@Component相反,JSR-330 @Named和 JSR-250 ManagedBeanComments 是不可组合的。请使用 Spring 的构造型模型来构建自定义组件 Comments。

7.11.3 JSR-330 标准 Comments 的局限性

使用标准 Comments 时,重要的是要知道某些重要功能不可用,如下表所示:

表 7.6. Spring 组件模型元素与 JSR-330 变体

Springjavax.inject.*javax.inject 限制/Comments
@Autowired@Inject@Inject没有“必需”属性;可以与 Java 8 的Optional一起使用。
@Component@Named/@ManagedBeanJSR-330 没有提供可组合的模型,只是一种识别命名组件的方法。
@Scope("singleton")@SingletonJSR-330 的默认范围类似于 Spring 的prototype。但是,为了使其与 Spring 的常规默认设置保持一致,默认情况下,在 Spring 容器中声明的 JSR-330 bean 为singleton。为了使用singleton以外的范围,您应该使用 Spring 的@Scope注解。 javax.inject还提供@ScopeComments。但是,此仅用于创建自己的 Comments。
@Qualifier@ Qualifier/@ Namedjavax.inject.Qualifier只是用于构建自定义限定符的元 Comments。可以通过javax.inject.Named来关联具体的字符串限定符(例如带有值的 Spring 的@Qualifier)。
@Value-no equivalent
@Required-no equivalent
@Lazy-no equivalent
ObjectFactoryProviderjavax.inject.Provider是 Spring 的ObjectFactory的直接替代方法,只是方法名get()较短。它也可以与 Spring 的@Autowired或未 Comments 的构造函数和 setter 方法结合使用。

7.12 基于 Java 的容器配置

7.12.1 基本概念:@Bean 和@Configuration

Spring 的新 Java 配置支持中的主要工件是@ConfigurationComments 的类和@BeanComments 的方法。

@BeanComments 用于指示方法实例化,配置和初始化要由 Spring IoC 容器 Management 的新对象。对于熟悉 Spring 的<beans/> XML 配置的人来说,@BeanComments 与<bean/>元素具有相同的作用。您可以将@Bean带 Comments 的方法与任何 Spring @Component一起使用,但是,它们通常与@Configuration bean 一起使用。

@ConfigurationComments 类表示该类的主要目的是作为 Bean 定义的来源。此外,@Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义 Bean 间的依赖关系。可能最简单的@Configuration类如下所示:

@Configuration
public class AppConfig {

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

上面的AppConfig类等效于以下 Spring <beans/> XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

Full @Configuration vs 'lite' @Bean mode?

当在未使用@ConfigurationComments 的类中声明@Bean方法时,它们被称为以“精简”模式进行处理。在@Component甚至是“普通旧类”中声明的 Bean 方法将被视为“精简版”,其包含类的主要目的有所不同,而@Bean方法只是那里的一种奖励。例如,服务组件可以通过每个适用的组件类上的附加@Bean方法向容器公开 Management 视图。在这种情况下,@Bean方法是一种简单的通用工厂方法机制。

与完整@Configuration不同,lite @Bean方法无法声明 Bean 之间的依赖关系。相反,它们对包含组件的内部状态以及可能声明的参数进行操作。因此,这种@Bean方法不应调用其他@Bean方法。每个这样的方法实际上只是用于特定 bean 引用的工厂方法,而没有任何特殊的运行时语义。这里的积极副作用是,不必在运行时应用 CGLIB 子类,因此在类设计方面没有任何限制(即,包含类仍然可以是final等)。

在常见情况下,将在@Configuration类中声明@Bean方法,以确保始终使用“完全”模式,因此跨方法引用将被重定向到容器的生命周期 Management。这将防止通过常规 Java 调用意外地调用同一@Bean方法,这有助于减少在“精简”模式下运行时难以追查的细微错误。

@Bean@Configuration注解将在以下各节中进行深入讨论。但是,首先,我们将介绍使用基于 Java 的配置创建 spring 容器的各种方法。

7.12.2 使用 AnnotationConfigApplicationContext 实例化 Spring 容器

以下各节介绍 Spring 3.0 的新增功能 Spring 的AnnotationConfigApplicationContext。这种通用的ApplicationContext实现不仅能够接受@Configuration类作为 Importing,而且还能够接受普通@Component类和带有 JSR-330 元数据 Comments 的类。

当提供@Configuration类作为 Importing 时,@Configuration类本身被注册为 bean 定义,并且该类中所有已声明的@Bean方法也被注册为 bean 定义。

当提供@Component和 JSR-330 类时,它们将注册为 bean 定义,并且假定在必要时在这些类中使用了诸如@Autowired@Inject之类的 DI 元数据。

Simple construction

与实例化ClassPathXmlApplicationContext时将 Spring XML 文件用作 Importing 的方式几乎相同,实例化AnnotationConfigApplicationContext时可以将@Configuration类用作 Importing。这允许 Spring 容器完全不使用 XML:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

如上所述,AnnotationConfigApplicationContext不限于仅使用@Configuration个类。可以将任何@Component或 JSR-330 带 Comments 的类作为 Importing 提供给构造函数。例如:

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 依赖项注入 Comments,例如@Autowired

使用寄存器以编程方式构建容器(Class<?>…)

AnnotationConfigApplicationContext可以使用无参数构造函数实例化,然后使用register()方法进行配置。以编程方式构建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 ...)启用组件扫描

要启用组件扫描,只需 Comments 您的@Configuration类,如下所示:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}

Tip

经验丰富的 Spring 用户将熟悉 Spring context:名称空间中的 XML 声明

<beans>
<context:component-scan base-package="com.acme"/>
</beans>

在上面的示例中,将扫描com.acme包,查找带有@ComponentComments 的类,然后将这些类注册为容器内的 Spring bean 定义。 AnnotationConfigApplicationContext公开scan(String…)方法以允许相同的组件扫描功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

Note

请记住,@Configuration类与@Componentmeta-annotated,因此它们是组件扫描的候选者!在上面的示例中,假设AppConfigcom.acme包(或下面的任何包)中声明,则在对scan()的调用期间将其拾取,并且在refresh()时,将处理其所有@Bean方法并将其注册为容器内的 bean 定义。

使用 AnnotationConfigWebApplicationContext 支持 Web 应用程序

AnnotationConfigWebApplicationContext可提供AnnotationConfigApplicationContextWebApplicationContext变体。在配置 Spring ContextLoaderListener servlet 侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。下面是配置典型 Spring MVC Web 应用程序的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是方法级 Comments,是 XML <bean/>元素的直接类似物。Comments 支持<bean/>提供的某些属性,例如:init-methoddestroy-methodautowiringname

您可以在@ConfigurationComments 的类或@ComponentComments 的类中使用@BeanComments。

声明一个 bean

要声明 bean,只需使用@BeanComments 对方法进行 Comments。您可以使用此方法在指定为该方法的返回值的类型的ApplicationContext内注册 bean 定义。默认情况下,bean 名称将与方法名称相同。以下是@Bean方法声明的简单示例:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

前面的配置与下面的 Spring XML 完全等效:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明使名为transferService的 bean 在ApplicationContext中可用,并绑定到TransferServiceImpl类型的对象实例:

transferService -> com.acme.TransferServiceImpl

您还可以使用接口(或 Base Class)返回类型声明@Bean方法:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

但是,这会将提前类型预测的可见性限制为指定的接口类型(TransferService),然后在实例化受影响的单例 bean 之后,容器才知道完整类型(TransferServiceImpl)。非惰性单例 bean 根据其声明 Sequences 实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试按非声明的类型进行匹配(例如@Autowired TransferServiceImpl,只有当“ transferService” bean 具有被实例化)。

Tip

如果您pass 语句的服务接口一致地引用类型,则@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明可能的最具体的返回类型(至少与引用您的 bean 的注入点所要求的具体类型一样)更为安全。

Bean dependencies

带有@BeanComments 的方法可以具有任意数量的参数,这些参数描述了构建该 bean 所需的依赖关系。例如,如果我们的TransferService要求AccountRepository,则可以通过方法参数实现该依赖关系:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

解析机制与基于构造函数的依赖项注入几乎相同,有关更多详细信息,请参见相关部分

接收生命周期回调

任何使用@BeanComments 定义的类都支持常规的生命周期回调,并且可以使用 JSR-250 中的@PostConstruct@PreDestroyComments,有关更多详细信息,请参见JSR-250 annotations

还完全支持常规的 Spring lifecycle回调。如果 bean 实现InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。

还完全支持*Aware接口的标准集合,例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等。

@BeanComments 支持指定任意的初始化和销毁回调方法,就像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();
    }
}

Note

默认情况下,使用 Java config 定义的具有公共closeshutdown方法的 bean 会自动通过销毁回调注册。如果您有一个公共的closeshutdown方法,并且您不希望在容器关闭时调用它,只需将@Bean(destroyMethod="")添加到 bean 定义中以禁用默认的(inferred)模式。

默认情况下,您可能要对通过 JNDI 获取的资源执行此操作,因为其生命周期是在应用程序外部进行 Management 的。特别是,请确保始终对DataSource执行此操作,因为这在 Java EE 应用程序服务器上是有问题的。

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}

同样,对于@Bean方法,您通常会选择使用程序化 JNDI 查找:使用 Spring 的JndiTemplate/JndiLocatorDelegate帮助器或直接使用 JNDI InitialContext用法,但不使用JndiObjectFactoryBean变体,这会迫使您将返回类型声明为FactoryBean类型而不是实际的目标类型,因此在打算引用此处提供的资源的其他@Bean方法中更难用于交叉引用调用。

当然,对于上面的Foo,在构造期间直接调用init()方法同样有效:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...
}

Tip

当直接用 Java 工作时,您可以对对象执行任何操作,而不必总是依赖于容器的生命周期!

指定 bean 范围

使用@Scope 注解

您可以指定使用@BeanComments 定义的 bean 应该具有特定范围。您可以使用Bean Scopes部分中指定的任何标准范围。

默认范围是singleton,但是您可以使用@ScopeComments 覆盖它:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
@Scope 和 scoped-proxy

Spring 提供了一种通过scoped proxies处理范围内的依赖项的便捷方法。使用 XML 配置时创建此类代理的最简单方法是<aop:scoped-proxy/>元素。使用@ScopeComments 在 Java 中配置 bean,可以通过 proxyMode 属性提供同等的支持。默认值为无代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES

如果您使用 Java 从 XML 参考文档(请参阅前面的链接)将作用域代理示例移植到我们的@Bean,它将类似于以下内容:

// 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 命名

默认情况下,配置类使用@Bean方法的名称作为结果 bean 的名称。但是,可以使用name属性覆盖此功能。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }
}

Bean aliasing

第 7.3.1 节“命名 bean”中讨论的那样,有时希望为单个 bean 提供多个名称,否则称为* bean aliasing *。 @Bean注解的name属性为此目的接受一个 String 数组。

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

Bean description

有时提供更详细的 beanLiterals 描述会很有帮助。当暴露出 bean(可能通过 JMX)以进行监视时,这尤其有用。

要将说明添加到@Bean,可以使用@Description注解:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

7.12.4 使用@Configuration 注解

@Configuration是类级别的 Comments,指示对象是 Bean 定义的源。 @Configuration类通过公共@Bean带 Comments 的方法声明 Bean。对@Configuration类的@Bean方法的调用也可以用于定义 Bean 间的依赖关系。有关一般介绍,请参见第 7.12.1 节“基本概念:@Bean 和@Configuration”

注入 Bean 间依赖性

@Bean彼此依赖时,表达这种依赖就像让一个 bean 方法调用另一个一样简单:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

在上面的示例中,foo bean 通过构造函数注入接收对bar的引用。

Note

仅当在@Configuration类中声明@Bean方法时,此声明 bean 间依赖关系的方法才有效。您不能使用普通的@Component类声明 Bean 间的依赖关系。

查找方法注入

如前所述,查找方法注入是您不应该使用的高级功能。在单例作用域的 bean 依赖于原型作用域的 bean 的情况下,这很有用。将 Java 用于这种类型的配置为实现此模式提供了自然的方法。

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 配置支持,您可以创建CommandManager的子类,在该子类中,抽象createCommand()方法被覆盖,从而可以查找新的(原型)命令对象:

@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 的配置在内部如何工作的更多信息

下面的示例显示一个被@BeanComments 的方法被调用两次:

@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 中,实例化的 bean 默认具有singleton范围。这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的 bean。请注意,从 Spring 3.2 开始,不再需要将 CGLIB 添加到您的 Classpath 中,因为 CGLIB 类已经在org.springframework.cglib下重新打包并直接包含在 spring-core JAR 中。

Note

根据 bean 的范围,行为可能有所不同。我们在这里谈论单例。

Tip

由于 CGLIB 在启动时会动态添加功能,因此存在一些限制,特别是配置类必须不是最终的。但是,从 4.3 版本开始,配置类中允许使用任何构造函数,包括对默认注入使用@Autowired或单个非默认构造函数声明。

如果您希望避免任何 CGLIB 施加的限制,请考虑在非@Configuration类上声明@Bean方法,例如在普通的@Component类上。 @Bean方法之间的跨方法调用将不会被拦截,因此您将不得不仅依赖那里的构造函数或方法级别的依赖注入。

7.12.5 编写基于 Java 的配置

使用@Import 注解

就像 Spring XML 文件中使用<import/>元素来帮助模块化配置一样,@ImportComments 允许从另一个配置类中加载@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();
    }
}

现在,无需在实例化上下文时同时指定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);
}

这种方法简化了容器的实例化,因为只需要处理一个类,而不是要求开发人员在构造过程中记住大量的@Configuration类。

Tip

从 Spring Framework 4.2 开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果您想避免组件扫描,请使用一些配置类作为显式定义所有组件的入口点,这特别有用。

注入对导入的@Bean 定义的依赖性

上面的示例有效,但过于简单。在大多数实际情况下,Bean 在配置类之间将相互依赖。当使用 XML 时,这本身就不是问题,因为不涉及编译器,并且可以简单地声明ref="someBean"并相信 Spring 会在容器初始化期间将其解决。当然,当使用@Configuration类时,Java 编译器会对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。

幸运的是,解决这个问题很简单。与我们已经讨论过了一样,@Bean方法可以具有任意数量的描述 Bean 依赖关系的参数。让我们考虑一个具有多个@Configuration类的更真实的场景,每个类取决于在其他类中声明的 bean:

@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类最终只是容器中的另一个 bean:这意味着它们可以像其他任何 bean 一样利用@Autowired@Value注入等优势!

Warning

确保以这种方式注入的依赖项只是最简单的一种。 @Configuration类是在上下文初始化期间非常早地处理的,因此强制以这种方式注入依赖项可能会导致意外的早期初始化。只要有可能,请像上面的示例中那样使用基于参数的注入。

另外,请特别注意通过@Bean定义的BeanPostProcessorBeanFactoryPostProcessor。通常应将它们声明为static @Bean方法,而不触发其包含的配置类的实例化。否则,@Autowired@Value不能在配置类本身上工作,因为太早将其创建为 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");
}

Tip

从 Spring Framework 4.3 开始,仅支持@Configuration类中的构造方法注入。还请注意,如果目标 Bean 仅定义一个构造函数,则无需指定@Autowired;在上面的示例中,在RepositoryConfig构造函数上不需要@Autowired

在上面的场景中,使用@Autowired可以很好地工作并提供所需的模块化,但是确切地确定自动装配的 Bean 定义在何处声明仍然有些模棱两可。例如,当开发人员查看ServiceConfig时,您如何确切知道@Autowired AccountRepository bean 的声明位置?它在代码中不是明确的,这可能很好。请记住,Spring 工具套件提供的工具可以呈现图表,显示所有内容的连接方式-这可能就是您所需要的。另外,您的 Java IDE 可以轻松找到AccountRepository类型的所有声明和使用,并将快速向您显示返回该类型的@Bean方法的位置。

如果这种歧义是不可接受的,并且您希望从 IDE 内部直接从一个@Configuration类导航到另一个@Configuration类,请考虑自动装配配置类本身:

@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紧密耦合;这就是权衡。通过使用基于接口或基于抽象类的@Configuration类,可以稍微缓解这种紧密耦合。考虑以下:

@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松散耦合,并且内置的 IDE 工具仍然有用:开发人员很容易获得RepositoryConfig实现的类型层次结构。这样,对@Configuration类及其依赖项进行导航变得与导航基于接口的代码的通常过程没有什么不同。

Tip

如果您想影响某些 Bean 的启动创建 Sequences,请考虑将其中一些声明为@Lazy(用于首次访问而不是在启动时创建)或声明为@DependsOn(在某些其他 Bean 上)(确保在创建特定的其他 Bean 之前)当前的 bean,而不是后者的直接依赖项所暗示的)。

有条件地包含@Configuration 类或@Bean 方法

基于某些任意系统状态,有条件地启用或禁用完整的@Configuration类,甚至单个@Bean方法通常很有用。一个常见的示例是仅在 Spring Environment中启用了特定概要文件时才使用@ProfileComments 来激活 bean(有关详细信息,请参见第 7.13.1 节“ Bean 定义配置文件”)。

@ProfileComments 实际上是使用称为@Conditional的更加灵活的 Comments 来实现的。 @Conditional注解指示在注册@Bean之前应参考的org.springframework.context.annotation.Condition特定实现。

Condition接口的实现只提供返回truefalsematches(…)方法。例如,这是用于@Profile的实际Condition实现:

@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 配置

Spring 的@Configuration类支持并非旨在 100%完全替代 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是配置容器的理想方法。在使用 XML 方便或有必要的情况下,您可以选择:使用ClassPathXmlApplicationContext以“以 XML 为中心”的方式实例化容器,或使用AnnotationConfigApplicationContext@ImportResourceComments 以“以 Java 中心”的方式实例化容器来导入根据需要的 XML。

以 XML 为中心的@Configuration 类的使用

最好从 XML 引导 Spring 容器,并以即席方式包含@Configuration类。例如,在使用 Spring XML 的现有大型代码库中,根据需要创建@Configuration类并将它们包含在现有 XML 文件中会更加容易。在下面,您将找到在这种以 XML 为中心的情况下使用@Configuration类的选项。

请记住,@Configuration类最终只是容器中的 bean 定义。在此示例中,我们创建了一个名为AppConfig@Configuration类,并将其作为<bean/>定义包含在system-test-config.xml中。由于<context:annotation-config/>已打开,因此容器将识别@Configuration注解并正确处理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);
    // ...
}

Note

在上面的system-test-config.xml中,AppConfig <bean/>没有声明id元素。虽然这样做是可以接受的,但由于没有其他 bean 会引用它,因此没有必要,而且不太可能通过名称从容器中显式获取它。与DataSource bean 相同-它只能按类型自动装配,因此并不一定要求使用显式 Bean id

因为@Configuration使用@Component进行元 Comments,所以@ConfigurationComments 的类自动成为组件扫描的候选对象。使用与上述相同的方案,我们可以重新定义system-test-config.xml以利用组件扫描的优势。请注意,在这种情况下,我们不需要显式声明<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 类为中心的 XML 与@ImportResource 的使用

@Configuration类是配置容器的主要机制的应用程序中,仍然可能有必要至少使用一些 XML。在这些情况下,只需使用@ImportResource并仅定义所需的 XML。这样做实现了“以 Java 为中心”的方法来配置容器,并将 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 环境抽象

Environment是集成在容器中的抽象,它为应用程序环境的两个关键方面建模:profilesproperties

  • profile *是命名的逻辑定义的 bean 定义组,仅当给定的 profile 处于活动状态时才向容器注册。可以将 Bean 分配给概要文件,无论是以 XML 定义还是通过 Comments 定义。 Environment对象与配置文件相关的作用是确定当前哪些配置文件(如果有)处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。

属性在几乎所有应用程序中都起着重要作用,并且可能源自多种来源:属性文件,JVM 系统属性,系统环境变量,JNDI,servlet 上下文参数,临时属性对象,Map 等。 Environment对象与属性有关的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性。

7.13.1 Bean 定义配置文件

Bean 定义配置文件是核心容器中的一种机制,允许在不同环境中注册不同的 Bean。 “环境”一词对于不同的用户而言可能意味着不同的含义,并且此功能可以在许多用例中提供帮助,包括:

  • 处理开发中的内存中数据源与在进行 QA 或生产时从 JNDI 查找相同的数据源

  • 仅在将应用程序部署到性能环境中时注册监视基础结构

  • 为 ClientA 和 ClientB 部署注册 bean 的自定义实现

让我们考虑一个实际应用中需要DataSource的用例。在测试环境中,配置可能如下所示:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

现在让我们考虑如何将该应用程序部署到 QA 或生产环境中,假设该应用程序的数据源将在生产应用程序服务器的 JNDI 目录中注册。我们的dataSource bean 现在看起来像这样:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的流逝,Spring 用户已经设计出了多种方法来完成此任务,通常依赖于系统环境变量和包含${placeholder}令牌的 XML <import/>语句的组合,这些语句根据环境变量的值解析为正确的配置文件路径。 Bean 定义配置文件是一项核心容器功能,可提供此问题的解决方案。

如果我们将上述特定于环境的 Bean 定义的示例用例概括起来,最终将需要在某些上下文中而不是在其他上下文中注册某些 Bean 定义。您可能会说您要在情况 A 中注册一个特定的 bean 定义配置文件,在情况 B 中注册一个不同的配置文件。首先让我们看一下如何更新配置以反映这种需求。

@Profile

@ProfileComments 使您可以指示一个或多个指定的配置文件处于活动状态时有资格注册的组件。使用上面的示例,我们可以重写dataSource配置,如下所示:

@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");
    }
}

Note

如前所述,对于@Bean方法,您通常会选择使用程序化 JNDI 查找:使用 Spring 的JndiTemplate/JndiLocatorDelegate帮助器或上面显示的直接 JNDI InitialContext用法,而不使用JndiObjectFactoryBean变体,后者会迫使您将返回类型声明为FactoryBean类型。

@Profile可用作meta-annotation,用于创建自定义组成的 Comments。下面的示例定义了一个自定义@ProductionComments,可以将其替换为@Profile("production")

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

Tip

如果@Configuration类用@Profile标记,则与该类关联的所有@Bean方法和@ImportComments 将被绕过,除非一个或多个指定的配置文件处于活动状态。如果@Component@Configuration类标记为@Profile({"p1", "p2"}),则除非已激活配置文件“ p1”和/或“ p2”,否则不会注册/处理该类。如果给定的配置文件以 NOT 运算符(!)为前缀,则如果该配置文件为“未激活”,则将注册带 Comments 的元素。例如,给定@Profile({"p1", "!p2"}),如果配置文件“ p1”处于活动状态或配置文件“ p2”未处于活动状态,则会进行注册。

@Profile也可以在方法级别声明为仅包含配置类的一个特定 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");
    }
}

Note

对于@Bean方法上的@Profile,可能适用特殊情况:如果具有相同 Java 方法名称的@Bean方法重载(类似于构造函数重载),则必须在所有重载方法上一致声明@Profile条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,不能使用@Profile选择具有特定自变量签名的重载方法;同一 bean 的所有工厂方法之间的解析在创建时遵循 Spring 的构造函数解析算法。

如果要定义具有不同概要文件条件的备用 Bean,请使用不同的 Java 方法名称,它们通过@Bean name 属性指向相同的 Bean 名称,如上面的示例所示。如果参数签名都相同(例如,所有变体都具有 no-arg 工厂方法),则这首先是在有效的 Java 类中表示这种排列的唯一方法(因为只能有一个方法用于特定的名称和参数签名)。

XML Bean 定义配置文件

XML 对应项是<beans>元素的profile属性。上面的示例配置可以用两个 XML 文件重写,如下所示:

<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已被限制为仅允许这些元素作为文件中的最后一个元素。这应该有助于提供灵 Active,而不会引起 XML 文件混乱。

激活 Profile

现在,我们已经更新了配置,我们仍然需要指示 Spring 哪个配置文件处于活动状态。如果我们现在启动示例应用程序,将会看到一个NoSuchBeanDefinitionException抛出,因为容器找不到名为dataSource的 Spring bean。

可以通过多种方式来激活配置文件,但是最直接的方法是针对通过ApplicationContext可用的Environment API 以编程方式进行配置:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

此外,还可以通过spring.profiles.active属性声明性地激活配置文件,该属性可以通过系统环境变量,JVM 系统属性,web.xml中的 servlet 上下文参数或什至作为 JNDI 中的条目(请参见第 7.13.2 节“ PropertySource 抽象”)来指定。在集成测试中,可以通过spring-test模块中的@ActiveProfiles注解来声明活动配置文件(请参阅名为“使用环境配置文件进行上下文配置”的部分)。

注意,配置文件不是“非此即彼”的命题。可以一次激活多个配置文件。通过编程,只需为setActiveProfiles()方法提供多个配置文件名称即可,该方法接受String… varargs:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

以声明方式,spring.profiles.active可以接受以逗号分隔的配置文件名称列表:

-Dspring.profiles.active="profile1,profile2"

Default profile

  • default *配置文件表示默认情况下启用的配置文件。考虑以下:
@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();
    }
}

如果没有任何配置文件处于活动状态,则会创建上面的dataSource;这可以看作是为一个或多个 bean 提供* default 定义的一种方法。如果启用了任何配置文件,则 default *配置文件将不适用。

可以使用Environment上的setDefaultProfiles()或使用spring.profiles.default属性声明性地更改默认配置文件的名称。

7.13.2 PropertySource 抽象

Spring 的Environment抽象提供了对属性源的可配置层次结构的搜索操作。为了全面解释,请考虑以下内容:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码段中,我们看到了一种高级方式来询问 Spring 是否为当前环境定义了foo属性。为了回答这个问题,Environment对象对一组PropertySource对象执行搜索。 PropertySource是对任何键-值对源的简单抽象,而 Spring 的StandardEnvironment配置了两个 PropertySource 对象-一个代表 JVM 系统属性集(* a la * System.getProperties())和一个代表系统环境变量集(* * ala * System.getenv())。

Note

这些默认属性来源针对StandardEnvironment存在,供独立应用程序使用。 StandardServletEnvironment填充了其他默认属性源,包括 servlet 配置和 servlet 上下文参数。 StandardPortletEnvironment同样可以访问 portlet config 和 portlet 上下文参数作为属性源。两者都可以选择启用JndiPropertySource。有关详细信息,请参见 javadocs。

具体来说,当使用StandardEnvironment时,如果在运行时存在foo系统属性或foo环境变量,则对env.containsProperty("foo")的调用将返回 true。

Tip

执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此,如果在对env.getProperty("foo")的调用期间在两个位置都同时设置了foo属性,则系统属性值将“获胜”并优先于环境变量返回。请注意,属性值不会被合并,而是会被前面的条目完全覆盖。

对于常见的StandardServletEnvironment,完整层次结构如下所示,最高优先级条目位于顶部:

  • ServletConfig 参数(如果适用,例如在DispatcherServlet上下文的情况下)

  • ServletContext 参数(web.xml 上下文参数条目)

  • JNDI 环境变量(“ java:comp/env /”条目)

  • JVM 系统属性(“ -D”命令行参数)

  • JVM 系统环境(os 环境变量)

最重要的是,整个机制是可配置的。也许您有想要集成到此搜索中的自定义属性源。没问题-只需实现并实例化自己的PropertySource并将其添加到当前EnvironmentPropertySources集合中:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码中,搜索中以最高优先级添加了MyPropertySource。如果它包含foo属性,它将被检测并在其他PropertySource中的任何foo属性之前返回。 MutablePropertySources API 公开了许多方法,这些方法可以精确地控制属性源集。

7.13.3 @PropertySource

@PropertySourceComments 为将PropertySource添加到 Spring 的Environment提供了一种方便的声明性机制。

给定包含键/值对testbean.name=myTestBean的文件“ app.properties”,以下@Configuration类使用@PropertySource,从而对testBean.getName()的调用将返回“ 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资源位置中存在的任何${…}占位符都将根据已经针对该环境注册的一组属性源进行解析。例如:

@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;
    }
}

假设“ my.placeholder”存在于已注册的资源来源之一中,例如系统属性或环境变量,则占位符将解析为相应的值。如果不是,那么将使用“默认值/路径”作为默认值。如果未指定默认值并且无法解析属性,则将引发IllegalArgumentException

Note

根据 Java 8 约定,@PropertySourceComments 是可重复的。但是,所有此类@PropertySource注解都需要在同一级别上声明:直接在配置类上声明,或在同一自定义注解内作为元注解。不建议将直接 Comments 和元 Comments 混合使用,因为直接 Comments 将有效地覆盖元 Comments。

7.13.4 语句中的占位符解析

从历史上看,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。情况不再如此。由于环境抽象是在整个容器中集成的,因此很容易通过它路由占位符的解析。这意味着您可以按照自己喜欢的任何方式配置解析过程:更改搜索系统属性和环境变量的优先级,或者将其完全删除;将您自己的财产来源添加到适当的组合中。

具体来说,只要在Environment中可用,以下语句无论在customer属性的定义位置如何都有效:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

7.14 注册 LoadTimeWeaver

LoadTimeWeaver被 Spring 使用,以在将类加载到 Java 虚拟机(JVM)中时对其进行动态转换。

要启用加载时编织,请将@EnableLoadTimeWeaving添加到您的@Configuration类中:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

另外,对于 XML 配置,请使用context:load-time-weaver元素:

<beans>
    <context:load-time-weaver/>
</beans>

一旦配置为ApplicationContextApplicationContext内的任何 bean 都可以实现LoadTimeWeaverAware,从而接收对加载时织布器实例的引用。这与Spring 的 JPA 支持结合使用特别有用,在这种情况下,JPA 类转换可能需要进行加载时编织。有关更多详细信息,请查阅LocalContainerEntityManagerFactoryBean javadocs。有关 AspectJ 加载时编织的更多信息,请参见第 11.8.4 节“在 Spring 框架中使用 AspectJ 进行加载时编织”

7.15 ApplicationContext 的其他功能

如本章介绍中所讨论的,org.springframework.beans.factory包提供了用于 Management 和操作 bean 的基本功能,包括以编程方式。 org.springframework.context软件包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口,从而以更多面向应用程序框架的样式提供附加功能。许多人以完全声明性的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依靠诸如ContextLoader之类的支持类来自动实例化ApplicationContext作为 Java EE Web 应用程序正常启动过程的一部分。

为了以更加面向框架的方式增强BeanFactory功能,上下文包还提供了以下功能:

  • 通过MessageSource界面访问 i18n 样式的邮件

  • 通过ResourceLoader界面访问资源,例如 URL 和文件。

  • 通过使用ApplicationEventPublisher接口将事件发布*传递给实现ApplicationListener接口的 bean。

  • 加载多个(分层)上下文,使每个上下文都可以通过HierarchicalBeanFactory接口集中在一个特定的层上,例如应用程序的 Web 层。

7.15.1 使用 MessageSource 进行国际化

ApplicationContext接口扩展了名为MessageSource的接口,因此提供了国际化(i18n)功能。 Spring 还提供了HierarchicalMessageSource接口,该接口可以分层解析消息。这些接口一起提供了 Spring 影响消息解析的基础。这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法。如果找不到针对指定语言环境的消息,则使用默认消息。使用标准库提供的MessageFormat功能,传入的所有参数都将成为替换值。

  • String getMessage(String code, Object[] args, Locale loc):与先前的方法基本相同,但有一个区别:无法指定默认消息;如果找不到该消息,则抛出NoSuchMessageException

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有属性也都包装在名为MessageSourceResolvable的类中,您可以将其与该方法一起使用。

加载ApplicationContext时,它将自动搜索上下文中定义的MessageSource bean。 Bean 必须具有名称messageSource。如果找到了这样的 bean,则对先前方法的所有调用都将委派给消息源。如果找不到消息源,则ApplicationContext尝试查找包含同名 bean 的父对象。如果是这样,它将使用该 bean 作为MessageSource。如果ApplicationContext找不到任何消息源,则实例化一个空的DelegatingMessageSource以便能够接受对上面定义的方法的调用。

Spring 提供了两个MessageSource实现ResourceBundleMessageSourceStaticMessageSource。两者都实现HierarchicalMessageSource以便进行嵌套消息传递。 StaticMessageSource很少使用,但提供了将消息添加到源中的编程方式。下例显示ResourceBundleMessageSource

<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>

在该示例中,假设您在 Classpath 中定义了三个资源束,分别称为formatexceptionswindows。解析消息的任何请求都将通过 ResourceBundles 以 JDK 解析消息的标准方式处理。出于本示例的目的,假设以上两个资源束文件的内容为…

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

下一个示例显示了执行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为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属性引用了许多资源包。列表中传递给basenames属性的三个文件以 Classpath 的根文件形式存在,分别称为format.propertiesexceptions.propertieswindows.properties

下一个示例显示了传递给消息查找的参数。这些参数将被转换为字符串,并插入到查找消息中的占位符中。

<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实现遵循与标准 JDK ResourceBundle相同的语言环境解析和后备规则。简而言之,并 continue 前面定义的示例messageSource,如果要根据英国(en-GB)语言环境解析消息,则可以分别创建名为format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties的文件。

通常,语言环境解析由应用程序的周围环境 Management。在此示例中,手动指定将针对其解析(英国)消息的语言环境。

# 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);
}

运行上述程序的结果输出将是…

Ebagum lad, the 'userDao' argument is required, I say, required.

您还可以使用MessageSourceAware界面获取对已定义的任何MessageSource的引用。创建和配置 Bean 时,在实现MessageSourceAware接口的ApplicationContext中定义的任何 Bean 都会被注入应用程序上下文的MessageSource

Note

*作为ResourceBundleMessageSource的替代方法,Spring 提供了ReloadableResourceBundleMessageSource类。该变体支持相同的包文件格式,但比基于标准 JDK 的ResourceBundleMessageSource实现更灵活.*特别是,它允许从任何 Spring 资源位置(而不仅仅是从 Classpath)读取文件,并支持对包属性文件的热重载(同时有效地将它们缓存在两者之间)。查看ReloadableResourceBundleMessageSource javadocs 以获得详细信息。

7.15.2 标准和自定义事件

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的 bean 部署到上下文中,则每次将ApplicationEvent发布到ApplicationContext时,都会通知该 bean。本质上,这是标准的* Observer *设计模式。

Tip

从 Spring 4.2 开始,事件基础结构得到了显着改进,并提供了annotation-based model以及发布任何任意事件的功能,该对象不一定是从ApplicationEvent扩展的。发布此类对象后,我们会为您包装一个事件。

Spring 提供以下标准事件:

表 7.7. 内置事件

EventExplanation
ContextRefreshedEvent初始化或刷新ApplicationContext时发布,例如,使用ConfigurableApplicationContext接口上的refresh()方法。这里的“已初始化”是指所有 Bean 都已加载,检测到并激活了后处理器 Bean,已预先实例化单例,并且已准备好使用ApplicationContext对象。只要尚未关闭上下文,只要选定的ApplicationContext实际上支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
ContextStartedEventApplicationContext启动时使用ConfigurableApplicationContext界面上的start()方法发布。这里的“已启动”表示所有Lifecycle bean 都收到一个明确的启动 signal。通常,此 signal 用于在显式停止后重新启动 Bean,但也可以用于启动尚未配置为自动启动的组件,例如,尚未在初始化时启动的组件。
ContextStoppedEventApplicationContext停止时使用ConfigurableApplicationContext界面上的stop()方法发布。这里的“停止”表示所有Lifecycle bean 都收到一个明确的停止 signal。停止的上下文可以通过start()调用重新启动。
ContextClosedEventApplicationContext关闭时使用ConfigurableApplicationContext界面上的close()方法发布。这里的“关闭”表示所有单例 bean 都被销毁。封闭的环境已经到了生命的尽头。它不能刷新或重新启动。
RequestHandledEvent一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。该请求在请求完成后*发布。此事件仅适用于使用 Spring 的DispatcherServlet的 Web 应用程序。

您还可以创建和发布自己的自定义事件。这个例子演示了一个简单的类,它扩展了 Spring 的ApplicationEventBase 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()方法。通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为 Spring bean 来完成的。以下示例演示了此类:

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...
    }
}

在配置时,Spring 容器将检测到EmailService实现ApplicationEventPublisherAware并将自动调用setApplicationEventPublisher()。实际上,传入的参数将是 Spring 容器本身。您只需通过其ApplicationEventPublisher接口与应用程序上下文进行交互。

要接收自定义ApplicationEvent,请创建一个实现ApplicationListener的类,并将其注册为 Spring Bean。以下示例演示了此类:

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通常使用自定义事件BlackListEvent的类型进行参数化。这意味着onApplicationEvent()方法可以保持类型安全,从而避免了向下转换的任何需要。您可以根据需要注册任意数量的事件侦听器,但是请注意,默认情况下,事件侦听器会同步接收事件。这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。这种同步和单线程方法的一个优点是,当侦听器收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。如果需要其他用于事件发布的策略,请参阅 Spring 的ApplicationEventMulticaster接口的 javadoc。

以下示例显示了用于注册和配置上述每个类的 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类型的自定义事件。 blackListNotifier bean 被注册为ApplicationListener并因此接收BlackListEvent,此时它可以通知适当的参与者。

Note

Spring 的事件机制旨在在同一应用程序上下文内在 Spring bean 之间进行简单的通信。但是,对于更复杂的企业集成需求,单独维护的Spring Integration项目为构建基于众所周知的 Spring 编程模型的轻量级事件pattern-oriented,事件驱动的体系结构提供了完整的支持。

基于 Comments 的事件侦听器

从 Spring 4.2 开始,可以通过EventListenerComments 在托管 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...
    }
}

如上所示,方法签名再次声明了其侦听的事件类型,但这一次具有灵活的名称,并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析您的通用参数,也可以通过通用类型来缩小事件类型。

如果您的方法应该侦听多个事件,或者如果您想完全不使用任何参数来定义它,则也可以在 Comments 本身上指定事件类型:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

也可以通过定义SpEL expression的 Comments 的condition属性添加其他运行时筛选,该SpEL expression应该匹配以针对特定事件实际调用该方法。

例如,如果事件的content属性等于foo,则可以将我们的通知程序重写为仅被调用:

@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式都针对专用上下文进行评估。下表列出了可用于上下文的项目,因此可以将其用于条件事件处理:

表 7.8. Event SpEL 可用的元数据

NameLocationDescriptionExample
Eventroot object实际的ApplicationEvent#root.event
Arguments arrayroot object用于调用目标的参数(作为数组)#root.args[0]
Argument nameevaluation context任何方法参数的名称。如果由于某种原因名称不可用(例如,没有调试信息),则参数名称也可以在#a<#arg>下使用,其中*#arg *代表参数索引(从 0 开始)。#blEvent#a0(也可以使用#p0#p<#arg>表示法作为别名)。

请注意,即使您的方法签名实际上引用了已发布的任意对象,#root.event仍允许您访问基础事件。

如果需要通过处理另一个事件来发布事件,只需更改方法签名以返回应发布的事件,例如:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

Note

asynchronous listeners不支持此功能。

此新方法将为上述方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果您需要发布多个事件,则只需返回Collection事件即可。

Asynchronous Listeners

如果要特定的侦听器异步处理事件,只需重用常规@Async 支持

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

使用异步事件时,请注意以下限制:

  • 如果事件监听器抛出Exception,它将不会传播给调用者,请检查AsyncUncaughtExceptionHandler以获取更多详细信息。

  • 此类事件侦听器无法发送答复。如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。

Ordering listeners

如果需要在另一个监听器之前调用该监听器,只需将@Order注解添加到方法声明中:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

Generic events

您也可以使用泛型来进一步定义事件的结构。考虑一个EntityCreatedEvent<T>,其中T是创建的实际实体的类型。您可以创建以下侦听器定义,以仅针对Person接收EntityCreatedEvent

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由于类型擦除,这仅在触发的事件解析了事件侦听器所依据的通用参数(如class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })时才有效。

在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像上面的事件一样)。在这种情况下,您可以实现ResolvableTypeProvider指导运行时环境所提供的框架以外的框架:

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()));
    }
}

Tip

这不仅适用于ApplicationEvent,还适用于您作为事件发送的任何任意对象。

7.15.3 方便访问低级资源

为了最佳使用和理解应用程序上下文,用户通常应该熟悉 Spring 的Resource抽象,如第 8 章,资源章中所述。

应用程序上下文是ResourceLoader,可用于加载Resource s。 Resource本质上是 JDK 类java.net.URL的功能更丰富的版本,实际上Resource的实现在适当的地方包装了java.net.URL的实例。 Resource可以从几乎任何位置以透明方式获取低级资源,包括从 Classpath,文件系统位置,标准 URL 可描述的任何位置以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。

您可以配置部署到应用程序上下文中的 Bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时使用应用程序上下文本身作为ResourceLoader进行自动回调。您还可以公开Resource类型的属性,以用于访问静态资源。它们将像其他任何属性一样注入其中。您可以将这些Resource属性指定为简单的 String 路径,并依赖于上下文自动注册的特殊 JavaBean PropertyEditor,以便在部署 Bean 时将这些文本字符串转换为实际的Resource对象。

提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,并且以简单的形式对特定的上下文实现进行了适当的处理。 ClassPathXmlApplicationContext将简单的位置路径视为 Classpath 位置。您也可以使用带有特殊前缀的位置路径(资源字符串)来强制从 Classpath 或 URL 中加载定义,而不管实际的上下文类型如何。

7.15.4 Web 应用程序的便捷 ApplicationContext 实例化

您可以使用ContextLoader声明性地创建ApplicationContext实例。当然,您也可以使用ApplicationContext实现之一以编程方式创建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>

侦听器检查contextConfigLocation参数。如果参数不存在,那么侦听器将使用/WEB-INF/applicationContext.xml作为默认值。当参数* does *存在时,侦听器使用 sched 义的定界符(逗号,分号和空格)分隔字符串,并将这些值用作将在其中搜索应用程序上下文的位置。还支持 Ant 风格的路径模式。对于名称以“ Context.xml”结尾的所有文件(位于“ WEB-INF”目录中),示例为/WEB-INF/*Context.xml;对于“ WEB-INF”的任何子目录中的所有此类文件,示例为/WEB-INF/**/*Context.xml

7.15.5 将 Spring ApplicationContext 部署为 Java EE RAR 文件

可以将 Spring ApplicationContext 部署为 RAR 文件,将上下文及其所有必需的 bean 类和库 JAR 封装在 Java EE RAR 部署单元中。这等效于引导独立的 ApplicationContext,该应用程序仅托管在 Java EE 环境中,并且能够访问 Java EE 服务器功能。 RAR 部署是部署无头 WAR 文件(实际上是没有任何 HTTP 入口点的 WAR 文件,仅用于自举 Java EE 环境中的 Spring ApplicationContext 的情况)的场景的一种更自然的选择。

对于不需要 HTTP 入口点而仅由消息端点和计划的作业组成的应用程序上下文,RAR 部署是理想的选择。在这样的上下文中,Bean 可以使用应用程序服务器资源,例如 JTA 事务 Management 器和绑定 JNDI 的 JDBC DataSources 和 JMS ConnectionFactory 实例,并且还可以通过该平台的 JMX 服务器注册-全部通过 Spring 的标准事务 Management 以及 JNDI 和 JMX 支持工具。应用程序组件还可以通过 Spring 的TaskExecutor抽象与应用程序服务器的 JCA WorkManager 进行交互。

请查看SpringContextResourceAdapter类的 javadoc,以获得 RAR 部署中涉及的配置详细信息。

*为了将 Spring ApplicationContext 作为 Java EE RAR 文件进行简单部署:*将所有应用程序类打包到 RAR 文件中,该文件是具有不同文件 extensions 的标准 JAR 文件。将所有必需的库 JAR 添加到 RAR 归档文件的根目录中。添加“ META-INF/ra.xml”部署 Descriptors(如SpringContextResourceAdapter的 javadoc 中所示)和相应的 Spring XML bean 定义文件(通常为“ META-INF/applicationContext.xml”),并删除生成的 RAR 文件到应用程序服务器的部署目录中。

Note

这种 RAR 部署单位通常是独立的;它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。与基于 RAR 的 ApplicationContext 的交互通常是通过与其他模块共享的 JMS 目标进行的。例如,基于 RAR 的 ApplicationContext 还可以计划一些作业,对文件系统(或类似文件)中的新文件做出反应。如果需要允许来自外部的同步访问,则可以例如导出 RMI 端点,这些端点当然可以由同一台计算机上的其他应用程序模块使用。

7.16 BeanFactory

BeanFactory API 为 Spring 的 IoC 功能提供了基础。它的特定 Contract 主要用于与 Spring 的其他部分以及相关的第三方框架集成,并且其DefaultListableBeanFactory实现是更高级别GenericApplicationContext容器中的关键委托。

BeanFactory和相关的接口(例如BeanFactoryAwareInitializingBeanDisposableBean)是其他框架组件的重要集成点:不需要任何 Comments 甚至是反射,它们允许容器及其组件之间非常有效的交互。应用程序级 Bean 可以使用相同的回调接口,但通常更喜欢通过 Comments 或通过程序配置进行声明式依赖注入。

请注意,核心BeanFactory API 级别及其DefaultListableBeanFactory实现不对配置格式或要使用的任何组件 Comments 进行假设。所有这些类型都是通过XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor等 extensions 引入的,这些 extensions 在共享的BeanDefinition对象上作为核心元数据表示而运行。这就是使 Spring 的容器如此灵活和可扩展的本质。

下一节将说明BeanFactoryApplicationContext容器级别之间的区别以及对引导的影响。

7.16.1 BeanFactory 或 ApplicationContext?

除非您有充分的理由这样做,否则请使用ApplicationContext,并将GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。这些是用于所有常见目的的 Spring 核心容器的主要入口点:加载配置文件,触发 Classpath 扫描,以编程方式注册 Bean 定义和带 Comments 的类。

因为ApplicationContext包含BeanFactory的所有功能,所以通常建议在纯BeanFactory上使用,除非需要完全控制 Bean 处理的情况。在ApplicationContext(例如GenericApplicationContext实现)中,将根据惯例检测到几种类型的 bean(即按 bean 名称或 bean 类型),特别是后处理器,而普通的DefaultListableBeanFactory对于任何特殊 bean 都是不可知的。

对于许多扩展的容器功能(例如 Comments 处理和 AOP 代理),BeanPostProcessor 扩展点是必不可少的。如果仅使用普通DefaultListableBeanFactory,则默认情况下将不会检测到此类后处理器并将其激活。这种情况可能会造成混乱,因为您的 bean 配置实际上并没有错。在这种情况下,需要通过其他设置完全引导容器。

下表列出了BeanFactoryApplicationContext接口和实现提供的功能。

表 7.9. 功能矩阵

FeatureBeanFactoryApplicationContext
Bean instantiation/wiringYesYes
集成生命周期 ManagementNoYes
自动BeanPostProcessor注册NoYes
自动BeanFactoryPostProcessor注册NoYes
方便的MessageSource访问权限(用于内部化)NoYes
内置的ApplicationEvent发布机制NoYes

要使用DefaultListableBeanFactory显式注册 bean 后处理器,您需要以编程方式调用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);

在这两种情况下,显式的注册步骤都是不方便的,这就是为什么在 Spring 支持的应用程序中,各种ApplicationContext变体优于普通DefaultListableBeanFactory的原因,尤其是在典型企业设置中依靠BeanFactoryPostProcessorBeanPostProcessor扩展容器功能时。

Note

AnnotationConfigApplicationContext开箱即用地注册了所有常见的 Comments 后处理器,并且可以通过诸如@EnableTransactionManagement之类的配置 Comments 在封面下引入其他处理器。在 Spring 基于 Comments 的配置模型的抽象级别上,bean 后处理器的概念仅是内部容器详细信息。

7.16.2 胶水代码和邪恶的单例

最好以依赖项注入(DI)样式编写大多数应用程序代码,这种代码是从 Spring IoC 容器中提供的,在创建时具有由容器提供的自己的依赖项,并且完全不了解容器。但是,对于有时需要将其他代码绑定在一起的小的代码粘合层,有时需要对 Spring IoC 容器进行单例(或准单例)样式访问。例如,第三方代码可能会尝试直接构造新对象(Class.forName()样式),而无法从 Spring IoC 容器中获取这些对象。如果由第三方代码构造的对象是小存根或代理,然后使用对 Spring IoC 容器的单例样式访问来获取要委托的真实对象,然后对于大多数代码(从容器中出来的对象)仍然实现了控制反转。因此,大多数代码仍然不知道容器或容器的访问方式,并且与其他代码保持脱钩,从而带来所有好处。 EJB 也可以使用这种存根/代理方法来委托从 Spring IoC 容器检索的普通 Java 实现对象。尽 Management 想情况下,Spring IoC 容器本身不必一定是单例的,但就每个 bean 使用自己的 bean 的内存使用或初始化时间(在 Spring IoC 容器中使用诸如 Hibernate SessionFactory的 bean 而言)而言,可能并不现实非单个 Spring IoC 容器。

在服务定位器样式中查找应用程序上下文有时是访问共享的 SpringManagement 的组件的唯一选择,例如在 EJB 2.1 环境中,或者当您希望在 WAR 文件中共享单个 ApplicationContext 作为 WebApplicationContexts 的父级时。在这种情况下,您应该研究使用Spring 队博客条目中描述的 Util 类ContextSingletonBeanFactoryLocator定位符。


[1]See Background

[2]See 第 7.4.1 节“依赖注入”