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 方法。