7. IoC容器

7.1 Spring IoC容器和bean简介

本章介绍了Spring Framework实现的控制反转(IoC) [1] 原理。 IoC也称为依赖注入(DI)。这是一个过程,通过这个过程,对象定义它们的依赖关系,即它们使用的其他对象,只能通过构造函数参数,工厂方法的参数,或者在构造或从工厂方法返回后在对象实例上设置的属性。 。然后容器在创建bean时注入这些依赖项。这个过程基本上是相反的,因此控制反转控制(IoC),bean本身控制实例化或位置通过使用类的直接构造或诸如服务定位器模式之类的机制来实现其依赖性。

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

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

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

7.2 容器概述

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

ApplicationContext 接口的几个实现是与Spring一起提供的。在独立应用程序中,通常会创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的实例。虽然XML一直是定义配置元数据的传统格式,但您可以通过提供少量XML配置来声明性地支持这些额外的元数据格式,从而指示容器使用Java注释或代码作为元数据格式。

在大多数应用程序方案中,不需要显式用户代码来实例化Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序的 web.xml 文件中的简单的八行(左右)样板Web描述符XML通常就足够了(参见 Section 7.15.4, “Convenient ApplicationContext instantiation for web applications” )。如果您使用的是 Spring Tool Suite Eclipse支持的开发环境,只需点击几下鼠标或按键即可轻松创建此样板配置。

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

Figure 7.1. The Spring IoC container

container magic

7.2.1 配置元数据

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

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

基于XML的元数据不是唯一允许的配置元数据形式。 Spring IoC容器本身完全与实际编写此配置元数据的格式分离。目前,许多开发人员为其Spring应用程序选择基于Java的配置。

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

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

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

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

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

以下示例显示了基于XML的配置元数据的基本结构:

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

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

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

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

</beans>

id 属性是一个字符串,用于标识单个bean定义。 class 属性定义bean的类型并使用完全限定的类名。 id属性的值指的是协作对象。本例中未显示用于引用协作对象的XML;有关更多信息,请参阅 Dependencies

7.2.2 实例化容器

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

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

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

以下示例显示了服务层对象 (services.xml) 配置文件:

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

    <!-- services -->

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

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

</beans>

以下示例显示了数据访问对象 daos.xml file:

<?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对象/关系映射标准)组成。 property name 元素引用JavaBean属性的名称, ref 元素引用另一个bean定义的名称。 idref 元素之间的这种联系表达了协作对象之间的依赖关系。有关配置对象的依赖关系的详细信息,请参阅 Dependencies

编写基于XML的配置元数据

让bean定义跨越多个XML文件会很有用。通常,每个单独的XML配置文件都代表架构中的逻辑层或模块。

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

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

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

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

可以(但不建议)使用相对“../”路径引用父目录中的文件。这样做会对当前应用程序之外的文件创建依赖关系。特别是,不建议将此引用用于“classpath:”URL(例如,“classpath:../ services.xml”),其中运行时解析过程选择“最近的”类路径根,然后查看其父目录。类路径配置更改可能导致选择不同的,不正确的目录。您始终可以使用完全限定的资源位置而不是相对路径:例如,“file:C:/config/services.xml”或“classpath:/config/services.xml”。但是,请注意您将应用程序的配置与特定的绝对位置耦合。通常最好为这样的绝对位置保持间接,例如,通过在运行时针对JVM系统属性解析的“$ {...}”占位符。

import指令是beans命名空间本身提供的功能。除了普通bean定义之外的其他配置功能在Spring提供的一系列XML命名空间中可用,例如: “context”和“util”命名空间。

Groovy Bean定义DSL

作为外化配置元数据的另一个示例,bean定义也可以在Spring的Groovy Bean中表示定义DSL,从Grails框架中已知。通常,此类配置将存在“.groovy”文件中,其结构如下:

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

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

7.2.3 使用容器

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

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

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

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

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

使用Groovy配置,bootstrapping看起来非常相似,只是一个不同的上下文实现类,它是Groovy感知的(但也理解XML bean定义):

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

最灵活的变体是 GenericApplicationContext 与读者代表的组合,例如使用 XmlBeanDefinitionReader 用于XML文件:

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

或者使用 GroovyBeanDefinitionReader for 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框架组件(如控制器和JSF托管bean)提供依赖注入,允许您通过元数据(例如自动装配注释)声明对特定bean的依赖性。

7.3 Bean概述

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

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

  • 包限定类名:通常是正在定义的bean的实际实现类。

  • Bean行为配置元素,它说明了bean在容器中的行为方式(范围,生命周期回调等)。

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

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

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

Table 7.1. The bean definition

property解释于...
classSection 7.3.2, “Instantiating beans”
nameSection 7.3.1, “Naming beans”
范围Section 7.5, “Bean scopes”
构造函数参数Section 7.4.1, “Dependency Injection”
propertiesSection 7.4.1, “Dependency Injection”
自动装配模式Section 7.4.5, “Autowiring collaborators”
延迟初始化模式Section 7.4.4, “Lazy-initialized beans”
初始化方法the section called “Initialization callbacks”
销毁方法the section called “Destruction callbacks”

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

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

7.3.1 命名bean

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

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

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


Bean Naming Conventions

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

命名bean始终使您的配置更易于阅读和理解,如果您使用的是Spring AOP,那么在将建议应用于与名称相关的一组bean时,它会有很大帮助。


使用类路径中的组件扫描,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 的DataSource。子系统B的配置元数据可以通过名称 subsystemB-dataSource 引用DataSource。在编写使用这两个子系统的主应用程序时,主应用程序通过名称 myApp-dataSource 引用DataSource。要使所有三个名称引用同一对象,可以将以下别名定义添加到配置元数据中:

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

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


Java-configuration

如果您使用的是Java配置,则 @Bean 注释可用于提供别名,请参阅 Section 7.12.3, “Using the @Bean annotation” 以获取详细信息。


7.3.2 实例化bean

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

如果使用基于XML的配置元数据,则指定要在 <bean/> 元素的 class 属性中实例化的对象的类型(或类)。此 class 属性(内部是 BeanDefinition 实例上的 Class 属性)通常是必需的。 (有关例外情况,请参阅 the section called “Instantiation using an instance factory method”Section 7.7, “Bean definition inheritance” 。)您可以通过以下两种方式之一使用 Class 属性:

  • 通常,在容器本身通过反向调用其构造函数直接创建bean的情况下指定要构造的bean类,稍微等同于使用 new 运算符的Java代码。

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


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

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

com.example.Foo$Bar

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


使用构造函数实例化

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

Spring IoC容器几乎可以管理您希望它管理的任何类;它不仅限于管理真正的JavaBeans。大多数Spring用户更喜欢实际的JavaBeans,只有一个默认(无参数)构造函数,并且在容器中的属性之后建模了适当的setter和getter。您还可以在容器中拥有更多异国情调的非bean样式类。例如,如果您需要使用绝对不符合JavaBean规范的旧连接池,那么Spring也可以对其进行管理。

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

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

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

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

使用静态工厂方法实例化

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

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

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

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

有关向工厂方法提供(可选)参数以及在从工厂返回对象后设置对象实例属性的机制的详细信息,请参阅 Dependencies and configuration in detail

使用实例工厂方法实例化

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

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

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

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

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

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

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

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

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

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

在Spring文档中,工厂bean指的是在Spring容器中配置的bean,它将通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大小写)是指特定于Spring的FactoryBean。

7.4 依赖关系

典型的企业应用程序不包含单个对象(或Spring用法中的bean)。即使是最简单的应用程序也有一些对象可以协同工作,以呈现最终用户所看到的连贯应用程序。下一节将介绍如何定义多个独立的bean定义,以及对象协作实现目标的完全实现的应用程序。

7.4.1 依赖注入

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

使用DI原理的代码更清晰,当对象提供其依赖项时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI存在两个主要变体, Constructor-based dependency injectionSetter-based dependency injection

基于构造函数的依赖注入

基于构造函数的DI由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。调用具有特定参数的 static 工厂方法来构造bean几乎是等效的,本讨论同样处理构造函数和 static 工厂方法的参数。以下示例显示了一个只能通过构造函数注入进行依赖注入的类。请注意,此类没有什么特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。

public class SimpleMovieLister {

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

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

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

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

package x.y;

public class Foo {

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

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

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

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

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

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

package examples;

public class ExampleBean {

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

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

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

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

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

使用 index 属性显式指定构造函数参数的索引。例如:

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

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

您还可以使用构造函数参数名称进行值消歧:

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

请记住,为了使这项工作开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果无法使用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的依赖注入

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

以下示例显示了一个只能使用纯setter注入进行依赖注入的类。这个类是传统的Java。它是一个POJO,它不依赖于容器特定的接口,基类或注释。

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


Constructor-based or setter-based DI?

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

Spring团队通常提倡构造函数注入,因为它可以将应用程序组件实现为不可变对象确保所需的依赖项不是 null 。此外,构造函数注入的组件始终以完全初始化的状态返回到客户端(调用)代码。作为旁注,大量的构造函数参数是一个糟糕的代码气味,暗示该类可能有太多的责任,应该重构以更好地解决关注点的正确分离。

Setter注入应主要仅用于可在类中指定合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何位置执行非空检查。 setter注入的一个好处是setter方法使该类的对象可以在以后重新配置或重新注入。因此,通过 JMX MBeans 进行管理是一个令人信服的二手注射用例。

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


依赖关系解析过程

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

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

  • 对于每个bean,如果使用的是依赖于普通构造函数的,那么它的依赖关系将以属性,构造函数参数或static-factory方法的参数的形式表示。实际创建bean时,会将这些依赖项提供给bean。

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

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

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


Circular dependencies

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

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

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

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


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

如果不存在循环依赖关系,那么当一个或多个协作bean被注入依赖bean时,每个协作bean在被注入依赖bean之前完全配置。这意味着如果bean A依赖于bean B,Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是预先实例化的单例),设置依赖项,并调用相关的生命周期方法(例如 configured init methodInitializingBean callback method )。

依赖注入的示例

以下示例将基于XML的配置元数据用于基于setter的DI。 Spring XML配置文件的一小部分指定了一些bean定义:

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

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

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

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

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

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

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

在前面的示例中,声明setter与XML文件中指定的属性匹配。以下示例使用基于构造函数的DI:

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

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

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

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

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

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

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

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

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

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

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

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

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

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

7.4.2 详细信息的依赖关系和配置

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

直线值(基元,字符串等)

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

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

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

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

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

</beans>

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

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

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

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

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

idref元素

idref 元素只是一种防错方法,可以将容器中另一个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,则只能在部署容器后很长时间才能发现此错误和结果异常。

4.0 beans xsd不再支持idref元素的本地属性,因为它不再提供常规bean引用的值。升级到4.0架构时,只需将现有的idref本地引用更改为idref bean即可。

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

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

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

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

<ref bean="someBean"/>

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

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

4.0 bean xsd不再支持ref元素的本地属性,因为它不再提供常规bean引用的值。升级到4.0架构时,只需将现有的ref本地引用更改为ref bean。

内 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的范围。

收藏

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

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected]</prop>
            <prop key="support">[email protected]</prop>
            <prop key="development">[email protected]</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>

映射键或值的值或设置值也可以是以下任何元素:

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

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

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

以下示例演示了集合合并:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[email protected]</prop>
                <prop key="support">[email protected]</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">[email protected]</prop>
                <prop key="support">[email protected]</prop>
            </props>
        </property>
    </bean>
<beans>

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

[email protected]
[email protected]
[email protected]

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

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

集合合并的限制

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

强烈类型的集合

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

public class Foo {

    private Map<String, Float> accounts;

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

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

空字符串值和空字符串值

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

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

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

exampleBean.setEmail("");

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

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

以上配置等同于以下Java代码:

exampleBean.setEmail(null);

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

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

Spring支持可扩展的配置格式 with namespaces ,它们基于XML Schema定义。本章中讨论的 beans 配置格式在XML Schema文档中定义。但是,p-namespace未在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="[email protected]"/>
    </bean>

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

该示例显示了bean定义中名为email的p命名空间中的属性。这告诉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命名空间的属性值,还使用特殊格式来声明属性引用。第一个bean定义使用 <property name="spouse" ref="jane"/> 来创建从bean john 到bean jane 的引用,而第二个bean定义使用 p:spouse-ref="jane" 作为属性来完成同样的事情。在这种情况下, spouse 是属性名称,而 -ref 部分表示这不是直接值,而是对另一个bean的引用。

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

带有c-namespace的XML快捷方式

the section called “XML shortcut with the p-namespace” 类似,Spring 3.1中新引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套 constructor-arg 元素。

让我们回顾 the section called “Constructor-based dependency injection” 中带有 c: 命名空间的示例:

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

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

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

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

</beans>

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

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

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

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

在实践中,构造函数分辨率 mechanism 在匹配参数方面非常有效,因此除非确实需要,否则我们建议在整个配置中使用名称表示法。

复合属性名称

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

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

foo bean具有 fred 属性,该属性具有 bob 属性,具有 sammy 属性,并且最终 sammy 属性设置为值 123 。为了使其工作,在构造bean之后, foofred 属性和 fredbob 属性不能是 null ,否则抛出 NullPointerException

7.4.3 使用依赖

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

bean定义中的depends-on属性既可以指定初始化时间依赖关系,也可以指定仅限单例bean的相应销毁时间依赖关系。在给定的bean本身被销毁之前,首先销毁定义与给定bean的依赖关系的从属bean。因此,依赖也可以控制关机顺序。

7.4.4 懒惰初始化的bean

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

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

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

当前面的配置被 ApplicationContext 消耗时, ApplicationContext 启动时不会急切地预先实例化名为 lazy 的bean,而 not.lazy bean则被急切地预先实例化。

但是,当延迟初始化的bean是未进行延迟初始化的单例bean的依赖项时, ApplicationContext 会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。惰性初始化的bean被注入到其他地方的单独的bean中,而这个bean并不是惰性初始化的。

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

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

7.4.5 自动装配协作者

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

  • 自动装配可以显着减少指定属性或构造函数参数的需要。 (其他机制,如bean模板 discussed elsewhere in this chapter 在这方面也很有 Value 。)

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

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

Table 7.2. Autowiring modes

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

使用byType或构造函数自动装配模式,您可以连接数组和类型集合。在这种情况下,提供容器内与预期类型匹配的所有autowire候选者以满足依赖性。如果预期的密钥类型为 String ,则可以自动装配强类型 Map 。自动装配的Maps值将包含与预期类型匹配的所有Bean实例,而Maps键将包含相应的bean名称。

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

自动装配的限制和缺点

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

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

  • propertyconstructor-arg 设置中的显式依赖项始终覆盖自动装配。您无法自动装配所谓的简单属性,例如基元, StringsClasses (以及此类简单属性的数组)。这种限制是按设计的。

  • 自动装配不如显式布线精确。尽管如上表所示,Spring会小心避免在可能产生意外结果的歧义的情况下进行猜测,但不再明确记录Spring管理对象之间的关系。

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

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

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

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

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

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

  • 实现基于注释的配置可用的更精细控制,如 Section 7.9, “Annotation-based container configuration” 中所述。

从自动装配中排除bean

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

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

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

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

7.4.6 方法注入

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

解决方案是放弃一些控制反转。您可以通过实现 ApplicationContextAware 接口来 make bean A aware of the container ,并且每次bean A需要时,通过 making a getBean("B") call to the container 请求(通常是新的)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 Framework。 Method Injection是Spring IoC容器的一个先进功能,它允许以干净的方式处理这个用例。


您可以在 this blog entry 中详细了解方法注入的动机。


查找方法注入

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

为了使这个动态子类工作,Spring bean容器将子类化的类不能是final,并且要重写的方法也不能是final。对具有抽象方法的类进行单元测试需要您自己对类进行子类化,并提供抽象方法的存根实现。组件扫描也需要具体的方法,这需要具体的类别来获取。另一个关键限制是查找方法不适用于工厂方法,特别是配置类中的@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 )中,要注入的方法需要以下形式的签名:

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

标识为commandManager的bean在需要myCommand 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类。

访问不同范围的目标bean的另一种方法是ObjectFactory / Provider注入点。查看名为“Scoped beans as dependencies”的部分。感兴趣的读者也可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)。

任意方法更换

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

使用基于XML的配置元数据,您可以使用 replaced-method 元素将已存在的方法实现替换为已部署的bean。考虑以下类,使用方法computeValue,我们要覆盖它:

public class MyValueCalculator {

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

    // some other methods...
}

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

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

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

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

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

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

您可以在 <replaced-method/> 元素中使用一个或多个包含的 <arg-type/> 元素来指示被覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是a完全限定类型名称的子字符串。例如,以下全部匹配 java.lang.String

java.lang.String
String
Str

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

7.5 Bean范围

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

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

开箱即用支持以下范围。你也可以创建 a custom scope.

Table 7.3. Bean scopes

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

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

7.5.1 单身范围

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

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

singleton

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

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

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

7.5.2 原型范围

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

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

prototype

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

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

在与其他作用域相比,Spring不管理原型bean的完整生命周期:容器实例化,配置和组装原型对象,并将其交给客户端,而不再记录该原型实例。因此,尽管无论范围如何都在所有对象上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。客户端代码必须清理原型范围的对象并释放原型bean所持有的昂贵资源。要使Spring容器释放原型范围的bean所拥有的资源,请尝试使用自定义 bean post-processor ,它包含对需要清理的bean的引用。

在某些方面,Spring容器关于原型范围bean的角色是Java new 运算符的替代品。超过该点的所有生命周期管理必须由客户端处理。 (有关Spring容器中bean的生命周期的详细信息,请参阅 Section 7.6.1, “Lifecycle callbacks” 。)

7.5.3 具有原型bean依赖关系的单例bean

当您使用具有依赖于原型bean的单例作用域bean时,请注意在实例化时解析依赖项。因此,如果依赖项将原型范围的bean注入到单例范围的bean中,则会实例化一个新的原型bean,然后将依赖注入到单例bean中。原型实例是唯一提供给单例范围bean的实例。

但是,假设您希望单例范围的bean在运行时重复获取原型范围的bean的新实例。您不能将原型范围的bean依赖注入到您的单例bean中,因为当Spring容器实例化单例bean并解析和注入其依赖项时,该注入只发生一次。如果您需要在运行时多次使用原型bean的新实例,请参阅 Section 7.4.6, “Method injection”

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

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

初始Web配置

为了支持 requestsessionglobalSessionapplicationwebsocket 级别(Web范围的bean)中bean的范围,在定义bean之前需要进行一些小的初始配置。 (标准范围 singletonprototype 不需要此初始设置。)

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

如果您在Spring Web MVC中访问scoped bean,实际上是在Spring DispatcherServletDispatcherPortlet 处理的请求中,则无需进行特殊设置: DispatcherServletDispatcherPortlet 已经公开了所有相关状态。

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

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

或者,如果您的侦听器设置存在问题,请考虑使用Spring的 RequestContextFilter 。过滤器映射取决于周围的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可以在调用链中进一步使用。

请求范围

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

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

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

使用注释驱动的组件或Java Config时, @RequestScope 注释可用于将组件分配给 request 范围。

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

会话范围

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

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

Spring容器通过在单个HTTP Session 的生命周期中使用 userPreferences bean定义来创建 UserPreferences bean的新实例。换句话说, userPreferences bean实际上是在HTTP范围内 Session 级别。与 request-scoped beans一样,您可以根据需要更改创建的实例的内部状态,因为知道同样使用从同一 userPreferences bean定义创建的实例的其他HTTP Session 实例在状态中看不到这些更改,因为它们特别适用于个人HTTP Session 。最终丢弃HTTP Session 时,也会丢弃作用于该特定HTTP Session 的bean。

使用注释驱动的组件或Java Config时, @SessionScope 注释可用于将组件分配给 session 范围。

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

全局会话范围

考虑以下bean定义:

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

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

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

申请范围

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

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

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

使用注释驱动的组件或Java Config时, @ApplicationScope 注释可用于将组件分配给 application 范围。

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

作为依赖项的Scoped bean

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

您还可以在作为单例的作用域的bean之间使用<aop:scoped-proxy />,然后引用通过可序列化的中间代理,从而能够在反序列化时重新获取目标单例bean。当针对范围原型的bean声明<aop:scoped-proxy />时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后该调用将被转发到该目标实例。此外,范围代理不是以生命周期安全的方式从较短范围访问bean的唯一方法。您也可以简单地将您的注入点(即构造函数/ setter参数或自动装配字段)声明为ObjectFactory ,允许getObject()调用在每次需要时按需检索当前实例 - 而无需保留实例或单独存储它。作为扩展变体,您可以声明ObjectProvider ,它提供了几个额外的访问变体,包括getIfAvailable和getIfUnique。 JSR-330的变体称为Provider,与Provider 声明一起使用,并且对每次检索尝试都使用相应的get()调用。有关JSR-330整体的更多详细信息,请参见此处。

以下示例中的配置只有一行,但了解“为什么”以及它背后的“如何”非常重要。

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

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

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

要创建此类代理,请将子 <aop:scoped-proxy/> 元素插入到作用域bean定义中(请参阅 the section called “Choosing the type of proxy to create”Chapter 41, XML Schema-based configuration )。为什么在 requestsessionglobalSession 和自定义范围级别定义bean的定义需要 <aop:scoped-proxy/> 元素?让我们检查下面的单例bean定义,并将其与您需要为上述范围定义的内容进行对比(请注意,以下 userPreferences bean定义不完整)。

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

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

在前面的示例中,单例bean userManager 注入了对HTTP Session -scoped bean userPreferences 的引用。这里的重点是 userManager bean是一个单例:它将在每个容器中实例化一次,并且它的依赖项(在这种情况下只有一个, userPreferences bean)也只注入一次。这意味着 userManager bean只能在完全相同的情况下运行 userPreferences 对象,即它最初注入的对象。

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

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

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

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

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

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

或者,您可以通过为 <aop:scoped-proxy/> 元素的 proxy-target-class 属性的值指定 false 来配置Spring容器,以便为此类作用域bean创建基于JDK接口的标准代理。使用基于JDK接口的代理意味着您不需要在应用程序类路径中使用其他库来实现此类代理。但是,这也意味着scoped bean的类必须至少实现一个接口,并且注入scoped bean的所有协作者必须通过其中一个接口引用bean。

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

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

有关选择基于类或基于接口的代理的更多详细信息,请参阅 Section 11.6, “Proxying mechanisms”

7.5.5 自定义范围

beans 捞机构是可扩展的;您可以定义自己的范围,甚至可以重新定义现有范围,尽管后者被认为是不好的做法,您无法覆盖内置的 singletonprototype 范围。

创建自定义范围

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

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

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

Object get(String name, ObjectFactory objectFactory)

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

Object remove(String name)

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

void registerDestructionCallback(String name, Runnable destructionCallback)

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

String getConversationId()

使用自定义范围

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

void registerScope(String scopeName, Scope scope);

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

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

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

下面的示例使用Spring附带的SimpleThreadScope,但默认情况下未注册。对于您自己的自定义Scope实现,指令是相同的。

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

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

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

使用自定义 Scope 实现,您不仅限于范围的编程注册。您还可以使用 CustomScopeConfigurer 类以声明方式执行 Scope 注册:

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

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

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

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

</beans>

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

7.6 自定义bean的性质

7.6.1 生命周期回调

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

JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean不会耦合到Spring特定的接口。有关详细信息,请参见第7.9.8节“@ PostConstruct和@PreDestroy”。如果您不想使用JSR-250注释但仍希望删除耦合,请考虑使用init-method和destroy-method对象定义元数据。

在内部,Spring Framework使用 BeanPostProcessor 实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为Spring不提供开箱即用的功能,您可以自己实现 BeanPostProcessor 。有关更多信息,请参阅 Section 7.8, “Container Extension Points”

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

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

初始化回调

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

void afterPropertiesSet() throws Exception;

建议您不要使用 InitializingBean 接口,因为它会不必要地将代码耦合到Spring。或者,使用 @PostConstruct 注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用 init-method 属性指定具有void无参数签名的方法的名称。使用Java配置,可以使用 @BeaninitMethod 属性,请参阅 the section called “Receiving lifecycle callbacks” 。例如,以下内容:

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

销毁回调

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

void destroy() throws Exception;

建议您不要使用 DisposableBean 回调接口,因为它会不必要地将代码耦合到Spring。或者,使用 @PreDestroy 批注或指定bean定义支持的泛型方法。使用基于XML的配置元数据,可以使用 <bean/> 上的 destroy-method 属性。使用Java配置,您可以使用 @BeandestroyMethod 属性,请参阅 the section called “Receiving lifecycle callbacks” 。例如,以下定义:

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

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

默认初始化和销毁方法

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

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

假设您的初始化回调方法名为 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.");
        }
    }
}
<beans default-init-method="init">

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

</beans>

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

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

如果现有的bean类已经有在与惯例有差异命名回调方法,你可以通过指定覆盖默认(XML格式,这是)使用 init-method 和的 <bean/> 本身 destroy-method 属性的方法名。

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

结合生命周期机制

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

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

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

  • 使用 @PostConstruct 注释的方法
    679 afterPropertiesSet()InitializingBean 回调接口定义

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

Destroy方法以相同的顺序调用:

  • 使用 @PreDestroy 注释的方法
    _67917 destroy()DisposableBean 回调接口定义

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

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

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

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

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

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

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

public interface Phased {

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

    boolean isAutoStartup();

    void stop(Runnable callback);
}

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

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

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

如上所述, LifecycleProcessor 接口定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关闭过程,就像显式调用了 stop() 一样,但是当上下文关闭时会发生。另一方面,'refresh'回调启用了 SmartLifecycle beans的另一个功能。刷新上下文(在所有对象都已实例化并初始化之后),将调用该回调,此时默认生命周期处理器将检查每个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值。如果 "true" ,则该对象将在该点启动,而不是等待显式调用上下文或其自己的 start() 方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。 "phase" 值以及任何 "depends-on" 关系将以与上述相同的方式确定启动顺序。

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

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

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

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

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

public final class Boot {

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

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

        // app runs here...

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

7.6.2 ApplicationContextAware和BeanNameAware

ApplicationContext 创建实现 org.springframework.context.ApplicationContextAware 接口的对象实例时,将为该实例提供对该 ApplicationContext 的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

从Spring 2.5开始,自动装配是获得对 ApplicationContext 的引用的另一种选择。 "traditional" constructorbyType 自动装配模式(如 Section 7.4.5, “Autowiring collaborators” 中所述)可分别为构造函数参数或setter方法参数提供类型 ApplicationContext 的依赖关系。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用新的基于注释的自动装配功能。如果这样做, ApplicationContext 将自动装入一个字段,构造函数参数或方法参数,如果相关的字段,构造函数或方法带有 @Autowired 注释,则该参数需要 ApplicationContext 类型。有关更多信息,请参阅 Section 7.9.2, “@Autowired”

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在普通bean属性填充之后但在初始化回调之前调用回调,例如 InitializingBean afterPropertiesSet或自定义init方法。

7.6.3 其他Aware接口

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

Table 7.4. Aware interfaces

姓名已注入的依赖关系在...中解释
ApplicationContextAware声明 ApplicationContextSection 7.6.2, “ApplicationContextAware and BeanNameAware”
ApplicationEventPublisherAware封闭的事件发布者 ApplicationContextSection 7.15, “Additional capabilities of the ApplicationContext”
BeanClassLoaderAware用于加载bean类的类加载器。Section 7.3.2, “Instantiating beans”
BeanFactoryAware声明 BeanFactorySection 7.6.2, “ApplicationContextAware and BeanNameAware”
BeanNameAware声明bean的名称Section 7.6.2, “ApplicationContextAware and BeanNameAware”
BootstrapContextAware资源适配器 BootstrapContext 容器运行。通常仅在JCA中可用 ApplicationContext sChapter 32, JCA CCI
LoadTimeWeaverAware定义的weaver用于在加载时处理类定义Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework”
MessageSourceAware用于解析消息的已配置策略(支持参数化和国际化)Section 7.15, “Additional capabilities of the ApplicationContext”
NotificationPublisherAwareSpring JMX通知发布商Section 31.7, “Notifications”
PortletConfigAware当前 PortletConfig 容器运行。仅在支持Web的Spring中有效 ApplicationContextChapter 25, Portlet MVC Framework
PortletContextAware当前 PortletContext 容器运行。仅在支持Web的Spring中有效 ApplicationContextChapter 25, Portlet MVC Framework
ResourceLoaderAware已配置的加载程序,用于对资源进行低级访问Chapter 8, Resources
ServletConfigAware当前 ServletConfig 容器运行。仅在支持Web的Spring中有效 ApplicationContextChapter 22, Web MVC framework
ServletContextAware当前 ServletContext 容器运行。仅在支持Web的Spring中有效 ApplicationContextChapter 22, Web MVC framework

再次注意,这些接口的使用将您的代码与Spring API联系起来,并且不遵循Inversion of Control样式。因此,建议将它们用于需要以编程方式访问容器的基础结构bean。

7.7 Bean定义继承

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

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

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

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

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

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

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

前面的示例使用 abstract 属性将父bean定义显式标记为abstract。如果父定义未指定类,则需要将父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() 方法忽略定义为abstract的bean定义。

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

7.8 集装箱扩建点

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

7.8.1 使用BeanPostProcessor自定义bean

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

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

BeanPostProcessors对bean(或对象)实例进行操作;也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessors完成它们的工作。 BeanPostProcessors的范围是每个容器。这仅在您使用容器层次结构时才有意义。如果在一个容器中定义BeanPostProcessor,它将只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如第7.8.2节“使用BeanFactoryPostProcessor定制配置元数据”中所述。

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

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

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

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如上所述),但也可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory注册它们。当需要在注册之前评估条件逻辑,或者甚至在层次结构中跨上下文复制bean后处理器时,这非常有用。但请注意以编程方式添加的BeanPostProcessors不遵守Ordered接口。这是注册的顺序,它决定了执行的顺序。另请注意,无论是否有任何显式排序,以编程方式注册的BeanPostProcessors始终在通过自动检测注册的BeanPostProcessors之前处理。

实现BeanPostProcessor接口的类是特殊的,容器对它们的处理方式不同。直接引用的所有BeanPostProcessors和bean都会在启动时实例化,这是ApplicationContext的特殊启动阶段的一部分。接下来,所有BeanPostProcessors都以排序方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessors和它们直接引用的bean都不适合自动代理,因此没有编入方面的方面。对于任何此类bean,您应该看到一条信息性日志消息:“Bean foo不适合所有BeanPostProcessor接口处理(例如:不符合自动代理条件)”。请注意,如果使用自动装配或@Resource将bean连接到BeanPostProcessor(可能会回退到自动装配),Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,从而使它们不符合自动代理或其他类型的条件 beans 后处理。例如,如果您有一个使用@Resource注释的依赖项,其中field / setter名称不直接对应于bean的声明名称而且没有使用name属性,那么Spring将访问其他bean以按类型匹配它们。

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

示例:Hello World,BeanPostProcessor样式

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

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

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

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

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

</beans>

注意如何简单地定义 InstantiationTracingBeanPostProcessor 。它甚至没有名称,因为它是一个bean,它可以像任何其他bean一样依赖注入。 (前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在 Headers 为 Chapter 35, Dynamic language support 的章节中有详细说明。)

以下简单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 : [email protected]
[email protected]

示例:RequiredAnnotationBeanPostProcessor

将回调接口或注释与自定义 BeanPostProcessor 实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的 RequiredAnnotationBeanPostProcessor - 一个带有Spring发行版的 BeanPostProcessor 实现,它确保用(任意)注释标记的bean上的JavaBean属性实际上(配置为)依赖注入值。

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

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

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

如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(如上面第7.8.1节“使用BeanPostProcessor定制bean”中所述)。虽然技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,从而违反标准的容器生命周期。这可能会导致负面影响,例如绕过bean后处理。此外,BeanFactoryPostProcessors的范围是每个容器。这仅在您使用容器层次结构时才有意义。如果在一个容器中定义BeanFactoryPostProcessor,它将仅应用于该容器中的bean定义。 BeanFactoryPostProcessors不会在另一个容器中对一个容器中的Bean定义进行后处理,即使两个容器都是同一个容器的一部分层次结构。

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

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

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

示例:类名替换PropertyPlaceholderConfigurer

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<值>类路径:COM /富/ strategy.properties <值> custom.strategy.class = com.foo.DefaultStrategy

<bean id =“serviceStrategy”class =“$ {custom.strategy.class}”/>如果在运行时无法将类解析为有效类,则bean的解析将在创建时失败,即在用于非惰性init bean的ApplicationContext的preInstantiateSingletons()阶段。

示例: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的bean的定义,该bean具有驱动程序和url属性。

也支持复合属性名称,只要路径的每个组件(重写的最终属性除外)都已经非空(可能由构造函数初始化)。在这个例子中......

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

指定的覆盖值始终是文字值;它们不会被翻译成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

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

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

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

为自己工厂的对象实现 org.springframework.beans.factory.FactoryBean 接口。

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

FactoryBean 接口提供了三种方法:

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

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

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

FactoryBean 概念和接口在Spring Framework中的许多地方使用;超过50个 FactoryBean 接口的实现与Spring本身一起发布。

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

7.9 基于注释的容器配置


Are annotations better than XML for configuring Spring?

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

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


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

注释注入在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 。)

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

7.9.1 @Required

@Required 注释适用于bean属性setter方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

此注释仅表示受影响的bean属性必须在配置时填充,通过bean定义中的显式属性值或通过自动装配填充。如果尚未填充受影响的bean属性,容器将引发异常;这允许急切和明确的失败,以后避免 NullPointerException 等。仍然建议您将断言放入bean类本身,例如,放入init方法。即使您在容器外部使用类,这样做也会强制执行那些必需的引用和值。

7.9.2 @Autowired

在下面的示例中,可以使用

JSR 330的@Inject注释代替Spring的@Autowired注释。有关详细信息,请参见此处

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

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

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

正如所料,您还可以将 @Autowired 注释应用于 "traditional" 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;
    }

    // ...
}

确保您的目标组件(例如MovieCatalog,CustomerPreferenceDao)始终以您用于@Autowired注释注入点的类型声明。否则,由于在运行时未找到类型匹配,注入可能会失败。对于通过类路径扫描找到的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;
    }

    // ...
}

如果希望按特定顺序对数组或列表中的项进行排序,则目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority批注。否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。 @Order注释可以在目标类级别声明,也可以在@Bean方法上声明,可能是每个bean定义非常独立(如果具有相同bean类的多个定义)。 @Order值可能影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。请注意,标准的javax.annotation.Priority注释在@Bean级别不可用,因为它无法在方法上声明。它的语义可以通过@Order值与每个类型的单个bean上的@Primary一起建模。

只要预期的密钥类型是 String ,即使是类型化的 Map 也可以自动装配。 Map值将包含所需类型的所有bean,并且键将包含相应的bean名称:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

每个类只能标记一个带注释的构造函数,但可以注释多个非必需的构造函数。在这种情况下,每个都被认为是候选者之一,Spring使用最贪婪的构造函数,其依赖性可以得到满足,即构造函数具有最大数量的参数。建议使用@Autowired的必需属性而不是@Required注释。 required属性表示该属性不是自动装配所必需的,如果无法自动装配,则会忽略该属性。另一方面,@ Required更强大,因为它强制执行由容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。

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

public class SimpleMovieLister {

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

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

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

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

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

由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程进行更多控制。实现这一目标的一种方法是使用Spring的 @Primary 注释。 @Primary 表示当多个bean可以自动装配到单值依赖项时,应该优先选择特定的bean。如果候选者中只存在一个“主”bean,则它将是自动装配的值。

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

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

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

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相应的bean定义如下所示。

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

    <context:annotation-config/>

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

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

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

</beans>

7.9.4 使用限定符微调基于注释的自动装配

@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定义如下所示。具有限定符值“main”的bean与使用相同值限定的构造函数参数连接。

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

    <context:annotation-config/>

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

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

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

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

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

</beans>

对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用id "main" 而不是嵌套的限定符元素定义bean,从而得到相同的匹配结果。但是,虽然您可以使用此约定按名称引用特定bean,但 @Autowired 基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回退,限定符值在类型匹配集合中也总是具有缩小的语义;它们在语义上不表示对唯一bean id的引用。良好的限定符值是 "main" 或 "EMEA" 或 "persistent" ,表示独立于bean id 的特定组件的特征,可以在匿名bean定义(如前例中的定义)的情况下自动生成。

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

在类型匹配候选项中,根据目标bean名称选择限定符值,甚至不需要在注入点使用@Qualifier注释。如果没有其他分辨率指示符(例如限定符或主要标记),对于非唯一依赖性情况,Spring将使注入点名称(即字段名称或参数名称)与目标bean名称匹配,并选择相同的 - 如果有的候选人。也就是说,如果您打算按名称表达注释驱动的注入,请不要主要使用@Autowired,即使能够在类型匹配候选项中通过bean名称进行选择。相反,使用JSR-250 @Resource注释,该注释在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。@Autowired具有相当不同的语义:在按类型选择候选bean之后,将仅在那些类型选择的候选者中考虑指定的字符串限定符值,例如,将“帐户”限定符与标记有相同限定符标签的bean匹配。对于本身定义为集合/映射或数组类型的bean,@ Resource是一个很好的解决方案,通过唯一名称引用特定的集合或数组bean。也就是说,从4.3开始,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,集合/映射和数组类型也可以通过Spring的@Autowired类型匹配算法进行匹配。在这种情况下,限定符值可用于在相同类型的集合中进行选择,如上一段所述。从4.3开始,@ Autowired还考虑了自我引用注入,即引用回到当前注入的bean。请注意,自我注射是一种后备;对其他组件的常规依赖性始终具有优先权。从这个意义上讲,自我引用并不参与常规的候选人选择,因此尤其不是主要的;相反,它们总是最低优先级。在实践中,仅使用自引用作为最后的手段,例如通过bean的事务代理调用同一实例上的其他方法:在这种情况下,考虑将受影响的方法分解为单独的委托bean。或者,使用@Resource,它可以通过其唯一名称获取代理回到当前bean。 @Autowired适用于字段,构造函数和多参数方法,允许在参数级别缩小限定符注释。相比之下,@ Resource仅支持具有单个参数的字段和bean属性setter方法。因此,如果您的注射目标是构造函数或多参数方法,请坚持使用限定符。

您可以创建自己的自定义限定符注释。只需定义注释并在定义中提供 @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>

Section 7.10, “Classpath scanning and managed components” 中,您将看到基于注释的替代方法,即在XML中提供限定符元数据。具体来说,请参阅 Section 7.10.8, “Providing qualifier metadata with annotations”

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

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

}

然后将注释添加到要自动装配的字段或属性中:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}

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

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

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

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

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

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

@Configuration
public class MyConfiguration {

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

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

假设上面的bean实现了一个通用接口,即 Store<String>Store<Integer> ,你可以使用 Store 接口,并且泛型将用作限定符:

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

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

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

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

7.9.6 CustomAutowireConfigurer

CustomAutowireConfigurerBeanFactoryPostProcessor ,使您可以注册自己的自定义限定符注释类型,即使它们没有使用Spring的 @Qualifier 注释注释。

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

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

  • 每个bean定义的 autowire-candidate

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

  • @Qualifier 注释的存在以及使用 CustomAutowireConfigurer 注册的任何自定义注释

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

7.9.7 @Resource

Spring还支持在字段或bean属性setter方法上使用JSR-250 @Resource 注释进行注入。这是Java EE 5和6中的常见模式,例如在JSF 1.2托管bean或JAX-WS 2.0 endpoints 中。 Spring也支持Spring管理对象的这种模式。

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

注释提供的名称由ApplicationAtext解析为Bean名称,CommonAnnotationBeanPostProcessor可以识别该名称。如果您显式配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析名称。但是,建议您依赖于默认行为,只需使用Spring的JNDI查找功能来保持间接级别。

在没有指定显式名称且类似于 @Autowired@Resource 用法的独占情况下, @Resource 找到主要类型匹配而不是特定的命名bean并解析众所周知的可解析依赖项: BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource 接口。

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

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

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

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

7.10 类路径扫描和托管组件

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

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

7.10.1 @Component和进一步的构造型注释

@Repository 注释是满足存储库(也称为数据访问对象或DAO)的角色或构造型的任何类的标记。该标记的用途包括自动转换异常,如 Section 20.2.2, “Exception translation” 中所述。

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

7.10.2 元注释

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

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

    // ....
}

元注释也可以组合以创建组合注释。例如,Spring MVC的 @RestController 注释由 @Controller@ResponseBody 组成。

此外,组合注释可以选择性地从元注释重新声明属性以允许用户定制。当您只想公开元注释属性的子集时,这可能特别有用。例如,Spring的 @SessionScope 注释将范围名称硬编码为 session ,但仍允许自定义 proxyMode

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

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

}

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

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

或者使用 proxyMode 的重写值,如下所示:

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

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

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

Spring可以自动检测构造型类,并使用 ApplicationContext 注册相应的 BeanDefinition 。例如,以下两个类符合此类自动检测的条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

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

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

为简洁起见,上面可能使用了注释的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>

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

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

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

您可以通过包含值为false的annotation-config属性来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

7.10.4 使用过滤器自定义扫描

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

Table 7.5. Filter Types

过滤器类型示例表达式描述
注释(默认)org.example.SomeAnnotation目标组件中类型级别的注释。
assignableorg.example.SomeClass目标组件可分配给的类(或接口)(延长/执行)。
aspectjorg.example..*Service+要与目标组件匹配的AspectJ类型表达式。
regexorg\.example\.Default.*要与目标组件类名匹配的正则表达式。
customorg.example.MyTypeFilterorg.springframework.core.type .TypeFilter 界面的自定义实现。

以下示例显示忽略所有 @Repository 注释并使用 "stub" 存储库的配置。

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

您还可以通过在注释上设置useDefaultFilters = false或提供use-default-filters =“false”作为元素的属性来禁用默认过滤器。这实际上将禁止自动检测使用@ Component,@ Repository,@ Service,@ Controller或@Configuration注释的类。

7.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 和自定义限定符注释。

除了组件初始化的作用外,@ 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最有意义。对于其他作用域,工厂方法只会看到触发在给定作用域中创建新bean实例的注入点:例如,触发创建惰性单例bean的依赖项。在这种情况下,使用提供的注入点元数据和语义关注。

@Component
public class FactoryMethodComponent {

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

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

您可以将@Bean方法声明为静态,允许在不创建包含配置类作为实例的情况下调用它们。这在定义后处理器bean时特别有意义,例如,类型为BeanFactoryPostProcessor或BeanPostProcessor,因为这些bean将在容器生命周期的早期初始化,并应避免在此时触发配置的其他部分。请注意,对静态@Bean方法的调用永远不会被容器拦截,甚至在@Configuration类中也不会被拦截(参见上文)。这是由于技术限制:CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。 @Bean方法的Java语言可见性对Spring容器中生成的bean定义没有立即影响。你可以自由地声明你的工厂方法,你认为适合非@Configuration类,也适用于任何地方的静态方法。但是,@ Consfiguration类中的常规@Bean方法需要是可覆盖的,即它们不能声明为private或final。还将在给定组件或配置类的基类以及由组件或配置类实现的接口中声明的Java 8缺省方法上发现@Bean方法。这使得在编写复杂的配置安排时具有很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8默认方法实现多重继承。最后,请注意,单个类可以为同一个bean保存多个@Bean方法,作为多个工厂方法的安排,具体取决于运行时的可用依赖项。这与在其他配置方案中选择“最贪婪”构造函数或工厂方法的算法相同:将在构造时选择具有最多可满足依赖项的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。

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

如果您不想依赖默认的bean命名策略,则可以提供自定义bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名:

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

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

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

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

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

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

有关特定于Web的范围(如Spring上下文中的 "request" / "session" )的详细信息,请参阅 Section 7.5.4, “Request, session, global session, application, and WebSocket scopes” 。与这些范围的预构建注释一样,您也可以使用Spring的元注释方法编写自己的范围注释:例如:使用 @Scope("prototype") 进行元注释的自定义注释,可能还会声明自定义范围代理模式。

要为范围解析提供自定义策略而不是依赖基于注释的方法,请实现ScopeMetadataResolver接口,并确保包含默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名:

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

使用某些非单例作用域时,可能需要为作用域对象生成代理。推理在 the section called “Scoped beans as dependencies” 中描述。为此,component-scan元素上提供了scoped-proxy属性。三个可能的值是:no,interfaces和targetClass。例如,以下配置将生成标准JDK动态代理:

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

7.10.8 使用注释提供限定符元数据

@Qualifier 注释在 Section 7.9.4, “Fine-tuning annotation-based autowiring with qualifiers” 中讨论。该部分中的示例演示了在解析自动线候选时使用 @Qualifier 注释和自定义限定符注释来提供细粒度控制。因为这些示例基于XML bean定义,所以使用XML中 bean 元素的 qualifiermeta 子元素在候选bean定义上提供限定符元数据。当依赖类路径扫描来自动检测组件时,您可以在候选类上为限定符元数据提供类型级注释。以下三个示例演示了此技术:

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

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

7.11 使用JSR 330标准注释

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

如果您使用的是Maven,则javax.inject工件可在标准Maven存储库中找到(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。您可以将以下依赖项添加到文件pom.xml:
<的groupId> javax.inject </的groupId>
<artifactId的> javax.inject </ artifactId的>
<版本> 1 </版本>
</依赖性>

7.11.1 使用@Inject和@Named进行依赖注入

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

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

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

@Autowired 一样,可以在字段级别,方法级别和构造函数 - 参数级别使用 @Inject 。此外,您可以将注入点声明为 Provider ,允许按需访问较短范围的bean或通过 Provider.get() 调用对其他bean的延迟访问。作为上述示例的变体:

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

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

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

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

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

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

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

public class SimpleMovieLister {

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

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

7.11.2 @Named和@ManagedBean:@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  {
    ...
}

与@Component相比,JSR-330 @Named和JSR-250 ManagedBean注释不可组合。请使用Spring的构造型模型来构建自定义组件注释。

7.11.3 JSR-330标准注释的局限性

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

Table 7.6. Spring component model elements vs. JSR-330 variants

Spring javax.inject。*javax.inject限制/评论
@Autowired@Inject@Inject 没有'required'属性;可以与Java 8的 Optional 一起使用。
@Component@Named / @ManagedBeanJSR-330不提供可组合模型,只是一种识别命名组件的方法。
@Scope(“singleton”)@SingletonJSR-330默认范围类似于Spring的 prototype 。但是,为了使其与Spring的一般默认值保持一致,默认情况下,在Spring容器中声明的JSR-330 bean是 singleton 。要使用 singleton 以外的范围,您应该使用Spring的 @Scope 注释。 javax.inject 还提供了 @Scope 注释。然而,这个仅用于创建自己的注释。
@Qualifier@Qualifier / @Namedjavax.inject.Qualifier 只是构建自定义限定符的元注释。具体字符串限定符(如Spring的带有值的 @Qualifier )可以通过 javax.inject.Named 关联。
@Value-无对应
@Required-无对应
@Lazy-无对应
ObjectFactoryProviderjavax.inject.Provider 是Spring的 ObjectFactory 的直接替代品,只是使用更短的 get() 方法名称。它也可以与Spring的 @Autowired 结合使用,或与非注释的构造函数和setter方法结合使用。

7.12 基于Java的容器配置

7.12.1 基本概念:@Bean和@Configuration

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

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

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

Full @Configuration vs 'lite' @Bean mode?

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

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

在常见的场景中, @Bean 方法将在 @Configuration 类中声明,确保始终使用“完整”模式,因此交叉方法引用将被重定向到容器的生命周期管理。这将防止通过常规Java调用意外地调用相同的 @Bean 方法,这有助于减少在“精简”模式下操作时难以跟踪的细微错误。


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

7.12.2 使用AnnotationConfigApplicationContext实例化Spring容器

下面的部分记录了Spring的 AnnotationConfigApplicationContext ,Spring 3.0中的新功能。这个多功能的 ApplicationContext 实现不仅能够接受 @Configuration 类作为输入,还能接受使用JSR-330元数据注释的普通 @Component 类和类。

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

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

结构简单

与实例化 ClassPathXmlApplicationContext 时将Spring XML文件用作输入的方式大致相同, @Configuration 类可以在实例化 AnnotationConfigApplicationContext 时用作输入。这允许Spring容器的完全无XML使用:

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

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

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

使用扫描启用组件扫描(String ...)

要启用组件扫描,只需按如下方式注释 @Configuration 类:

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

有经验的Spring用户将熟悉与Spring的上下文相同的XML声明:namespace
<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);
}

请记住@Configuration类是使用@Component进行元注释的,因此它们是组件扫描的候选者!在上面的例子中,假设AppConfig是在com.acme包(或下面的任何包)中声明的,它将在调用scan()期间被拾取,并且在refresh()时,它的所有@Bean方法都将被处理并且在容器中注册为bean定义。

使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigWebApplicationContextWebApplicationContext 变体可用于 AnnotationConfigWebApplicationContext 。可以使用该实现配置Spring ContextLoaderListener servlet监听器时,Spring MVC DispatcherServlet 等。以下是配置典型Spring MVC Web应用程序的 web.xml 片段。注意使用 contextClass context-param和init-param:

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

7.12.3 使用@Bean注释

@Bean 是方法级注释,是XML <bean/> 元素的直接模拟。注释支持 <bean/> 提供的一些属性,例如: init-methoddestroy-methodautowiringname

您可以在 @Configuration -annotated或 @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>

这两个声明都在 ApplicationContext 中创建了一个名为 transferService 的bean,绑定到 TransferServiceImpl 类型的对象实例:

transferService -> com.acme.TransferServiceImpl

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

@Configuration
public class AppConfig {

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

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

如果您始终通过声明的服务接口引用您的类型,则@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,更安全地声明可能的最具体的返回类型(至少与引用您的bean的注入点所需的具体相同)。

Bean依赖项

@Bean 带注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们的 TransferService 需要 AccountRepository ,我们可以通过方法参数实现该依赖:

@Configuration
public class AppConfig {

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

解析机制与基于构造函数的依赖注入非常相似,有关详细信息,请参阅 the relevant section

接收生命周期回调

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

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

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

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

public class Foo {

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

public class Bar {

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

@Configuration
public class AppConfig {

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

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

默认情况下,使用Java配置定义的具有公共关闭或关闭方法的bean会自动使用销毁回调登记。如果您有一个公共关闭或关闭方法,并且您不希望在容器关闭时调用它,只需将@Bean(destroyMethod =“”)添加到您的bean定义中以禁用默认(推断)模式。对于通过JNDI获取的资源,您可能希望默认执行此操作,因为其生命周期在应用程序外部进行管理。特别是,请确保始终为DataSource执行此操作,因为已知它在Java EE应用程序服务器上存在问题。 @Bean(=了destroyMethod “”)
public DataSource dataSource()抛出NamingException {
return(DataSource)jndiTemplate.lookup(“MyDS”);
此外,使用@Bean方法,您通常会选择使用编程JNDI查找:使用Spring的JndiTemplate / JndiLocatorDelegate帮助程序或直接JNDI InitialContext用法,但不使用强制您将返回类型声明为FactoryBean类型的JndiObjectFactoryBean变体实际的目标类型,使其更难用于其他打算在此处引用所提供资源的@Bean方法中的交叉引用调用。

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

@Configuration
public class AppConfig {

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

    // ...
}

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

指定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代理示例从XML参考文档(参见前面的链接)移植到我们的 @Bean ,它将如下所示:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

自定义bean命名

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

@Configuration
public class AppConfig {

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

Bean别名

正如 Section 7.3.1, “Naming beans” 中所讨论的,有时需要为单个bean提供多个名称,也称为bean别名。为此, @Bean 注释的 name 属性接受String数组。

@Configuration
public class AppConfig {

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

Bean说明

有时提供bean的更详细的文本描述是有帮助的。当bean暴露(可能通过JMX)用于监视目的时,这可能特别有用。

要向 @Bean 添加描述,可以使用 @Description 注释:

@Configuration
public class AppConfig {

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

7.12.4 使用@Configuration批注

@Configuration 是类级别注释,指示对象是bean定义的源。 @Configuration classes通过public @Bean 注释方法声明bean。调用 @Configuration 类上的 @Bean 方法也可用于定义bean间依赖项。有关一般介绍,请参见 Section 7.12.1, “Basic concepts: @Bean and @Configuration”

注入bean间依赖关系

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

@Configuration
public class AppConfig {

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

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

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

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

查找方法注入

如前所述, lookup method injection 是一项很少使用的高级功能。在单例范围的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 的新实例并将其返回,因此通常需要2个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的bean默认具有 singleton 范围。这就是魔术的来源:所有 @Configuration 类在启动时使用 CGLIB 进行子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域)bean。请注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已在 org.springframework.cglib 下重新打包并直接包含在spring-core JAR中。

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

由于CGLIB在启动时动态添加功能,因此存在一些限制,特别是配置类不能是最终的。但是,从4.3开始,配置类允许使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。如果您希望避免任何CGLIB强加的限制,请考虑在非@Configuration类上声明您的@Bean方法,例如而是在普通的@Component类上。然后,@ Bean方法之间的跨方法调用将不会被拦截,因此您必须完全依赖于构造函数或方法级别的依赖注入。

7.12.5 编写基于Java的配置

使用@Import注释

就像在Spring XML文件中使用 <import/> 元素来帮助模块化配置一样, @Import 注释允许加载 @Bean 来自另一个配置类的定义:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在,在实例化上下文时,不需要同时指定 ConfigA.classConfigB.class ,只需要显式提供 ConfigB

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求开发人员在构造期间记住可能大量的类.677194_类。

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

注入对导入的@Bean定义的依赖关系

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

幸运的是,解决这个问题很简单。作为 we already discussed@Bean 方法可以具有描述bean依赖关系的任意数量的参数。让我们考虑一个更真实的场景,其中包含几个 @Configuration 类,每个类都取决于其他类中声明的bean:

@Configuration
public class ServiceConfig {

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

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有另一种方法可以达到相同的效果。请记住, @Configuration 类最终只是容器中的另一个bean:这意味着它们可以像其他任何bean一样利用 @Autowired@Value 注入等!

确保以这种方式注入的依赖项仅为最简单的依赖项。 @Configuration类在上下文初始化期间很早就被处理,并且强制以这种方式注入依赖项可能导致意外的早期初始化。尽可能采用基于参数的注入,如上例所示。另外,通过@Bean特别注意BeanPostProcessor和BeanFactoryPostProcessor定义。这些通常应该声明为静态@Bean方法,而不是触发其包含配置类的实例化。否则,@ Autowired和@Value将无法在配置类本身上工作,因为它过早地被创建为bean实例。

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

仅在Spring Framework 4.3中支持@Configuration类中的构造函数注入。另请注意,如果目标bean只定义了一个构造函数,则无需指定@Autowired;在上面的示例中,RepositoryConfig构造函数中不需要@Autowired。

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

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

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

在上面的情况中,它是完全明确的,其中定义了 AccountRepository 。但是, ServiceConfig 现在与 RepositoryConfig 紧密耦合;这是权衡。通过使用基于接口的或基于类的抽象 @Configuration 类,可以在某种程度上减轻这种紧密耦合。考虑以下:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

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

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在 ServiceConfig 与具体的 DefaultRepositoryConfig 松散耦合,内置的IDE工具仍然很有用:开发人员很容易获得 RepositoryConfig 实现的类型层次结构。通过这种方式,导航 @Configuration 类及其依赖项与导航基于接口的代码的常规过程没有什么不同。

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

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

基于某些任意方式有条件地启用或禁用完整的 @Configuration 类,甚至单独的 @Bean 方法通常很有用。系统状态。一个常见的例子是只有在Spring Environment 中启用了特定的配置文件时才使用 @Profile 注释激活bean(有关详细信息,请参阅 Section 7.13.1, “Bean definition profiles” )。

@Profile 注释实际上是使用名为 @Conditional 的更灵活的注释实现的。 @Conditional 注释指示在注册 @Bean 之前应查阅的特定 org.springframework.context.annotation.Condition 实现。

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

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

有关详细信息,请参阅 @Conditional javadocs

结合Java和XML配置

Spring的 @Configuration 类支持并非旨在成为Spring XML的100%完全替代品。诸如Spring XML命名空间之类的一些工具仍然是配置容器的理想方式。在XML方便或必要的情况下,您可以选择:使用 ClassPathXmlApplicationContext 以 "XML-centric" 方式实例化容器,或使用 AnnotationConfigApplicationContext 以 "Java-centric" 方式实例化 "Java-centric" 注释以根据需要导入XML。

以XML为中心使用@Configuration类

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

请记住, @Configuration 类最终只是容器中的bean定义。在此示例中,我们创建一个名为 AppConfig@Configuration 类,并将其作为 <bean/> 定义包含在 system-test-config.xml 中。由于 <context:annotation-config/> 已打开,容器将识别 @Configuration 注释并正确处理 AppConfig 中声明的 @Bean 方法。

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

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

system-test-config.xml

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

在上面的system-test-config.xml中,AppConfig 不声明id元素。虽然这样做是可以接受的,但是没有其他bean可以引用它,并且不太可能通过名称从容器中显式获取它。与DataSource bean类似 - 它只是按类型自动装配,因此不严格要求显式bean ID。

由于 @Configuration 是使用 @Component 元注释的,因此 @Configuration 注释类自动成为组件扫描的候选者。使用与上面相同的方案,我们可以重新定义 system-test-config.xml 以利用组件扫描。请注意,在这种情况下,我们不需要显式声明 <context:annotation-config/> ,因为 <context:component-scan/> 启用相同的功能。

system-test-config.xml

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Configuration以类为中心使用带@ImportResource的XML

@Configuration 类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,只需使用 @ImportResource 并根据需要定义尽可能多的XML。这样做可以实现配置容器的方法,并将XML保持在最低限度。

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

7.13 环境抽象

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

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

属性在几乎所有应用程序中都发挥着重要作用,可能源自各种源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,映射等。与属性相关的 Environment 对象的作用是为用户提供方便的服务接口,用于配置属性源和从中解析属性。

7.13.1 Bean定义配置文件

Bean定义配置文件是核心容器中的一种机制,允许在不同环境中注册不同的bean。单词环境对不同的用户来说意味着不同的东西,这个功能可以帮助许多用例,包括:

  • 在开发中使用内存中的数据源,在QA或 生产环境 环境中查找来自JNDI的相同数据源

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

  • 为客户A与客户B部署注册bean的自定义实现

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

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

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

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

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

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

@Profile

@Profile 注释允许您在一个或多个指定的配置文件处于活动状态时指示组件符合注册条件。使用上面的示例,我们可以重写 dataSource 配置,如下所示:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

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

如前所述,使用@Bean方法,您通常会选择使用编程JNDI查找:使用Spring的JndiTemplate / JndiLocatorDelegate帮助程序或上面显示的直接JNDI InitialContext用法,但不会强制使用JndiObjectFactoryBean变量来强制您声明返回类型为FactoryBean类型。

@Profile 可用作 meta-annotation ,以创建自定义组合注释。以下示例定义了一个自定义 @Production 注释,可用作 @Profile("production") 的插入式替换:

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

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

@Profile 也可以在方法级别声明,以仅包括配置类的一个特定bean,例如对于特定bean的替代变体:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development")
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

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

在@Bean方法上使用@Profile,可能会应用一个特殊的场景:对于相同Java方法名称的重载@Bean方法(类似于构造函数重载),需要在所有重载方法上一致地声明@Profile条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,@ Version不能用于选择具有特定参数签名的重载方法而不是另一个;同一个bean的所有工厂方法之间的分辨率遵循Spring的构造函数解析算法在创建时。如果要定义具有不同配置文件条件的备用Bean,请使用通过@Bean name属性指向同一bean名称的不同Java方法名称,如上例所示。如果参数签名都是相同的(例如,所有变体都具有no-arg工厂方法),那么这是首先在有效Java类中表示这种排列的唯一方法(因为只有一种方法可以特定的名称和参数签名)。

XML bean定义配置文件

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

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

也可以避免在同一文件中拆分和嵌套 <beans/> 元素:

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

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

spring-bean.xsd 已被限制为仅允许此类元素作为文件中的最后一个元素。这应该有助于提供灵活性,而不会在XML文件中引起混乱。

激活 Profiles

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

激活配置文件可以通过多种方式完成,但最直接的方法是以编程方式对照 Environment API进行操作。 ApplicationContext

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

此外,还可以通过 spring.profiles.active 属性以声明方式激活配置文件,该属性可以通过系统环境变量,JVM系统属性, web.xml 中的servlet上下文参数或甚至作为JNDI中的条目(请参阅 Section 7.13.2, “PropertySource abstraction” )指定。在集成测试中,可以通过 spring-test 模块中的 @ActiveProfiles 注释声明活动配置文件(请参阅 the section called “Context configuration with environment profiles” )。

请注意,配置文件不是 "either-or" 命题;可以一次激活多个配置文件。以编程方式,只需为 setActiveProfiles() 方法提供多个配置文件名称,该方法接受 String… varargs:

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

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

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

默认配置文件

默认配置文件表示默认启用的配置文件。考虑以下:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

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

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

7.13.2 PropertySource抽象

Spring的 Environment 抽象提供了对可配置的属性源层次结构的搜索操作。要完整解释,请考虑以下事项:

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

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

这些默认属性源存在于StandardEnvironment中,用于独立应用程序。 StandardServletEnvironment使用其他默认属性源填充,包括servlet配置和servlet上下文参数。类似地,StandardPortletEnvironment可以访问portlet配置和portlet上下文参数作为属性源。两者都可以选择启用JndiPropertySource。有关详细信息,请参阅javadocs。

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

执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此如果在调用env.getProperty(“foo”)期间恰好在两个位置都设置了foo属性,则系统属性值将为“win”并优先返回环境变量。请注意,属性值不会被合并,而是被前面的条目完全覆盖。对于公共StandardServletEnvironment,完整层次结构如下所示,顶部具有最高优先级条目:ServletConfig参数(如果适用,例如在DispatcherServlet上下文的情况下)ServletContext参数(web.xml context-param条目)JNDI环境变量(“java:comp / env /”条目)JVM系统属性(“-D”命令行参数)JVM系统环境(操作系统环境变量)

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

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

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

7.13.3 @PropertySource

@PropertySource 注释提供了一种方便的声明式机制,用于向Spring的 Environment 添加 PropertySource

给定包含键/值对 testbean.name=myTestBean 的文件 "app.properties" ,以下 @Configuration 类使用 @PropertySource ,使得对 testBean.getName() 的调用将返回 "myTestBean" 。

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

存在于 @PropertySource 资源位置的任何 ${…} 占位符将针对已针对环境注册的属性源集合进行解析。例如:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设 "my.placeholder" 存在于已经注册的一个属性源中,例如系统属性或环境变量,占位符将被解析为相应的值。如果没有,那么 "default/path" 将被用作默认值。如果未指定缺省值且无法解析属性,则将抛出 IllegalArgumentException

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

7.13.4 占位符决议在陈述中

从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。情况不再如此。因为环境抽象集成在整个容器中,所以很容易通过它来解决占位符的分辨率。这意味着您可以以任何您喜欢的方式配置解析过程:更改搜索系统属性和环境变量的优先级,或者完全删除它们;根据需要将您自己的属性源添加到混合中。

具体而言,只要 customer 属性在 Environment 中可用,以下语句就可以工作:

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

7.14 注册LoadTimeWeaver

Spring使用 LoadTimeWeaver 在类加载到Java虚拟机(JVM)时动态转换类。

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

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

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

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

一旦配置为 ApplicationContextApplicationContext 中的任何bean都可以实现 LoadTimeWeaverAware ,从而接收对load-time weaver实例的引用。这与 Spring’s JPA support 结合使用特别有用,其中JPA类转换可能需要加载时编织。有关更多详细信息,请参阅 LocalContainerEntityManagerFactoryBean javadocs。有关AspectJ加载时编织的更多信息,请参阅 Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework”

7.15 ApplicationContext的其他功能

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

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

  • 通过 MessageSource 界面访问i18n-style的消息。

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

  • 事件发布,即通过使用 ApplicationEventPublisher 接口实现 ApplicationListener 接口的bean。

  • 加载多个(分层)上下文,允许每个上下文通过 HierarchicalBeanFactory 界面聚焦于一个特定层,例如应用程序的Web层。

7.15.1 使用MessageSource进行国际化

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

  • String getMessage(String code, Object[] args, String default, Locale loc) :用于从 MessageSource 检索消息的基本方法。如果未找到指定区域设置的消息,则使用默认消息。传入的任何参数都使用标准库提供的 MessageFormat 功能成为替换值。

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

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

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

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

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

在该示例中,假设您在类路径中定义了三个名为 formatexceptionswindows 的资源包。任何解决消息的请求都将以JDK标准的方式处理,通过ResourceBundles解析消息。出于示例的目的,假设上述两个资源包文件的内容是......

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

下一个示例中显示了执行 MessageSource 功能的程序。请记住,所有 ApplicationContext 实现也都是 MessageSource 实现,因此可以强制转换为 MessageSource 接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

上述程序产生的结果将是......

Alligators rock!

总而言之, MessageSource 在名为 beans.xml 的文件中定义,该文件存在于类路径的根目录中。 messageSource bean定义通过其 basenames 属性引用了许多资源包。在列表中传递给 basenames 属性的三个文件作为类路径根目录下的文件存在,分别称为 format.propertiesexceptions.propertieswindows.properties

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

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.foo.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}

调用 execute() 方法得到的结果将是......

The userDao argument is required.

关于国际化(i18n),Spring的各种 MessageSource 实现遵循与标准JDK ResourceBundle 相同的区域设置解析和回退规则。简而言之,继续前面定义的示例 messageSource ,如果要解析针对英国( en-GB )语言环境的消息,您将分别创建名为 format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties 的文件。

通常,区域设置解析由应用程序的周围环境管理。在此示例中,将手动指定将解析(英国)消息的区域设置。

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

运行上述程序产生的结果将是......

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

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

作为ResourceBundleMessageSource的替代,Spring提供了一个ReloadableResourceBundleMessageSource类。此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置(不仅仅是从类路径)读取文件,并支持bundle属性文件的热重新加载(同时有效地在它们之间缓存它们)。有关详细信息,请查看ReloadableResourceBundleMessageSource javadocs。

7.15.2 标准和自定义事件

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

从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注释的模型以及发布任意事件的能力,这是一个不一定从ApplicationEvent扩展的对象。当发布这样的对象时,我们将它包装在一个事件中。

Spring提供以下标准事件:

Table 7.7. Built-in Events

活动说明
ContextRefreshedEvent初始化或刷新 ApplicationContext 时发布,例如,使用 ConfigurableApplicationContext 接口上的 refresh() 方法。 "Initialized" 这里表示已加载所有bean,检测并激活后处理器bean,预先实例化单例,并且 ApplicationContext 对象已准备就绪。只要上下文尚未关闭,只要所选的 ApplicationContext 实际上支持这样的 "hot" 刷新,就可以多次触发刷新。例如, XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。
ContextStartedEvent使用 ConfigurableApplicationContext 接口上的 start() 方法启动 ApplicationContext 时发布。 "Started" 这里表示所有 Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未为自动启动配置的组件,例如,尚未在初始化时启动的组件。
ContextStoppedEvent使用 ConfigurableApplicationContext 接口上的 stop() 方法停止 ApplicationContext 时发布。 "Stopped" 这里表示所有 Lifecycle bean都收到一个明确的停止信号。可以通过 start() 重新启动已停止的上下文呼叫。
ContextClosedEventApplicationContext 关闭后,使用 ConfigurableApplicationContext 界面上的 close() 方法发布。 "Closed" 这里表示所有单例bean都被销毁。封闭的环境达到其生命的终点;它无法刷新或重新启动。
RequestHandledEvent一个特定于Web的事件,告诉所有bean已经为HTTP请求提供服务。请求完成后发布此事件。此事件仅适用于使用Spring的 DispatcherServlet 的Web应用程序。

您还可以创建和发布自己的自定义事件。这个例子演示了一个扩展Spring的 ApplicationEvent 基类的简单类:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

要发布自定义 ApplicationEvent ,请在 ApplicationEventPublisher 上调用 publishEvent() 方法。通常,这是通过创建一个实现 ApplicationEventPublisherAware 并将其注册为Spring bean的类来完成的。以下示例演示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

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

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

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

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

以下示例显示了用于注册和配置上述每个类的bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>

总而言之,当调用 emailService bean的 sendEmail() 方法时,如果有任何电子邮件应该被列入黑名单,则会发布类型为 BlackListEvent 的自定义事件。 blackListNotifier bean注册为 ApplicationListener ,因此接收 BlackListEvent ,此时它可以通知相关方。

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

基于注释的事件侦听器

从Spring 4.2开始,可以通过 EventListener 注释在托管bean的任何公共方法上注册事件侦听器。 BlackListNotifier 可以重写如下:

public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

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

如果您的方法应该监听多个事件,或者您想要根据任何参数进行定义,那么也可以在注释本身上指定事件类型:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

还可以通过注释的 condition 属性添加额外的运行时过滤,该属性定义 SpEL expression ,该 SpEL expression 应该匹配以实际调用特定事件的方法。

例如,如果事件的 content 属性等于 foo ,则可以重写我们的通知程序以仅调用:

@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个 SpEL 表达式都针对专用上下文进行求值。下一个表列出了可用于上下文的项目,因此可以将它们用于条件事件处理:

Table 7.8. Event SpEL available metadata

名称位置说明示例
事件根对象实际 ApplicationEvent#root.event
参数arrayroot object用于调用目标的参数(作为数组)#root.args[0]
参数名称评估上下文任何方法参数的名称。如果由于某种原因名称不可用(例如没有调试信息),则参数名称也可在 #a<#arg> 下获得,其中#arg代表参数索引(从0开始)。#blEvent#a0 (也可以使用 #p0#p<#arg> 表示法作为别名)。请注意, #root.event 允许您访问基础事件,即使您的方法签名实际上是指已发布的任意对象。

如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名以返回应该发布的事件,例如:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

异步侦听器不支持此功能。

这个新方法将为上述方法处理的每个 BlackListEvent 发布一个新的 ListUpdateEvent 。如果您需要发布多个事件,请返回 Collection 事件。

异步侦听器

如果您希望特定侦听器异步处理事件,只需重用 regular @Async support

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

使用异步事件时请注意以下限制:

  • 如果事件侦听器抛出 Exception 它将不会传播给调用者,请检查 AsyncUncaughtExceptionHandler 以获取更多详细信息。

  • 此类事件侦听器无法发送回复。如果您需要作为处理结果发送另一个事件,请注入 ApplicationEventPublisher 以手动发送事件。

订购听众

如果需要在另一个之前调用侦听器,只需将 @Order 注释添加到方法声明中:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

通用事件

您还可以使用泛型来进一步定义事件的结构。考虑一个 EntityCreatedEvent<T> ,其中 T 是创建的实际实体的类型。您可以创建以下侦听器定义以仅接收 PersonEntityCreatedEvent

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由于类型擦除,这仅在被触发的事件解析事件侦听器在其上过滤的泛型参数(类似于 class PersonCreatedEvent extends EntityCreatedEvent<Person> { … } )时才有效。

在某些情况下,如果所有事件都遵循相同的结构(这应该是上述事件的情况),这可能会变得相当繁琐。在这种情况下,您可以实现 ResolvableTypeProvider 来指导框架超出运行时环境提供的范围:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(),
                ResolvableType.forInstance(getSource()));
    }
}

这不仅适用于ApplicationEvent,也适用于您作为事件发送的任意对象。

7.15.3 方便地访问低级资源

为了最佳地使用和理解应用程序上下文,用户通常应该熟悉Spring的 Resource 抽象,如 Chapter 8, Resources 章节所述。

应用程序上下文是 ResourceLoader ,可用于加载 ResourceResource 本质上是JDK类 java.net.URL 的功能更丰富的版本,实际上, Resource 的实现在适当的情况下包装 java.net.URL 的实例。 Resource 可以透明的方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,任何可用标准URL描述的位置,以及一些其他变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际应用程序上下文类型。

您可以配置部署到应用程序上下文中的bean,以实现特殊的回调接口 ResourceLoaderAware ,在初始化时自动回调,应用程序上下文本身作为 ResourceLoader 传入。您还可以公开 Resource 类型的属性,以用于访问静态资源;它们将像任何其他属性一样注入其中。您可以将这些 Resource 属性指定为简单的String路径,并依赖上下文自动注册的特殊JavaBean PropertyEditor ,以便在部署Bean时将这些文本字符串转换为实际的 Resource 对象。

提供给 ApplicationContext 构造函数的位置路径实际上是资源字符串,并且以简单形式适当地处理特定上下文实现。 ClassPathXmlApplicationContext 将简单的位置路径视为类路径位置。您还可以使用具有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义,而不管实际的上下文类型如何。

7.15.4 方便的Web应用程序的ApplicationContext实例化

您可以使用例如 ContextLoader 以声明方式创建 ApplicationContext 实例。当然,您也可以使用 ApplicationContext 实现之一以编程方式创建 ApplicationContext 实例。

您可以使用 ContextLoaderListener 注册 ApplicationContext ,如下所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

监听器检查 contextConfigLocation 参数。如果参数不存在,则侦听器将 /WEB-INF/applicationContext.xml 用作默认值。当参数确实存在时,侦听器使用预定义的分隔符(逗号,分号和空格)分隔String,并将值用作将搜索应用程序上下文的位置。还支持Ant样式的路径模式。对于名称以 "Context.xml" 结尾的所有文件(位于 "WEB-INF" 目录中)和 /WEB-INF/**/*Context.xml (对于 "WEB-INF" 的任何子目录中的所有此类文件),示例为 /WEB-INF/*Context.xml

7.15.5 部署SpringApplicationContext作为Java EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext,它只是托管在Java EE环境中,能够访问Java EE服务器设施。 RAR部署是部署无头WAR文件的场景的更自然的替代方案,实际上是没有任何HTTP入口点的WAR文件,仅用于在Java EE环境中引导Spring ApplicationContext。

RAR部署非常适用于不需要HTTP入口点但仅包含消息 endpoints 和预定作业的应用程序上下文。在这样的上下文中的Bean可以使用应用程序服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSources和JMS ConnectionFactory实例,也可以使用Spring的标准事务管理以及JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以通过Spring的 TaskExecutor 抽象与应用程序服务器的JCA WorkManager进行交互。

查看 SpringContextResourceAdapter 类的javadoc以获取RAR部署中涉及的配置详细信息。

对于将Spring ApplicationContext简单部署为Java EE RAR文件:将所有应用程序类打包到RAR文件中,该文件是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR存档的根目录中。添加 "META-INF/ra.xml" 部署描述符(如 SpringContextResourceAdapter s javadoc中所示)和相应的Spring XML bean定义文件(通常为 "META-INF/applicationContext.xml" ),并将生成的RAR文件放入应用程序服务器的部署目录中。

此类RAR部署单位通常是独立的;它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的ApplicationContext的交互通常通过与其他模块共享的JMS目标进行。例如,基于RAR的ApplicationContext还可以调度一些作业,对文件系统中的新文件(或类似物)作出反应。如果它需要允许来自外部的同步访问,它可以例如导出RMI endpoints ,当然可以由同一机器上的其他应用程序模块使用。

7.16 BeanFactory

BeanFactory API为Spring的IoC功能提供了基础。其特定 Contract 主要用于与Spring的其他部分和相关的第三方框架集成,其 DefaultListableBeanFactory 实现是更高级别 GenericApplicationContext 容器中的关键委托。

BeanFactory 和相关的接口,如 BeanFactoryAwareInitializingBeanDisposableBean 是其他框架组件的重要集成点:不需要任何注释甚至反射,它们允许容器与其组件之间的非常有效的交互。应用程序级bean可以使用相同的回调接口,但通常更喜欢通过注释或通过编程配置进行声明性依赖注入。

请注意,核心 BeanFactory API级别及其 DefaultListableBeanFactory 实现不会对配置格式或要使用的任何组件注释做出假设。所有这些风格都通过扩展来实现,例如 XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor ,作为核心元数据表示在共享的 BeanDefinition 对象上运行。这是使Spring的容器如此灵活和可扩展的本质。

以下部分解释了 BeanFactoryApplicationContext 容器级别之间的差异以及对引导的影响。

7.16.1 BeanFactory或ApplicationContext?

使用 ApplicationContext ,除非你有充分的理由不这样做, GenericApplicationContext 及其子类 AnnotationConfigApplicationContext 作为自定义引导的常见实现。这些是Spring用于所有常见目的的核心容器的主要入口点:加载配置文件,触发类路径扫描,以编程方式注册bean定义和带注释的类。

因为 ApplicationContext 包含 BeanFactory 的所有功能,所以除了需要完全控制bean处理的场景之外,通常建议使用 BeanFactory 。在诸如 GenericApplicationContext 实现之类的 ApplicationContext 中,将按惯例检测几种bean(即通过bean名称或bean类型),特别是后处理器,而普通 DefaultListableBeanFactory 对任何特殊bean都是不可知的。

对于许多扩展容器功能,例如注释处理和AOP代理, BeanPostProcessor extension point 是必不可少的。如果仅使用普通的 DefaultListableBeanFactory ,则默认情况下不会检测到并激活此类后处理器。这种情况可能令人困惑,因为您的bean配置实际上没有任何问题;它更像是需要充分的容器在这种情况下通过其他设置进行自举。

下表列出了 BeanFactoryApplicationContext 接口和实现提供的功能。

Table 7.9. Feature Matrix

专题BeanFactoryApplicationContext
Bean实例化/接线
集成生命周期管理
自动 BeanPostProcessor 注册
自动 BeanFactoryPostProcessor 注册
方便 MessageSource 访问(内部化)
内置 ApplicationEvent 发布机制

要使用 DefaultListableBeanFactory 显式注册bean后处理器,您需要以编程方式调用 addBeanPostProcessor

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory

要将 BeanFactoryPostProcessor 应用于普通 DefaultListableBeanFactory ,您需要调用其 postProcessBeanFactory 方法:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都不方便,这就是为什么各种 ApplicationContext 变体比Spring支持的应用程序中的普通 DefaultListableBeanFactory 更受欢迎,特别是在典型企业设置中依赖 BeanFactoryPostProcessorBeanPostProcessor 来扩展容器功能时。

AnnotationConfigApplicationContext具有开箱即用的所有常见注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)在封面下方引入其他处理器。在Spring的基于注释的配置模型的抽象级别,bean后处理器的概念变成仅仅是内部容器细节。

7.16.2 胶水代码和邪恶的单身人士

最好以依赖注入(DI)方式编写大多数应用程序代码,其中该代码由Spring IoC容器提供,在创建容器时由容器提供自己的依赖关系,并且完全不知道容器。但是,对于有时需要将其他代码绑定在一起的小型胶代码层,有时需要对Spring IoC容器进行单例(或准单例)样式访问。例如,第三方代码可能会尝试直接构造新对象( Class.forName() 样式),而无法从Spring IoC容器中获取这些对象。如果由第三方代码构造的对象是一个小的存根或代理,然后使用单一样式访问Spring IoC容器来获取委托的真实对象,然后仍然为大多数代码(来自容器的对象)实现了控制的反转。因此,大多数代码仍然没有意识到容器或它是如何被访问的,并且仍然与其他代码分离,并带来所有后续的好处。 EJB也可以使用这种存根/代理方法委托从Spring IoC容器中检索的普通Java实现对象。虽然Spring IoC容器本身在理想情况下不必是单例,但就内存使用或初始化时间(在Spring IoC容器中使用bean,例如Hibernate SessionFactory )而言,每个bean使用自己的bean时,它可能是不现实的,非单例Spring IoC容器。

在服务定位器样式中查找应用程序上下文有时是访问共享的Spring管理组件的唯一选项,例如在EJB 2.1环境中,或者当您希望在WAR文件中共享单个ApplicationContext作为WebApplicationContexts的父项时。在这种情况下,您应该考虑使用此 Spring team blog entry 中描述的实用程序类 ContextSingletonBeanFactoryLocator 定位器。


[1]Background

[2]Section 7.4.1, “Dependency Injection”

Updated at: 5 months ago
III. 核心技术Table of content8. 资源
Comment
You are not logged in.

There are no comments.