Core Technologies

参考文档的这一部分涵盖了 Spring 框架必不可少的所有技术。

其中最重要的是 Spring 框架的控制反转(IoC)容器。对 Spring 框架的 IoC 容器进行彻底处理之后,将全面介绍 Spring 的面向方面编程(AOP)技术。 Spring 框架具有自己的 AOP 框架,该框架在概念上易于理解,并且成功解决了 Java 企业编程中 AOP 要求的 80%的难题。

还提供了 Spring 与 AspectJ 的集成(就功能而言,目前是功能最丰富的 Java,当然还有 Java 企业领域中最成熟的 AOP 实现)。

1. IoC 容器

本章介绍了 Spring 的控制反转(IoC)容器。

1.1. Spring IoC 容器和 Bean 简介

本章介绍了反转控制(IoC)原则的 Spring 框架实现。 (请参阅控制反转。)IoC 也称为依赖项注入(DI)。在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起使用的其他对象) 。然后,容器在创建 bean 时注入那些依赖项。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化或位置的 bean 本身的逆过程(因此称为 Control Inversion)。

org.springframework.beansorg.springframework.context软件包是 Spring Framework 的 IoC 容器的基础。 BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContextBeanFactory的子接口。它增加了:

  • 与 Spring 的 AOP 功能轻松集成

  • 消息资源处理(用于国际化)

  • Event publication

  • 特定于应用程序层的上下文,例如用于 Web 应用程序的WebApplicationContext

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

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

1.2. 容器概述

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

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

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

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

container magic

图 1. Spring IoC 容器

1.2.1. 配置元数据

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

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

Note

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

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

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

这些 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="...">  (1) (2)
        <!-- 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>
  • (1) id属性是标识单个 bean 定义的字符串。
  • (2) class属性定义 bean 的类型并使用完全限定的类名。

id属性的值是指协作对象。在此示例中未显示用于引用协作对象的 XML。有关更多信息,请参见Dependencies

1.2.2. 实例化容器

提供给ApplicationContext构造函数的位置路径是资源字符串,这些资源字符串使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH等)加载配置元数据。

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

Note

了解了 Spring 的 IoC 容器之后,您可能想了解更多有关 Spring 的Resource抽象(如Resources中所述),该抽象为从 URI 语法中定义的位置读取 InputStream 提供了一种方便的机制。特别是,Resource路径用于构建应用程序上下文,如应用程序上下文和资源路径中所述。

以下示例显示了服务层对象(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位置,如previous section所示。或者,使用一个或多个<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.xmlclasspath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常,最好为这样的绝对位置保留一个间接寻址,例如通过在运行时针对 JVM 系统属性解析的“ ${…}”占位符。

名称空间本身提供了导入指令功能。 Spring 提供的一系列 XML 名称空间(例如contextutil名称空间)中提供了超出普通 bean 定义的其他配置功能。

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 定义文件。

1.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 定义)。以下示例显示了 Groovy 配置:

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

最灵活的变体是GenericApplicationContext与阅 Reader 委托组合,例如,对于 XML 文件,是XmlBeanDefinitionReader,如以下示例所示:

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)提供了依赖注入,使您可以通过元数据(例如自动装配)声明对特定 Bean 的依赖。

1.3. Bean 总览

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

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

  • 包限定的类名:通常,定义了 Bean 的实际实现类。

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

  • 引用其他 bean 进行其工作所需的 bean。这些引用也称为协作者或依赖项。

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

该元数据转换为构成每个 bean 定义的一组属性。下表描述了这些属性:

表 1. bean 定义

Property Explained in…
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Constructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
延迟初始化模式 Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

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

Note

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

1.3.1. 命名 bean

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

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

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

Bean 命名约定

约定是在命名 bean 时将标准 Java 约定用于实例字段名称。也就是说,bean 名称以小写字母开头,并从那里用驼峰式大小写。这样的名称的示例包括accountManageraccountServiceuserDaologinController等。

一致地命名 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注解可用于提供别名。有关详情,请参见使用@Bean注解

1.3.2. 实例化 bean

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

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

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

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

内部类名称

如果要为static嵌套类配置 Bean 定义,则必须使用嵌套类的二进制名称。

例如,如果您在com.example包中有一个名为SomeThing的类,并且此SomeThing类具有一个名为OtherThingstatic嵌套类,则 Bean 定义上class属性的值为com.example.SomeThing$OtherThing

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

用构造函数实例化

当通过构造方法创建一个 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()方法必须是静态方法。以下示例显示如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

以下示例显示了可与前面的 bean 定义一起使用的类:

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属性设置工厂方法本身的名称。以下示例显示了如何配置此类 Bean:

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

以下示例显示了相应的 Java 类:

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

以下示例显示了相应的 Java 类:

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 容器中配置并通过instancestatic工厂方法创建对象的 Bean。相反,FactoryBean(注意大小写)是指特定于 Spring 的FactoryBean

1.4. Dependencies

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

1.4.1. 依赖注入

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

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

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

基于构造函数的依赖关系注入

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

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

注意,该类没有什么特别的。它是一个 POJO,不依赖于特定于容器的接口,Base Class 或 注解。

构造函数参数解析

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

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

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

<beans>
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
    </bean>

    <bean id="thingTwo" class="x.y.ThingTwo"/>

    <bean id="thingThree" class="x.y.ThingThree"/>
</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>

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

Note

索引从 0 开始。

构造函数参数名称

您还可以使用构造函数参数名称来消除歧义,如以下示例所示:

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

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

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
基于 Setter 的依赖项注入

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

下面的示例显示只能通过使用纯 setter 注入来依赖注入的类。此类是常规的 Java。它是一个 POJO,不依赖于容器特定的接口,Base Class 或 注解。

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定义,带注解的组件(即以@Component@Controller等进行注解的类)或基于 Java 的@Bean方法。 @Configuration个类。然后将这些源在内部转换为BeanDefinition的实例,并用于加载整个 Spring IoC 容器实例。

基于构造函数或基于 setter 的 DI?

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

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

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

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

依赖关系解决流程

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

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

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

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

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

在创建容器时,Spring 容器会验证每个 bean 的配置。但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的 Bean。范围在Bean Scopes中定义。否则,仅在请求时才创建 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(经典的“养鸡和鸡蛋”场景)。

通常,您可以信任 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"/>

以下示例显示了相应的ExampleBean类:

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

以下示例显示了相应的ExampleBean类:

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

以下示例显示了相应的ExampleBean类:

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/>元素提供,与实际使用构造函数的情况完全相同。 factory 方法返回的类的类型不必与包含static factory 方法的类的类型相同(尽管在此示例中为)。实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用factory-bean属性而不是class属性),因此在此不讨论这些细节。

1.4.2. 依赖性和详细配置

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

直值(Primitives,字符串等)

<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 工具套件),否则错字是在运行时而不是设计时发现的。强烈建议您使用此类 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元素:

<ref bean="someBean"/>

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

<!-- in the parent context -->
<bean id="accountService" class="com.something.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/>元素定义了一个内部 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 和子 bean 定义的 Reader 可能希望先阅读relevant section,然后再 continue。

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

<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。以下 Java 类和 bean 定义显示了如何执行此操作:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>

当准备注入something 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-namespace 允许您使用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-命名空间的 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="thingOne" class="x.y.ThingTwo"/>
    <bean id="thingTwo" class="x.y.ThingThree"/>

    <!-- traditional declaration -->
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
        <constructor-arg value="[emailprotected]"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="thingOne" class="x.y.ThingOne" c:thingTwo-ref="thingTwo" c:thingThree-ref="thingThree" c:email="[emailprotected]"/>

</beans>

c:名称空间使用与p:相同的约定(对于 Bean 引用,尾随-ref)以其名称设置构造函数参数。同样,即使未在 XSD 模式中定义它(也存在于 Spring 内核中)也需要声明它。

对于极少数情况下无法使用构造函数自变量名称的情况(通常,如果字节码是在没有调试信息的情况下编译的),可以对参数索引使用后备,如下所示:

<!-- c-namespace index declaration -->
<bean id="thingOne" class="x.y.ThingOne" c:_0-ref="thingTwo" c:_1-ref="thingThree"/>

Note

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

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

复合属性名称

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

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

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

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

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

1.4.4. 懒初始化 bean

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

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

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.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>

1.4.5. 自动布线合作者

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

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

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

使用基于 XML 的配置元数据时(请参见Dependency Injection),可以使用<bean/>元素的autowire属性为 bean 定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个 bean 指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:

表 2.自动装配模式

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

byTypeconstructor自动装配模式下,您可以连接阵列和键入的集合。在这种情况下,将提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。如果预期的密钥类型为String,则可以自动连接强类型的Map实例。自动连接的Map实例的值包含与期望的类型匹配的所有 bean 实例,并且Map实例的键包含相应的 Bean 名称。

自动装配的局限性和缺点

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

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

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

  • 自动装配不如显式接线精确。尽管如前所述,Spring 还是小心避免在可能产生意外结果的模棱两可的情况下进行猜测。 SpringManagement 的对象之间的关系不再明确记录。

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

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

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

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

  • next section中所述,通过将其autowire-candidate属性设置为false来避免自动装配 bean 定义。

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

  • 基于注解的容器配置中所述,通过基于注解的配置实现更细粒度的控件。

从自动装配中排除 Bean

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

Note

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

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

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

1.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 容器的一项高级功能,使您可以干净地处理此用例。

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

查找方法注入

查找方法注入是容器重写容器 Management 的 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();
}

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

<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 的相同实例。

另外,在基于注解的组件模型中,您可以通过@Lookup注解 声明一个查找方法,如以下示例所示:

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

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

Tip

访问范围不同的目标 bean 的另一种方法是ObjectFactory/Provider注入点。参见范围 bean 作为依赖项

您可能还会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)很有用。

任意方法替换

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

借助基于 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。

1.5. bean 范围

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

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

下表描述了受支持的范围:

表 3. Bean 作用域

Scope Description
singleton (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype 将单个 bean 定义的作用域限定为任意数量的对象实例。
request 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session 将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application 将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket 将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

Note

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

1.5.1. 单例范围

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

换句话说,当您定义一个 bean 定义并且其作用域为单例时,Spring IoC 容器将为该 bean 定义所定义的对象创建一个实例。该单个实例存储在此类单例 bean 的高速缓存中,并且对该命名 bean 的所有后续请求和引用都返回该高速缓存的对象。下图显示了单例作用域如何工作:

singleton

Spring 的 singleton bean 的概念与“四人帮”(GoF)模式一书中定义的 singleton 模式不同。 GoF 单例对对象的范围进行硬编码,以使每个 ClassLoader 只能创建一个特定类的一个实例。最好将 Spring 单例的范围描述为每个容器和每个 bean。这意味着,如果您在单个 Spring 容器中为特定类定义一个 bean,则 Spring 容器将创建该 bean 定义所定义的类的一个且只有一个实例。 Singleton 范围是 Spring 中的默认范围。要将 bean 定义为 XML 中的单例,可以定义 bean,如以下示例所示:

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

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

1.5.2. 原型范围

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

下图说明了 Spring 原型范围:

prototype

(数据访问对象(DAO)通常不配置为原型,因为典型的 DAO 不拥有任何对话状态。对于我们而言,重用单例图的核心更为容易。)

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

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

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

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

1.5.3. 具有原型 Bean 依赖关系的 Singleton Bean

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

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

1.5.4. 请求,会话,应用程序和 WebSocket 范围

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

初始 Web 配置

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

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

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

如果您使用 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 应用程序的过滤器部分:

<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.something.LoginAction" scope="request"/>

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

使用注解驱动的组件或 Java 配置时,可以使用@RequestScope注解 将组件分配给request范围。以下示例显示了如何执行此操作:

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

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

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

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

使用注解驱动的组件或 Java 配置时,可以使用@SessionScope注解 将组件分配给session范围。

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

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

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

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

使用注解驱动的组件或 Java 配置时,可以使用@ApplicationScope注解 将组件分配给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()调用在需要时每次按需检索当前实例-holding 无需保留该实例或对其进行存储分别。

作为扩展变体,您可以声明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.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> (1)
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
  • (1) 定义代理的行。

要创建这样的代理,请将子<aop:scoped-proxy/>元素插入到作用域 bean 定义中(请参见选择要创建的代理类型基于 XML 模式的配置)。为什么在requestsession和自定义范围级别定义的 bean 定义需要<aop:scoped-proxy/>元素?考虑以下单例 bean 定义,并将其与需要为上述范围定义的内容进行对比(请注意,下面的userPreferences bean 定义不完整):

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

<bean id="userManager" class="com.something.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-scoped bean 注入到协作对象中时,您需要以下(正确和完整)配置,如以下示例所示:

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

<bean id="userManager" class="com.something.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.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

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

有关选择基于类或基于接口的代理的更多详细信息,请参见Proxying Mechanisms

1.5.5. 自定义范围

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

创建自定义范围

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

Scope接口有四种方法可以从范围中获取对象,从范围中删除对象,然后销毁它们。

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

Object get(String name, ObjectFactory objectFactory)

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

Object remove(String name)

以下方法注册在销毁作用域或销毁作用域中的指定对象时作用域应执行的回调:

void registerDestructionCallback(String name, Runnable destructionCallback)

有关销毁回调的更多信息,请参见javadoc或 Spring 范围实现。

以下方法获取基础范围的会话标识符:

String getConversationId()

每个范围的标识符都不相同。对于会话范围的实现,此标识符可以是会话标识符。

使用自定义范围

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

void registerScope(String scopeName, Scope scope);

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

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

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

Note

下一个示例使用SimpleThreadScope,它已包含在 Spring 中,但默认情况下未注册。这些说明与您自己的自定义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="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>

Note

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

1.6. 自定义 bean 的性质

Spring 框架提供了许多接口,可用于自定义 Bean 的性质。本节将它们分组如下:

1.6.1. 生命周期回调

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

Tip

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

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

在内部,Spring 框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring 默认不提供,则您可以自己实现BeanPostProcessor。有关更多信息,请参见集装箱延伸点

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

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

Initialization Callbacks

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

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到 Spring。另外,我们建议使用@PostConstruct注解或指定 POJO 初始化方法。对于基于 XML 的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。通过 Java 配置,可以使用@BeaninitMethod属性。参见接收生命周期回调。考虑以下示例:

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

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

前面的示例与下面的示例(由两个清单组成)几乎具有完全相同的效果:

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

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

但是,前面两个示例中的第一个示例并未将代码耦合到 Spring。

Destruction Callbacks

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

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到 Spring。另外,我们建议使用@PreDestroy注解或指定 bean 定义支持的通用方法。使用基于 XML 的配置元数据时,可以在<bean/>上使用destroy-method属性。通过 Java 配置,可以使用@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 容器配置为“查找”命名的初始化,并销毁每个 bean 上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而不必为每个 bean 定义配置init-method="init"属性。 Spring IoC 容器在创建 bean 时(并根据标准生命周期回调协定described previously)调用该方法。此功能还对初始化和销毁方法回调强制执行一致的命名约定。

假设您的初始化回调方法命名为init(),而 destroy 回调方法命名为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.");
        }
    }
}

然后,您可以在类似于以下内容的 Bean 中使用该类:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.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 生命周期行为:

Note

如果为一个 bean 配置了多个生命周期机制,并且为每个机制配置了不同的方法名称,则将按照此注解后列出的 Sequences 执行每个已配置的方法。但是,如果为多个生命周期机制中的多个生命周期配置了相同的方法名称(例如,对于初始化方法使用init()),则该方法将执行一次,如preceding section中所述。

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

  • @PostConstruct注解 的方法

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

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

销毁方法的调用 Sequences 相同:

  • @PreDestroy注解 的方法

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

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

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 SpringManagement 的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收到启动和停止 signal 时(例如,对于运行时的停止/重新启动场景),它将把这些调用级联到在该上下文中定义的所有Lifecycle实现。它通过委托LifecycleProcessor来完成此任务,如以下清单所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

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

Tip

请注意,常规org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。为了对特定 bean 的自动启动进行精细控制(包括启动阶段),请考虑实现org.springframework.context.SmartLifecycle

另外,请注意,不能保证会在销毁之前发出停止通知。在常规关闭时,在传播常规销毁回调之前,所有Lifecycle bean 都首先收到停止通知。但是,在上下文生存期内的热刷新或中止的刷新尝试中,仅调用 destroy 方法。

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

public interface Phased {

    int getPhase();
}

以下清单显示了SmartLifecycle接口的定义:

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接口的默认实现DefaultLifecycleProcessorawait 每个阶段内的对象组的超时值,以调用该回调。默认的每阶段超时为 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()方法的显式调用(与上下文刷新不同,对于标准的上下文实现,上下文启动不会自动发生)。 phase值和任何“依赖”关系确定启动 Sequences,如前所述。

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

Note

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

如果您在非 Web 应用程序环境中(例如,在富 Client 端桌面环境中)使用 Spring 的 IoC 容器,请向 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...
    }
}

1.6.2. ApplicationContextAware 和 BeanNameAware

ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,该实例将获得对该ApplicationContext的引用。以下清单显示了ApplicationContextAware接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

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

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

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

1.6.3. 其他感知接口

除了ApplicationContextAwareBeanNameAware(已讨论earlier)之外,Spring 还提供了Aware接口范围,这些接口使 Bean 向容器指示它们需要某种基础结构依赖性。通常,该名称很好地表明了依赖项类型。下表总结了最重要的Aware接口:

表 4.感知接口

Name Injected Dependency Explained in…
ApplicationContextAware 声明ApplicationContext ApplicationContextAware 和 BeanNameAware
ApplicationEventPublisherAware 附件ApplicationContext的事件发布者。 ApplicationContext 的其他功能
BeanClassLoaderAware 类加载器,用于加载 Bean 类。 Instantiating Beans
BeanFactoryAware 声明BeanFactory ApplicationContextAware 和 BeanNameAware
BeanNameAware 声明 bean 的名称。 ApplicationContextAware 和 BeanNameAware
BootstrapContextAware 运行容器的资源适配器BootstrapContext。通常仅在支持 JCA 的ApplicationContext实例中可用。 JCA CCI
LoadTimeWeaverAware 定义的编织器,用于在加载时处理类定义。 在 Spring Framework 中使用 AspectJ 进行加载时编织
MessageSourceAware 解决消息的已配置策略(支持参数化和国际化)。 ApplicationContext 的其他功能
NotificationPublisherAware Spring JMX 通知发布者。 Notifications
ResourceLoaderAware 配置的加载程序,用于对资源的低级别访问。 Resources
ServletConfigAware 当前容器运行的ServletConfig。仅在可感知网络的 Spring ApplicationContext中有效。 Spring MVC
ServletContextAware 当前容器运行的ServletContext。仅在可感知网络的 Spring ApplicationContext中有效。 Spring MVC

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

1.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">  (1)
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
  • (1) 请注意parent属性。

如果未指定子 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。

1.8. 集装箱延伸点

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

1.8.1. 使用 BeanPostProcessor 自定义 Bean

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

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

Note

BeanPostProcessor个实例在 bean(或对象)实例上运行。也就是说,Spring IoC 容器实例化一个 bean 实例,然后BeanPostProcessor个实例完成其工作。

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

要更改实际的 bean 定义(即定义 bean 的蓝图),您需要使用BeanFactoryPostProcessor,如使用 BeanFactoryPostProcessor 自定义配置元数据中所述。

org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。当此类被注册为容器的后处理器时,对于容器创建的每个 bean 实例,后处理器都会在容器初始化方法(例如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 的初始化,因此这种提早类型检测至关重要。

Programmatically registering BeanPostProcessor instances

推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以使用addBeanPostProcessor通过ConfigurableBeanFactory以编程方式注册它们。当您需要在注册之前评估条件逻辑,甚至需要跨层次结构的上下文复制 Bean 后处理器时,这将非常有用。但是请注意,以编程方式添加的BeanPostProcessor实例不遵守Ordered接口。在这里,注册的 Sequences 决定了执行的 Sequences。另请注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前进行处理,而不考虑任何明确的 Sequences。

BeanPostProcessor instances and AOP auto-proxying

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

对于任何此类 bean,您应该看到一条参考日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

如果您通过自动装配或@Resource将 Bean 连接到BeanPostProcessor(可能会退回到自动装配),则 Spring 在搜索类型匹配的依赖项候选对象时可能会访问意外的 bean,因此使它们不符合自动代理或其他类型的条件。 Bean 后处理。例如,如果您有一个用@Resource注解 的依赖项,其中字段或设置器名称不直接与 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;
    }
}

以下beans元素使用InstantiationTracingBeanPostProcessor

<?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 动态语言支持在标题为动态语言支持的一章中有详细介绍。)

以下 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 : org.springframework.scripting.groovy.GroovyMesse[emailprotected]
[emailprotected]
示例:RequiredAnnotationBeanPostProcessor

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

1.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接口的 javadoc。

Note

如果要更改实际的 bean 实例(即,从配置元数据创建的对象),则需要使用BeanPostProcessor(在使用 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

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

考虑以下基于 XML 的配置元数据片段,其中定义了带有占位符值的DataSource

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/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>

该示例显示了从外部Properties文件配置的属性。在运行时,将PropertyPlaceholderConfigurer应用于替换数据源的某些属性的元数据。将要替换的值指定为${property-name}形式的占位符,该形式遵循 Ant 和 log4j 和 JSP EL 样式。

实际值来自标准 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/something/jdbc.properties"/>

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

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

  • fallback(1):检查系统属性是否在指定的属性文件中不可解析。这是默认值。

  • override(2):在尝试指定的属性文件之前,请先检查系统属性。这使系统属性可以覆盖任何其他属性源。

有关更多信息,请参见PropertyPlaceholderConfigurer javadoc。

Tip

您可以使用PropertyPlaceholderConfigurer来替换类名,这在您必须在运行时选择特定的实现类时有时很有用。以下示例显示了如何执行此操作:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/something/strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.something.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

下面的清单显示了格式的示例:

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

此示例文件可与包含定义为dataSource且具有driverurl属性的 Bean 的容器定义一起使用。

只要路径的每个组成部分(最终属性被覆盖)之外的所有组成部分都已经为非空(可能是由构造函数初始化),则也支持复合属性名。在以下示例中,将tom bean 的fred属性的bob属性的sammy属性设置为标量值123

tom.fred.bob.sammy=123

Note

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

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

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

1.8.3. 使用 FactoryBean 自定义实例化逻辑

您可以为本身就是工厂的对象实现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 Framework 中的许多地方都使用过。 Spring 本身附带了FactoryBean接口的 50 多种实现。

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

1.9. 基于注解的容器配置

注解 在配置 Spring 方面比 XML 更好吗?

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

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

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

Note

注解注入在 XML 注入之前执行。因此,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 上的 注解。这意味着,如果您将<context:annotation-config/>放在WebApplicationContext中而不是DispatcherServlet,则它仅检查控制器中的@Autowired bean,而不检查服务。有关更多信息,请参见The DispatcherServlet

1.9.1. @Required

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

此注解指示必须在配置时通过 bean 定义中的显式属性值或通过自动装配来填充受影响的 bean 属性。如果受影响的 bean 属性尚未填充,则容器将引发异常。这允许急切和显式的故障,避免以后再出现NullPointerException个实例等。我们仍然建议您将 assert 放入 bean 类本身中(例如,放入 init 方法中)。这样做会强制执行那些必需的引用和值,即使您在容器外部使用该类也是如此。

1.9.2. 使用@Autowired

Note

在本节中包含的示例中,可以使用 JSR 330 的@Inject注解 代替 Spring 的@Autowired注解。有关更多详细信息,请参见here

您可以将@Autowired注解 应用于构造函数,如以下示例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

Note

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

您还可以将@Autowired注解 应用于“传统” setter 方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

您还可以将注解应用于具有任意名称和多个参数的方法,如以下示例所示:

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)由用于@Autowired注解 的注入点的类型一致地声明。否则,由于在运行时找不到类型匹配,注入可能会失败。

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

您还可以通过将注解添加到需要该类型数组的字段或方法中,来提供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或标准@Priority注解。否则,它们的 Sequences 将遵循容器中相应目标 bean 定义的注册 Sequences。

您可以在目标类级别和@Bean方法上声明@Order注解,可能通过单个 bean 定义来声明(如果使用同一 bean 类的多个定义)。 @Order值可能会影响注入点的优先级,但是要注意,它们不会影响单例启动 Sequences,这是由依赖关系和@DependsOn声明确定的正交关注点。

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

只要预期的密钥类型为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 可用,自动装配就会失败。默认行为是将带注解的方法,构造函数和字段视为指示所需的依赖项。在下面的示例中,您可以按照说明更改此行为:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

Note

每个类仅可以将一个带注解的构造函数标记为必需,但可以对多个非必需的构造函数进行 注解。在这种情况下,每个候选对象都将被考虑在内,并且 Spring 使用最贪婪的构造函数,其依赖关系可以得到满足-即具有最多参数的构造函数。

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

另外,您可以通过 Java 8 的java.util.Optional表示特定依赖项的非必需性质,如以下示例所示:

public class SimpleMovieLister {

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

从 Spring Framework 5.0 开始,您还可以使用@Nullable注解(在任何包中是任何一种形式,例如,来自 JSR-305 的javax.annotation.Nullable):

public class SimpleMovieLister {

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

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

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

Note

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

1.9.3. 使用@Primary 微调基于注解的自动装配

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

考虑以下将firstMovieCatalog定义为主MovieCatalog的配置:

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

使用前面的配置,下面的MovieRecommenderfirstMovieCatalog自动连接:

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>

1.9.4. 使用限定符对基于注解的自动装配进行微调

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

public class MovieRecommender {

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

    // ...
}

您还可以在各个构造函数参数或方法参数上指定@Qualifier注解,如以下示例所示:

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 定义。

<?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"/> (1)

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

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

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

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

</beans>
  • (1) 具有main限定符值的 Bean 与限定有相同值的构造函数自变量连接。
  • (2) 具有action限定符值的 Bean 与限定有相同值的构造函数参数连接。

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

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

Tip

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

就是说,如果您打算按名称表示注解驱动的注入,则不要主要使用@Autowired,即使它能够在类型匹配的候选对象中按 bean 名称进行选择。而是使用 JSR-250 @Resource注解,该注解的语义定义是通过其唯一名称来标识特定目标组件,而声明的类型与匹配过程无关。 @Autowired具有不同的语义:按类型选择候选 Bean 之后,仅在那些类型选择的候选中考虑指定的String限定符值(例如,将account限定符与标记有相同限定符标签的 Bean 匹配)。

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

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

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

您可以创建自己的自定义限定符 注解。为此,请定义一个注解并在定义中提供@Qualifier注解,如以下示例所示:

@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以匹配您的自定义限定符 注解。该类型与注解的完全限定的类名匹配。另外,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面的示例演示了两种方法:

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

Classpath 扫描和托管组件中,您可以看到基于注解的替代方法,以 XML 形式提供限定符元数据。具体来说,请参见提供带注解的限定符元数据

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

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

}

然后将注解添加到要自动装配的字段或属性,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}
  • (1) 此行添加了@Offline注解。

现在,bean 定义只需要一个限定符type,如以下示例所示:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> (1)
    <!-- inject any dependencies required by this bean -->
</bean>
  • (1) 此元素指定限定符。

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

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

    String genre();

    Format format();
}

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

public enum Format {
    VHS, DVD, BLURAY
}

要自动装配的字段将用定制限定符进行 注解,并包括两个属性genreformat的值,如以下示例所示:

public class MovieRecommender {

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

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

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

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

    // ...
}

最后,bean 定义应包含匹配的限定符值。此示例还演示了可以使用 bean 元属性代替<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>

1.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接口,并且通用接口用作限定符,如以下示例所示:

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

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

自动连接列表,Map实例和数组时,通用限定符也适用。以下示例将自动连接通用List

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

1.9.6. 使用 CustomAutowireConfigurer

CustomAutowireConfigurerBeanFactoryPostProcessor,即使您没有使用 Spring 的@Qualifier注解 对您自己的自定义限定符注解类型进行注册,您也可以使用它们。以下示例显示了如何使用CustomAutowireConfigurer

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

  • @Qualifier注解 和在CustomAutowireConfigurer中注册的所有自定义注解的存在

当多个 bean 可以作为自动装配候选者时,确定“主要”的步骤如下:如果候选者中恰好有一个 bean 定义具有primary属性设置为true,则将其选中。

1.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 名称。换句话说,它遵循名称语义,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") (1)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
  • (1) 此行注入@Resource

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

Note

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

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

因此,在下面的示例中,customerPreferenceDao字段首先查找名为 customerPreferenceDao 的 bean,然后回退到类型CustomerPreferenceDao的主类型匹配:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}
  • (1) context字段是根据已知的可解决依赖项类型ApplicationContext注入的。

1.9.8. 使用@PostConstruct 和@PreDestroy

CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还可以识别 JSR-250 生命周期 注解。在 Spring 2.5 中引入了对这些注解的支持,为initialization callbacksdestruction callbacks中描述的注解提供了另一种选择。假设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

有关组合各种生命周期机制的效果的详细信息,请参见组合生命周期机制

1.10. Classpath 扫描和托管组件

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

Note

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

1.10.1. @Component 和其他构造型 注解

@Repository注解 是满足存储库的角色或构造型(也称为数据访问对象或 DAO)的任何类的标记。如Exception Translation中所述,此标记的用途是自动翻译异常。

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

1.10.2. 使用元注解和组合 注解

Spring 提供的许多注解都可以在您自己的代码中用作元 注解。元注解是可以应用于另一个注解的 注解。例如,提到的earlier@Service注解 使用@Component进行元 注解,如以下示例所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {

    // ....
}
  • (1) Component导致@Service的处理方式与@Component相同。

您还可以结合使用元注解来创建“组合 注解”。例如,Spring MVC 的@RestController注解 由@Controller@ResponseBody组成。

此外,组合注解可以选择从元注解中重新声明属性,以允许自定义。当您只想公开元注解属性的子集时,这可能特别有用。例如,Spring 的@SessionScope注解 将作用域名称硬编码为session,但仍允许自定义proxyMode。以下清单显示了SessionScope注解的定义:

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

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

}

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

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

您还可以覆盖proxyMode的值,如以下示例所示:

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

有关更多详细信息,请参见Spring注解 编程模型 Wiki 页面。

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

为简洁起见,前面的示例可能使用了注解的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 任务的仅文件开关。另外,在某些环境中(例如,JDK 1.7.0_45 及更高版本上的独立应用程序(在清单中要求“受信任的库”设置”),见http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources),可能不会基于安全策略公开 Classpath 目录。

在 JDK 9 的模块路径(Jigsaw)上,Spring 的 Classpath 扫描通常可以按预期进行。但是,请确保将组件类导出到module-infoDescriptors 中。如果您希望 Spring 调用类的非公共成员,请确保它们已“打开”(即,它们在module-infoDescriptors 中使用opens声明而不是exports声明)。

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

Note

您可以通过包含注解设置属性false来禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册。

1.10.4. 使用过滤器自定义扫描

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

表 5.过滤器类型

Filter Type Example Expression Description
annotation (default) org.example.SomeAnnotation 在目标组件的类型级别上存在的 注解。
assignable org.example.SomeClass 目标组件可分配给(扩展或实现)的类(或接口)。
aspectj org.example..*Service+ 目标组件要匹配的 AspectJ 类型表达式。
regex org\.example\.Default.* 要与目标组件类名称匹配的正则表达式。
custom org.example.MyTypeFilter org.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

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

1.10.5. 在组件中定义 Bean 元数据

Spring 组件还可以将 bean 定义元数据贡献给容器。您可以使用与@Configuration带注解的类中定义 Bean 元数据相同的@Bean注解 来执行此操作。以下示例显示了如何执行此操作:

@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()@Bean注解 标识工厂方法和其他 bean 定义属性,例如通过@Qualifier注解 的限定符值。可以指定的其他方法级别注解是@Scope@Lazy和自定义限定符 注解。

Tip

除了用于组件初始化的角色外,还可以将@Lazy注解 放置在标有@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> }定义属性的值。对于@Value注解,表达式解析器已预先配置为在解析表达式文本时查找 bean 名称。

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

@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

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

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

1.10.6. 命名自动检测的组件

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

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

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

Note

如果不想依赖默认的 Bean 命名策略,则可以提供自定义 Bean 命名策略。首先,实现BeanNameGenerator接口,并确保包括默认的 no-arg 构造函数。然后,在配置扫描器时提供完全限定的类名,如以下示例注解和 Bean 定义所示:

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

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

1.10.7. 提供自动检测组件的范围

通常,与 SpringManagement 的组件一样,自动检测到的组件的默认且最常见的范围是singleton。但是,有时您需要由@Scope注解 指定的其他范围。您可以在注解中提供范围的名称,如以下示例所示:

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

Note

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

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

Note

要提供用于范围解析的自定义策略,而不是依赖于基于注解的方法,您可以实现ScopeMetadataResolver接口。确保包括默认的无参数构造函数。然后,可以在配置扫描程序时提供完全限定的类名,如以下注解和 Bean 定义示例所示:

@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 作为依赖项中描述。为此,在 component-scan 元素上可以使用 scoped-proxy 属性。三个可能的值是:nointerfacestargetClass。例如,以下配置产生标准的 JDK 动态代理:

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

1.10.8. 提供带注解的限定符元数据

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

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

Note

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

1.10.9. 生成候选组件的索引

尽管 Classpath 扫描非常快,但可以通过在编译时创建候选静态列表来提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用此机制,因为当ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描 Classpath。

要生成索引,请向每个包含组件的模块添加附加依赖关系,这些组件是组件扫描指令的目标。以下示例显示了如何使用 Maven 进行操作:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.1.3.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

以下示例显示了如何使用 Gradle 进行操作:

dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.1.3.RELEASE")
}

该过程将生成 jar 文件中包含的META-INF/spring.components文件。

Note

在 IDE 中使用此模式时,必须将spring-context-indexer注册为注解处理器,以确保在更新候选组件时索引是最新的。

Tip

当在 Classpath 上找到META-INF/spring.components时,索引将自动启用。如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建,则可以通过将spring.index.ignore设置为true来回退到常规的 Classpath 安排(好像根本没有索引)。系统属性或 Classpath 根目录下的spring.properties文件中。

1.11. 使用 JSR 330 标准 注解

从 Spring 3.0 开始,Spring 提供对 JSR-330 标准 注解(依赖注入)的支持。这些注解的扫描方式与 Spring注解 的扫描方式相同。要使用它们,您需要在 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>

1.11.1. @Inject 和@Named 的依赖注入

可以使用@javax.inject.Inject代替@Autowired,如下所示:

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属性。以下一对示例显示了如何使用@Inject@Nullable

public class SimpleMovieLister {

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

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

1.11.2. @Named 和@ManagedBean:@Component注解 的标准等效项

代替@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时,可以使用与使用 Spring注解 完全相同的方式来使用组件扫描,如以下示例所示:

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

Note

@Component相反,JSR-330 @Named和 JSR-250 ManagedBean注解 是不可组合的。您应该使用 Spring 的构造型模型来构建自定义组件 注解。

1.11.3. JSR-330 标准注解的局限性

当使用标准注解时,您应该知道某些重要功能不可用,如下表所示:

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

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

1.12. 基于 Java 的容器配置

本节介绍如何在 Java 代码中使用注解来配置 Spring 容器。它包括以下主题:

1.12.1. 基本概念:@Bean 和@Configuration

Spring 的新 Java 配置支持中的主要工件是@Configuration注解 的类和@Bean注解 的方法。

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

@Configuration注解 类表示该类的主要目的是作为 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>

完整的@Configuration 与“精简” @Bean 模式?

当在未使用@Configuration注解 的类中声明@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 容器的各种方法。

1.12.2. 使用 AnnotationConfigApplicationContext 实例化 Spring 容器

以下各节记录了 Spring 3.0 中引入的 Spring 的AnnotationConfigApplicationContext。这种通用的ApplicationContext实现不仅可以接受@Configuration类作为 Importing,而且还可以接受普通@Component类和带有 JSR-330 元数据注解的类。

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

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

Simple Construction

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

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 带注解的类作为 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 依赖项注入 注解,例如@Autowired

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

您可以使用 no-arg 构造函数实例化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 ...)启用组件扫描

要启用组件扫描,您可以如下注解您的@Configuration类:

@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig  {
    ...
}
  • (1) 此注解启用组件扫描。

Tip

有经验的 Spring 用户可能熟悉 Spring 的context:名称空间中的等效 XML 声明,如以下示例所示:

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

在前面的示例中,扫描com.acme包以查找带有@Component注解 的任何类,并将这些类注册为容器内的 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,因此它们是组件扫描的候选对象。在前面的示例中,假定在com.acme包(或下面的任何包)中声明了AppConfig,则在对scan()的调用期间将其拾取。在refresh()上,将处理其所有@Bean方法并将其注册为容器内的 bean 定义。

通过 AnnotationConfigWebApplicationContext 支持 Web 应用程序

AnnotationConfigWebApplicationContext可提供AnnotationConfigApplicationContextWebApplicationContext变体。在配置 Spring ContextLoaderListener servlet 侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。以下web.xml片段配置了典型的 Spring MVC Web 应用程序(请注意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>

1.12.3. 使用@Bean注解

@Bean是方法级 注解,是 XML <bean/>元素的直接类似物。注解 支持<bean/>提供的某些属性,例如:* init-method * destroy-method * autowiring * name

您可以在@Configuration注解 的类或@Component注解 的类中使用@Bean注解。

声明一个 Bean

要声明 bean,可以使用@Bean注解 对方法进行 注解。您可以使用此方法在指定为该方法的返回值的类型的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)。然后,使用只对容器知道一次的完整类型(TransferServiceImpl),实例化受影响的单例 bean。非惰性单例 bean 根据其声明 Sequences 实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试通过未声明的类型进行匹配(例如@Autowired TransferServiceImpl,该实例仅在实例化transferService bean 时才解析.)。

Tip

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

Bean Dependencies

带有@Bean注解 的方法可以具有任意数量的参数,这些参数描述构建该 bean 所需的依赖关系。例如,如果我们的TransferService要求AccountRepository,则可以使用方法参数实现该依赖关系,如以下示例所示:

@Configuration
public class AppConfig {

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

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

接收生命周期回调

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

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

也完全支持*Aware接口的标准集合(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。

@Bean注解 支持指定任意的初始化和销毁回调方法,就像bean元素上的 Spring XML 的init-methoddestroy-method属性一样,如以下示例所示:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

Note

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

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

以下示例显示了如何防止DataSource的自动销毁回调:

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

另外,对于@Bean方法,通常使用程序化 JNDI 查找,方法是使用 Spring 的JndiTemplateJndiLocatorDelegate帮助器,或者直接使用 JNDI InitialContext用法,而不使用JndiObjectFactoryBean变体(这将迫使您将返回类型声明为FactoryBean类型,而不是实际的目标。类型,使其更难以在打算引用此处提供的资源的其他@Bean方法中用于交叉引用调用。

对于前面注解中的示例中的BeanOne,在构造期间直接调用init()方法同样有效,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

Tip

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

指定 Bean 范围

Spring 包含@Scope注解,以便您可以指定 bean 的范围。

使用@Scope 注解

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

默认范围是singleton,但是您可以使用@Scope注解覆盖它,如以下示例所示:

@Configuration
public class MyConfiguration {

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

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

如果使用 Java 从 XML 参考文档(请参阅scoped proxies)将作用域代理示例移植到我们的@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 = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
Bean Aliasing

Naming Beans中所述,有时希望为单个 bean 提供多个名称,否则称为 bean 别名。 @Bean注解的name属性为此目的接受一个 String 数组。以下示例说明如何为 bean 设置多个别名:

@Configuration
public class AppConfig {

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

有时,提供有关 bean 的更详细的文本描述会很有帮助。当出于监视目的而暴露(可能通过 JMX)bean 时,这尤其有用。

要将说明添加到@Bean,可以使用@Description注解,如以下示例所示:

@Configuration
public class AppConfig {

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

1.12.4. 使用@Configuration 注解

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

注入 Bean 间的依赖关系

当 bean 相互依赖时,表示这种依赖关系就像让一个 bean 方法调用另一个一样简单,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中,beanOne通过构造函数注入接收对beanTwo的引用。

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

考虑下面的示例,该示例显示了一个被两次调用的@Bean注解 方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()clientService1()中被调用过一次,在clientService2()中被调用过一次。由于此方法创建了ClientDaoImpl的新实例并返回它,因此通常希望有两个实例(每个服务一个)。那绝对是有问题的:在 Spring 中,实例化的 bean 默认具有singleton范围。这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的 bean。

Note

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

Note

从 Spring 3.2 开始,不再需要将 CGLIB 添加到您的 Classpath 中,因为 CGLIB 类已经在org.springframework.cglib下重新打包并直接包含在 spring-core JAR 中。

Tip

由于 CGLIB 在启动时会动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从 4.3 版本开始,配置类