参考文档的这一部分涵盖了对Spring框架绝对不可或缺的所有技术。

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

本文还介绍了Spring与AspectJ的集成(就特性而言,它是目前最丰富的 - ,当然也是 企业空间中最成熟的AOP实现)。

AOT处理可用于提前优化您的应用程序。它通常用于使用GraalVM的本机映像部署。

1. The IoC Container

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

1.1. Introduction to the Spring IoC Container and Beans

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

org.springmework.Beansorg.springmework.context包是Spring框架的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。ApplicationContextBeanFactory的子接口。它补充道:

  • 更轻松地与Spring的AOP功能集成

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

  • 活动发布

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

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

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

1.2. Container Overview

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

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

在大多数应用程序场景中,实例化一个或多个Spring IOC容器实例不需要显式的用户代码。例如,在Web应用程序场景中,应用程序的web.xml文件中简单的八行(大约)行样板Web描述符XML通常就足够了(请参阅Web应用程序的便捷ApplicationContext实例化)。如果您使用Spring Tools for Eclipse(一个基于Eclipse的开发环境),只需点击几下鼠标或敲击几下键盘,就可以轻松地创建这个样板配置。

下图显示了Spring如何工作的高级视图。您的应用程序类与配置元数据结合在一起,这样,在创建和初始化ApplicationContext之后,您就拥有了一个完全配置且可执行的系统或应用程序。

container magic
Figure 1. The Spring IoC container

1.2.1. Configuration Metadata

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

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

XML-based metadata is not the only allowed form of configuration metadata. The Spring IoC container itself is totally decoupled from the format in which this configuration metadata is actually written. These days, many developers choose Java-based configuration for their Spring applications.

有关将其他形式的元数据与Spring容器一起使用的信息,请参阅:

  • 基于注释的配置:Spring2.5引入了对基于注释的配置元数据的支持。

  • 基于Java的配置:从Spring3.0开始,由Spring JavaConfig项目提供的许多特性成为核心Spring框架的一部分。因此,您可以使用Java文件而不是XML文件在应用程序类外部定义Bean。要使用这些新功能,请参阅@configuration@Bean@Import和@DependsOn注释。

Spring配置由容器必须管理的至少一个且通常不止一个的Bean定义组成。基于XML的配置元数据将这些Bean配置为顶级<;Bean/元素中的<;Bean/元素。Java配置通常在@Configuration类中使用@Bean注释的方法。

这些Bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象、数据访问对象(DAO)、表示对象(如StrutsAction实例)、基础结构对象(如HibernateSessionFaciles、JMS队列等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的责任。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。请参阅使用AspectJ通过Spring依赖注入域对象

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

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

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

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

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

</beans>
             
             
1 The id attribute is a string that identifies the individual bean definition.
2 The class attribute defines the type of the bean and uses the fully qualified classname.

id属性的值引用协作对象。本例中未显示用于引用协作对象的XML。有关详细信息,请参阅依赖项。

1.2.2. Instantiating a Container

提供给ApplicationContext构造函数的一个或多个位置路径是允许容器从各种外部资源(如本地文件系统、JavaCLASSPATH等)加载配置元数据的资源字符串。

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

             
             

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

以下示例显示了服务层对象(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 https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

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

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

</beans>
             
             

以下示例显示数据访问对象daos.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://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类和JpaAccount tDaoJpaItemDao类型的两个数据访问对象组成(基于JPA对象-关系映射标准)。属性名称元素引用JavaBean属性的名称,ref元素引用另一个Bean定义的名称。idref元素之间的这种链接表示协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅依赖项

Composing XML-based Configuration Metadata

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

您可以使用应用程序上下文构造函数从所有这些XML片段加载Bean定义。此构造函数接受多个资源位置,如上一节所示。或者,使用一次或多次出现的<;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.xmlhemeSource.xml。所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而MessageSource.xmlhemeSource.xml必须位于导入文件位置下方的资源位置。如您所见,前导斜杠被忽略。然而,考虑到这些路径是相对的,最好根本不使用斜杠。根据Spring模式,要导入的文件的内容,包括顶级<;Beans/>;元素,必须是有效的XMLBean定义。

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

您可以始终使用完全限定的资源位置,而不是相对路径:例如,文件:C:/config/services.xmlclasspath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。例如,通过在运行时根据 系统属性解析的“${jvm​}”占位符,为这样的绝对位置 - 保留间接性通常是更可取的。

命名空间本身提供了导入指令功能。除了普通的Bean定义之外,还有更多的配置特性可以在由SpringBean- 提供的一系列 名称空间中使用,例如上下文util名称空间。

The Groovy Bean Definition 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
        }
    }
}

              
              

这种配置风格在很大程度上等同于XMLBean定义,甚至支持Spring的XML配置命名空间。它还允许通过portBeans指令导入XML Bean定义文件。

1.2.3. Using the Container

ApplicationContext是高级工厂的接口,能够维护不同Bean及其依赖项的注册表。通过使用T getBean(字符串名称,Class<;T>;Required dType)方法,您可以检索Bean的实例。

ApplicationContext允许您读取和访问Bean定义,如下面的示例所示:

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

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

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

             
             

使用Groovy配置时,引导看起来非常相似。它有一个不同的上下文实现类,该实现类支持Groovy(但也理解XMLBean定义)。下面的示例显示了Groovy配置:

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

             
             

最灵活的变体是GenericApplicationContext与读取器委托 - 结合使用,以及用于xml文件的XmlBeanDefinitionReader,如下面的示例所示:

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

             
             

您还可以对Groovy文件使用GroovyBeanDefinitionReader,如下面的示例所示:

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

             
             

您可以在相同的ApplicationContext上混合和匹配这些读取器委托,从不同的配置源读取Bean定义。

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

1.3. Bean Overview

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

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

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

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

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

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

该元数据转换为组成每个Bean定义的一组属性。下表介绍了这些属性:

Table 1. The bean definition
Property Explained in…​

班级

实例化Bean

名字

命名Bean

范围

Bean作用域

构造函数参数

依赖项注入

属性

依赖项注入

自动配线模式

自动连接协作者

延迟初始化模式

延迟初始化的Bean

初始化方法

初始化回调

销毁方法

销毁回调

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

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

1.3.1. Naming Beans

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

在基于XML的配置元数据中,您可以使用id属性、名称属性或同时使用这两个属性来指定Bean标识符。id属性允许您只指定一个id。通常,这些名称是字母数字的(‘myBean’、‘omeService’等),但它们也可以包含特殊字符。如果要为Bean引入其他别名,还可以在name属性中指定它们,用逗号()、分号()或空格分隔。作为历史记录,在Spring3.1之前的版本中,id属性被定义为xsd:id类型,这限制了可能的字符。从3.1开始,它被定义为xsd:字符串类型。请注意,Beanid唯一性仍然由容器强制实现,尽管不再由XML解析器强制实现。

您不需要为Bean提供名称id。如果您没有显式地提供名称id,容器将为该Bean生成一个唯一的名称。但是,如果您希望通过使用ref元素或Service Locator样式查找来按名称引用该Bean,则必须提供一个名称。不提供名称的动机与使用内部Bean自动生成协作者有关。

Bean Naming Conventions

约定是在命名Bean时使用标准的Java约定,例如字段名称。也就是说,Bean名称以小写字母开头,并且从那里开始采用驼峰大小写。此类名称的示例包括帐户管理器帐户服务userDao登录控制器等。

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

With component scanning in the classpath, Spring generates bean names for unnamed components, following the rules described earlier: essentially, taking the simple class name and turning its initial character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by java.beans.Introspector.decapitalize (which Spring uses here).
Aliasing a Bean outside the Bean Definition

在Bean定义本身中,您可以为Bean提供多个名称,方法是使用由id属性指定的最多一个名称和name属性中的任意数量的其他名称。这些名称可以是相同Bean的等效别名,并且在某些情况下非常有用,例如,通过使用特定于组件本身的Bean名称,让应用程序中的每个组件引用公共依赖项。

然而,指定实际定义Bean的所有别名并不总是足够的。有时需要为在其他地方定义的Bean引入别名。这通常是在大型系统中的情况,其中配置在每个子系统之间拆分,每个子系统具有其自己的对象定义集。在基于XML的配置元数据中,您可以使用<;alias/>;元素来实现这一点。以下示例显示了如何执行此操作:

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

在这种情况下,在使用该别名定义之后,名为from名称的Bean(在同一容器中)也可能被称为toName

例如,子系统A的配置元数据可以通过子系统A-DataSource的名称来引用数据源。子系统B的配置元数据可以通过子系统B-DataSource的名称来引用数据源。在构建使用这两个子系统的主应用程序时,主应用程序使用名称myApp-DataSource引用数据源。要使这三个名称引用同一对象,您可以将以下别名定义添加到配置元数据:

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

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

Java-configuration

如果您使用Java配置,@Bean注释可以用来提供别名。有关详细信息,请参阅使用@Bean注释

1.3.2. Instantiating Beans

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

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

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

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

Nested class names

如果要为嵌套类配置Bean定义,则可以使用嵌套类的二进制名称或源名称。

例如,如果com.Example包中有一个名为Something的类,而这个Something类有一个名为OtherThing静态类,则它们可以用美元符号($)或圆点(.)分隔。因此,Bean定义中的<代码>类 属性的值应该是<代码>com.example.SomeThing$OtherThing或com.example.SomeThing.OtherThing.

Instantiation with a Constructor

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

Spring IOC容器几乎可以管理您希望它管理的任何类。它并不局限于管理真正的JavaBean。大多数Spring用户更喜欢只有一个默认(无参数)构造函数以及根据容器中的属性建模的适当setter和getter的实际JavaBean。您还可以在容器中包含更多奇异的非Bean样式的类。例如,如果您需要使用绝对不符合JavaBean规范的遗留连接池,Spring也可以管理它。

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

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

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

有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参阅注入依赖项

Instantiation with a Static Factory Method

在定义使用静态工厂方法创建的Bean时,使用属性指定包含静态工厂方法的类,并使用名为工厂方法的属性指定工厂方法本身的名称。您应该能够调用此方法(使用可选参数,如后面所述)并返回活动对象,该对象随后被视为是通过构造函数创建的。此类Bean定义的一个用途是在遗留代码中调用静态工厂。

下面的Bean定义指定将通过调用工厂方法来创建Bean。该定义没有指定返回对象的类型(类),而是指定了包含工厂方法的类。在本例中,createInstance()方法必须是静态方法。以下示例显示如何指定工厂方法:

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

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

Java
Kotlin
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

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

              
              

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

Instantiation by Using an Instance Factory Method

与通过静态工厂方法实例化类似,使用实例工厂方法实例化将调用容器中现有Bean的非静态方法来创建新Bean。要使用此机制,请将属性保留为空,并在Factory-Bean属性中指定当前(或父或祖先)容器中的Bean的名称,该容器包含要调用以创建对象的实例方法。使用Factory-method属性设置工厂方法本身的名称。以下示例显示如何配置此类Bean:

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

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

下面的示例显示了相应的类:

Java
Kotlin
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

              
              

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

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

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

<bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
              
              

下面的示例显示了相应的类:

Java
Kotlin
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)进行管理和配置。请参阅依赖项和配置的详细信息

In Spring documentation, "factory bean" refers to a bean that is configured in the Spring container and that creates objects through an instance or static factory method. By contrast, FactoryBean (notice the capitalization) refers to a Spring-specific FactoryBean implementation class.
Determining a Bean’s Runtime Type

确定特定Bean的运行时类型不是一件容易的事。Bean元数据定义中指定的类只是一个初始类引用,可能与已声明的工厂方法组合在一起,或者是一个可能导致Bean的不同运行时类型的FactoryBean类,或者在实例级工厂方法(通过指定的Factory-Bean名称解析)的情况下根本没有设置。此外,AOP代理可以用基于接口的代理包装Bean实例,并有限地公开目标Bean的实际类型(仅其实现的接口)。

要了解特定Bean的实际运行时类型,推荐的方法是为指定的Bean名称调用BeanFactory.getType。这会考虑以上所有情况,并返回BeanFactory.getBean调用将为相同的Bean名称返回的对象类型。

1.4. Dependencies

典型的企业应用程序并不由单个对象(或者用Spring的话来说就是Bean)组成。即使是最简单的应用程序也有几个对象一起工作,以呈现最终用户所看到的连贯的应用程序。下一节将解释如何从定义大量独立的Bean定义到完全实现的应用程序,在该应用程序中,对象协作以实现某个目标。

1.4.1. Dependency Injection

依赖项注入(DI)是一个过程,在此过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即,它们使用的其他对象)。然后,容器在创建Bean时注入这些依赖项。这个过程基本上是Bean本身的逆(由此得名,控制反转),通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。

使用依赖注入原则,代码更干净,当对象被提供依赖项时,解耦更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖于接口或抽象基类时,这允许在单元测试中使用存根或模拟实现。

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

Constructor-based Dependency Injection

基于构造函数的DI是通过容器调用具有多个参数的构造函数来实现的,每个参数代表一个依赖项。使用特定的参数调用静态工厂方法来构造Bean几乎是等价的,本讨论以类似的方式对待构造函数和静态工厂方法的参数。下面的示例显示了一个只能通过构造函数注入进行依赖项注入的类:

Java
Kotlin
public class SimpleMovieLister {

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

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

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

              
              

请注意,这个类没有什么特别之处。它是一个POJO,不依赖于容器特定的接口、基类或注释。

Constructor Argument Resolution

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

Java
Kotlin
package x.y;

public class ThingOne {

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

               
               

假设ThingTwoThingThree类通过继承不相关,则不存在潜在的歧义。因此,以下配置可以很好地工作,并且您不需要在<;Construction tor-arg/>;元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

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

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
               
               

当引用另一个Bean时,类型是已知的,并且可以进行匹配(就像前面的例子一样)。当使用简单类型时,例如<;Value>;True<;/Value>;,Spring无法确定值的类型,因此在没有帮助的情况下无法根据类型进行匹配。请考虑以下类:

Java
Kotlin
package examples;

public class ExampleBean {

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

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

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

               
               
Constructor argument type matching

在上述方案中,如果您使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型,如下面的示例所示:

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

您可以使用index属性显式指定构造函数参数的索引,如下面的示例所示:

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

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

The index is 0-based.
Constructor argument name

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

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

请记住,要使此功能开箱即用,必须在编译代码时启用调试标志,以便Spring可以从构造函数中查找参数名称。如果不能或不想用调试标志编译代码,可以使用@ConstructorPropertiesJDK注释显式命名构造函数参数。然后,样例类必须如下所示:

Java
Kotlin
package examples;

public class ExampleBean {

    // Fields omitted

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

                 
                 
Setter-based Dependency Injection

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

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

Java
Kotlin
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用户并不直接使用这些类(即以编程方式),而是使用基于Java的@configuration类中的XMLBean定义、带注释的组件(即用@Component@Controller等标注的类)或@Bean方法。然后在内部将这些源代码转换为BeanDefinition的实例,并用于加载整个Spring IOC容器实例。

Constructor-based or setter-based DI?

因为您可以混合使用基于构造函数和基于setter的DI,所以对强制依赖项使用构造函数,对可选依赖项使用setter方法或配置方法,这是一个很好的经验法则。请注意,在setter方法上使用@AuTower注释可以使该属性成为必需的依赖项;然而,使用带有参数编程验证的构造函数注入更可取。

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变的对象,并确保所需的依赖项不是。此外,构造函数注入的组件始终以完全初始化的状态返回给客户端(调用)代码。另外,大量的构造函数参数是一种糟糕的代码气味,这意味着类可能有太多的责任,应该进行重构以更好地解决适当的关注点分离问题。

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

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

Dependency Resolution Process

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

  • 创建ApplicationContext并使用描述所有Bean的配置元数据进行初始化。配置元数据可以由XML、Java代码或注释指定。

  • 对于每个Bean,它的依赖项以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用该方法而不是普通的构造函数)。这些依赖项在实际创建Bean时提供给Bean。

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

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

在创建容器时,Spring容器会验证每个Bean的配置。但是,在实际创建Bean之前,不会设置Bean属性本身。单例作用域并设置为预实例化(默认)的Bean是在创建容器时创建的。作用域在Bean Scope中定义。否则,仅在请求时才创建Bean。创建Bean可能会导致创建Bean图,因为创建并分配了Bean的依赖项及其依赖项(等等)。请注意,这些依赖项之间的解析不匹配可能会在后期出现 - ,即在第一次创建受影响的Bean时出现。

Circular dependencies

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

例如:A类通过构造函数注入需要B类的实例,B类通过构造函数注入需要A类的实例。如果将A类和B类的Bean配置为相互注入,则SpringIOC容器会在运行时检测到此循环引用,并抛出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,则在调用Bean A的setter方法之前,Spring IOC容器将完全配置Bean B。换句话说,将实例化Bean(如果它不是预实例化的单例),设置其依赖项,并调用相关的生命周期方法(如配置的init方法InitializingBean回调方法)。

Examples of Dependency Injection

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

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

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

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

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

Java
Kotlin
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

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

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

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

              
              

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

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

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

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

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

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

Java
Kotlin
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被告知调用静态工厂方法来返回对象的实例,而不是使用构造函数:

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

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

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

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

              
              

静态工厂方法的参数由<;Construction tor-arg/>;元素提供,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中是相同的)。实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用Factory-Bean属性而不是属性),因此我们在这里不讨论这些细节。

1.4.2. Dependencies and Configuration in Detail

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

Straight Values (Primitives, Strings, and so on)

<;Property/&>元素的属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。下面的示例显示了设置的各种值:

<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="misterkaoli"/>
</bean>
              
              

以下示例使用p名称空间进行更简洁的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 https://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="misterkaoli"/>

</beans>
              
              

前面的XML更加简洁。然而,除非您在创建Bean定义时使用支持自动属性完成的IDE(例如IntelliJ IdeaSpring Tools for Eclipse),否则会在运行时而不是设计时发现输入错误。强烈建议您提供此类IDE帮助。

您还可以配置java.util.Properties实例,如下所示:

<bean id="mappings" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

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

通过使用JavaBeansPropertyEditor机制,Spring容器将<;Value/>;元素中的文本转换为java.util.Properties实例。这是一个很好的捷径,也是Spring团队确实倾向于使用嵌套的<;Value/>;元素而不是属性样式的少数地方之一。

The idref element

idref元素只是将容器中另一个Bean的id(字符串值-而不是引用)传递给&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是否实际存在。在第二个变体中,不对传递给客户端Bean的Target Name属性的值执行验证。只有在实际实例化客户端Bean时才会发现拼写错误(最有可能导致致命的结果)。如果客户端Bean是原型Bean,则该拼写错误和产生的异常可能要在部署容器很久之后才能发现。

The local attribute on the idref element is no longer supported in the 4.0 beans XSD, since it does not provide value over a regular bean reference any more. Change your existing idref local references to idref bean when upgrading to the 4.0 schema.

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

References to Other Beans (Collaborators)

ref元素是<;构造函数-arg/>;<;Property/>;定义元素中的最后一个元素。在这里,您将Bean的指定属性的值设置为对容器管理的另一个Bean(协作者)的引用。引用的Bean是要设置其属性的Bean的依赖项,在设置属性之前会根据需要对其进行初始化。(如果协作者是单例Bean,则它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是否通过Bean属性指定另一个对象的ID或名称。

通过<;ref/>;标记的Bean属性指定目标Bean是最通用的形式,并且允许创建对同一容器或父容器中的任何Bean的引用,而不管它是否在相同的XML文件中。Bean属性的值可以与目标Bean的id属性相同,也可以与目标Bean的name属性中的值之一相同。下面的示例显示如何使用ref元素:

<ref bean="someBean"/>
              
              

通过属性指定目标Bean将创建对当前容器的父容器中的Bean的引用。属性的值可以与目标Bean的id属性相同,也可以与目标Bean的name属性中的某个值相同。目标Bean必须位于当前Bean的父容器中。主要当您具有容器层次,并且希望使用与父Bean具有相同名称的代理将现有Bean包装在父容器中时,应主要使用此Bean引用变量。以下清单对显示了如何使用属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required 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>
              
              
The local attribute on the ref element is no longer supported in the 4.0 beans XSD, since it does not provide value over a regular bean reference any more. Change your existing ref local references to ref bean when upgrading to the 4.0 schema.
Inner Beans

<;Property/<;<;Construction-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或名称。如果指定,容器不会使用这样的值作为标识符。容器在创建时还忽略作用域标志,因为内部Bean始终是匿名的,并且总是使用外部Bean创建的。不可能独立访问内部Bean,也不可能将它们注入到协作Bean中,而不是注入到封闭Bean中。

作为特殊情况,可以从自定义作用域 - 接收销毁回调,例如,对于包含在单例Bean中的请求作用域内部Bean。内部Bean实例的创建绑定到其包含的Bean,但销毁回调允许它参与请求作用域的生命周期。这不是一种常见的情况。内部Bean通常只是共享它们的包含Bean的作用域。

Collections

<;list/&>、<;set/&>、<;map/&>;<;props/&>元素分别设置JavaCollection类型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
              
              
Collection Merging

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

关于合并的这一节讨论父-子Bean机制。不熟悉父Bean和子Bean定义的读者可能希望在继续之前阅读相关小节。

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

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

请注意,在Bean定义的adminEmails属性的<;props/&>元素上使用了merge=true属性。当容器解析并实例化Bean时,得到的实例有一个adminEmailsProperties集合,其中包含合并子adminEmail集合和父adminEmail集合的结果。下面的清单显示了结果:

Properties集合的值集继承父<;道具/中的所有属性元素,而Support值的子级值重写父集合中的值。

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

Limitations of Collection Merging

不能合并不同的集合类型(如映射列表)。如果您确实尝试这样做,则会抛出适当的异常。必须在较低的继承子定义上指定merge属性。在父集合定义上指定merge属性是多余的,并且不会导致所需的合并。

Strongly-typed collection

由于Java对泛型类型的支持,您可以使用强类型集合。也就是说,可以声明Collection类型,使其只能包含(例如)字符串元素。如果使用Spring将强类型的集合依赖项注入到Bean中,则可以利用Spring的类型转换支持,以便在将强类型集合实例添加到集合之前,将其元素转换为适当的类型。下面的Java类和Bean定义说明了如何做到这一点:

Java
Kotlin
public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}

               
               
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>
               
               

SomethingBean的Account属性准备注入时,有关强类型Map<;字符串,Float&>元素类型的泛型信息可通过反射获得。因此,Spring的类型转换基础结构将各种Value元素识别为浮动类型,并且字符串值(9.992.753.99)被转换为实际的浮动类型。

Null and Empty String Values

Spring将属性等的空参视为空字符串。以下基于XML的配置元数据片段将电子邮件属性设置为空的字符串值(“”)。

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

上面的示例相当于下面的Java代码:

Java
Kotlin
exampleBean.setEmail("");

              
              

<;空/>;元素处理值。下面的清单显示了一个示例:

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

前面的配置相当于以下Java代码:

Java
Kotlin
exampleBean.setEmail(null);

              
              
XML Shortcut with the p-namespace

P名称空间允许您使用Bean元素的属性(而不是嵌套的<;Property/>;元素)来描述与Bean协作的属性值,或者同时使用两者。

下面的示例显示了两个解析为相同结果的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 https://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定义都引用另一个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 https://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=“spourth”ref=“Jane”/>;创建从BeanJohn到BeanJane的引用,而第二个Bean定义使用p:spourte-ref=“Jane”作为属性来执行完全相同的操作。在本例中,配偶是属性名,而-ref部分表示这不是一个直接的值,而是对另一个Bean的引用。

The p-namespace is not as flexible as the standard XML format. For example, the format for declaring property references clashes with properties that end in Ref, whereas the standard XML format does not. We recommend that you choose your approach carefully and communicate this to your team members to avoid producing XML documents that use all three approaches at the same time.
XML Shortcut with the c-namespace

与带有p名称空间的XML快捷方式类似,在Spring3.1中引入的c名称空间允许内联属性来配置构造函数参数,而不是嵌套的构造函数参数元素。

下面的示例使用c:命名空间执行与from基于构造函数的依赖项注入相同的操作:

<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 https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="[email protected]"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="[email protected]"/>

</beans>
              
              

c:命名空间使用与p:命名空间(Bean引用的尾随-ref)相同的约定来按名称设置构造函数参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心中)。

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

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree" c:_2="[email protected]"/>
              
              
Due to the XML grammar, the index notation requires the presence of the leading _, as XML attribute names cannot start with a number (even though some IDEs allow it). A corresponding index notation is also available for <constructor-arg> elements but not commonly used since the plain order of declaration is usually sufficient there.

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

Compound Property Names

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

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

Something Bean有一个fred属性,该属性有一个bob属性,该属性有一个Sammy属性,最后一个Sammy属性被设置为值123。为了使其正常工作,在构造Bean之后,某物fred属性和fredbob属性不能为。否则,抛出NullPointerException异常。

1.4.3. Using depends-on

如果一个Bean是另一个Bean的依赖项,这通常意味着一个Bean被设置为另一个Bean的属性。通常,您可以通过基于XML的配置元数据中的<;ref/>;元素实现这一点。然而,有时Bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化器时,例如用于数据库驱动程序注册。Dependons属性可以显式地强制在初始化使用该元素的Bean之前初始化一个或多个Bean。下面的示例使用Dependents-on属性来表示对单个Bean的依赖关系:

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

要表示对多个Bean的依赖关系,请提供一个Bean名称列表作为Dependers-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" />
             
             
The depends-on attribute can specify both an initialization-time dependency and, in the case of singleton beans only, a corresponding destruction-time dependency. Dependent beans that define a depends-on relationship with a given bean are destroyed first, prior to the given bean itself being destroyed. Thus, depends-on can also control shutdown order.

1.4.4. Lazy-initialized Beans

默认情况下,作为初始化过程的一部分,ApplicationContext实现急于创建和配置所有SingletonBean。通常,这种预实例化是可取的,因为配置或周围环境中的错误可以立即发现,而不是几小时甚至几天后。当这种行为不受欢迎时,您可以通过将Bean定义标记为延迟初始化来阻止单例Bean的预实例化。延迟初始化的Bean告诉IoC容器在第一次请求时创建Bean实例,而不是在启动时创建。

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

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

如果上述配置由ApplicationContext使用,则ApplicationContext启动时不会急于预实例化lazyBean,而会急于预实例化not.lazyBean。

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

您还可以通过使用<;Beans/>;元素的default-lazy-init属性在容器级别控制延迟初始化,如下面的示例所示:

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

1.4.5. Autowiring Collaborators

Spring容器可以自动绑定协作Bean之间的关系。您可以通过检查ApplicationContext的内容,让Spring自动为您的Bean解析协作者(其他Bean)。自动配线具有以下优势:

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

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

在使用基于XML的配置元数据时(请参阅依赖项注入),您可以使用<;Bean/>;元素的auTower属性为Bean定义指定自动绑定模式。自动装配功能有四种模式。您可以为每个Bean指定自动装配,因此可以选择自动装配哪些Bean。下表描述了四种自动装配模式:

Table 2. Autowiring modes
Mode Explanation

(默认)无自动布线。Bean引用必须由ref元素定义。对于较大的展开,不建议更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。

别名

按属性名称自动连接。Spring查找与需要自动绑定的属性同名的Bean。例如,如果将一个Bean定义设置为按名称自动布线,并且它包含一个master属性(即它有一个setMaster(..)方法),则Spring会查找一个名为master的Bean定义,并使用它来设置该属性。

byType

如果容器中只存在一个属性类型的Bean,则允许对该属性进行自动绑定。如果存在多个Bean,则抛出致命异常,指示您不能对该Bean使用byType自动装配。如果没有匹配的Bean,则不会发生任何事情(未设置该属性)。

构造函数

类似于byType,但适用于构造函数参数。如果容器中没有恰好一个构造函数参数类型的Bean,则会引发致命错误。

使用byType构造函数自动装配模式,可以连接数组和类型化集合。在这种情况下,容器内与预期类型匹配的所有自动布线候选对象都会被提供以满足依赖关系。如果预期的键类型为字符串,则可以自动连接强类型的Map实例。自动配置的Map实例的值由匹配预期类型的所有Bean实例组成,Map实例的键包含相应的Bean名称。

Limitations and Disadvantages of Autowiring

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

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

  • 属性构造函数参数设置中的显式依赖项始终覆盖自动装配。您不能自动绑定简单属性,如原语、字符串(以及这些简单属性的数组)。这种限制是设计出来的。

  • 自动配线不如显式配线精确。不过,正如前面的表中所指出的,在可能产生意外结果的歧义情况下,Spring会谨慎地避免猜测。不再显式地记录您的Spring托管对象之间的关系。

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

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

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

  • 放弃自动布线,转而使用显式布线。

  • 下一节中所述,通过将Bean定义的auTower-Candiate属性设置为FALSE来避免对其进行自动装配。

  • 将单个Bean定义指定为主要候选,方法是将其<;Bean/>;元素的主要属性设置为true

  • 实现基于注释的配置提供的更细粒度的控件,如基于注释的容器配置中所述。

Excluding a Bean from Autowiring

在每个Bean的基础上,您可以从自动装配中排除Bean。在Spring的XML格式中,将<;Bean/>;元素的auTower-Candiate属性设置为FALSE。容器使得特定的Bean定义对自动装配基础设施不可用(包括注释风格的配置,如@Autwire)。

The autowire-candidate attribute is designed to only affect type-based autowiring. It does not affect explicit references by name, which get resolved even if the specified bean is not marked as an autowire candidate. As a consequence, autowiring by name nevertheless injects a bean if the name matches.

您还可以根据Bean名称的模式匹配来限制自动布线候选对象。顶层<;Beans/>;元素接受其Default-autwire-Candients属性中的一个或多个模式。例如,要将自动布线候选状态限制为名称以Repository结尾的任何Bean,请提供*Repository的值。要提供多个模式,请在逗号分隔的列表中定义它们。对于Bean定义的auTower-Candidate属性,显式的trueFalse值始终优先。对于此类Bean,模式匹配规则不适用。

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

1.4.6. Method Injection

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

一个解决方案是放弃某种控制权的反转。您可以使Bean A知道容器,方法是实现ApplicationConextAware接口,并在Bean A需要时调用getBean(“B”)容器请求(通常是新的)Bean B实例。以下示例显示了此方法:

Java
Kotlin
// a class that uses a stateful Command-style class to perform some processing package fiona.apple; // Spring-API imports import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process(Map commandState) { // grab a new instance of the appropriate Command Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } protected Command createCommand() { // notice the Spring API dependency! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } 
             
             

前面的内容并不可取,因为业务代码知道并耦合到了Spring框架。方法注入是Spring IOC容器的一个有点高级的特性,它使您能够干净利落地处理这种用例。

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

Lookup Method Injection

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

  • 要使这个动态子类化起作用,Spring Bean容器子类的类不能是最终,要覆盖的方法也不能是最终

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

  • 具体的方法对于组件扫描也是必要的,这需要提取具体的类。

  • 另一个关键限制是,查找方法不能与工厂方法一起工作,尤其不能与配置类中的@Bean方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态地创建运行时生成的子类。

在前面代码片段中的CommandManager类的情况下,Spring容器动态覆盖createCommand()方法的实现。CommandManager类没有任何Spring依赖项,如修改后的示例所示:

Java
Kotlin
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);
              
              

如果该方法是抽象,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。请考虑以下示例:

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

标识为命令管理器的Bean在需要myCommandBean的新实例时调用其自己的createCommand()方法。如果确实需要的话,您必须小心地将myCommandBean部署为原型。如果它是Singleton,则每次都会返回相同的myCommandBean实例。

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

Java
Kotlin
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:

Java
Kotlin
public abstract class CommandManager {

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

    @Lookup
    protected abstract Command createCommand();
}

              
              

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

访问不同作用域的目标Bean的另一种方法是对象工厂/提供者注入点。请参阅作用域Bean作为依赖项

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

Arbitrary Method Replacement

与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一个方法实现替换托管Bean中的任意方法。在您真正需要此功能之前,您可以安全地跳过本部分的其余部分。

使用基于XML的配置元数据,您可以使用替换方法元素将已部署的Bean的现有方法实现替换为另一个方法实现。考虑下面的类,它有一个我们想要覆盖的名为culteValue的方法:

Java
Kotlin
public class MyValueCalculator {

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

    // some other methods...
}

              
              

实现org.springframework.beans.factory.support.MethodReplacer接口的类提供新的方法定义,如下面的示例所示:

Java
Kotlin
/** * 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"/>
              
              

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

java.lang.String
String
Str

              
              

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

1.5. Bean Scopes

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

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

下表描述了支持的作用域:

Table 3. Bean scopes
Scope Description

单例

(默认)将单个Bean定义作用域为每个Spring IOC容器的单个对象实例。

原型

将单个Bean定义的范围扩大到任意数量的对象实例。

请求

将单个Bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有其自己的Bean实例,该实例是在单个Bean定义之后创建的。仅在可识别Web的SpringApplicationContext的上下文中有效。

会话

将单个Bean定义限定为HTTP会话的生命周期。仅在可识别Web的SpringApplicationContext的上下文中有效。

应用程序

将单个Bean定义的范围限定为ServletContext的生命周期。仅在可识别Web的SpringApplicationContext的上下文中有效。

WebSocket

将单个Bean定义限定为WebSocket的生命周期。仅在可识别Web的SpringApplicationContext的上下文中有效。

As of Spring 3.0, a thread scope is available but is not registered by default. For more information, see the documentation for SimpleThreadScope. For instructions on how to register this or any other custom scope, see Using a Custom Scope.

1.5.1. The Singleton Scope

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

换句话说,当您定义一个Bean定义并且它的作用域为单例时,Spring IOC容器恰好创建由该Bean定义定义的对象的一个实例。这个实例存储在这样的单例Bean的缓存中,该命名Bean的所有后续请求和引用都返回缓存的对象。下图显示了Singleton作用域的工作原理:

singleton

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

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

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

1.5.2. The Prototype Scope

Bean部署的非Singleton Prototype作用域在每次请求特定Bean时都会创建一个新的Bean实例。也就是说,该Bean被注入到另一个Bean中,或者您通过容器上的getBean()方法调用来请求它。通常,您应该对所有有状态Bean使用Prototype作用域,对无状态Bean使用Singleton作用域。

下图说明了Spring Prototype作用域:

prototype

(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不包含任何会话状态。对我们来说,重用单例关系图的核心更加容易。)

下面的示例将Bean定义为XML中的原型:

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

与其他作用域不同,Spring不管理原型Bean的整个生命周期。容器实例化、配置和以其他方式组装原型对象,并将其交给客户端,而不进一步记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法而不考虑范围,但在原型的情况下,不调用配置的销毁生命周期回调。客户端代码必须清理原型作用域的对象,并释放原型Bean拥有的昂贵资源。要让Spring容器释放由Prototype作用域的Bean持有的资源,可以尝试使用定制的Bean后处理器,它包含对需要清理的Bean的引用。

在某些方面,对于原型作用域的Bean,Spring容器的角色是Javanew操作符的替代。超过这一点的所有生命周期管理都必须由客户端处理。(有关Spring容器中的Bean的生命周期的详细信息,请参阅生命周期回调。)

1.5.3. Singleton Beans with Prototype-bean Dependencies

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

然而,假设您希望单例作用域的Bean在运行时重复获取Prototype作用域的Bean的新实例。您不能将原型作用域的Bean注入到Singleton Bean中,因为这种注入只发生一次,当Spring容器实例化Singleton Bean并解析和注入它的依赖项时。如果您在运行时多次需要原型Bean的新实例,请参阅方法注入

1.5.4. Request, Session, Application, and WebSocket Scopes

请求会话应用程序WebSocket作用域只有在使用Web感知的SpringApplicationContext实现(如XmlWebApplicationContext)时才可用。如果将这些作用域与常规的Spring IOC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException,它会报告未知的Bean作用域。

Initial Web Configuration

为了支持请求会话应用程序WebSocket级别的Bean的作用域(Web作用域的Bean),在定义Bean之前需要进行一些较小的初始配置。(标准作用域SingletonPrototype不需要此初始设置。)

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

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

如果使用Servlet Web容器,并且请求在Spring的<代码>Dispatcher Servlet 之外处理(例如,在使用JSF或Struts时),则需要注册org.springframework.web.context.request.RequestContextListener<代码>ServletRequestListener 。这可以通过使用WebApplicationInitializer接口以编程方式完成。或者,将以下声明添加到Web应用程序的web.xml文件中:

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

或者,如果您的侦听器设置有问题,可以考虑使用Spring的RequestConextFilter。过滤器映射取决于周围的Web应用程序配置,因此您必须对其进行适当的更改。下面的清单显示了Web应用程序的筛选器部分:

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

DispatcherServletRequestContextListenerRequestContextFilter都执行完全相同的操作,即将HTTP请求对象绑定到为该请求提供服务的线程。这使得请求和会话作用域的Bean在调用链的更下游可用。

Request scope

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

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

Spring容器通过为每个HTTP请求使用loginActionBean定义来创建LoginActionBean的新实例。也就是说,loginActionBean的作用域是在HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从相同的loginActionBean定义创建的其他实例看不到这些状态更改。它们是针对个别请求的。当请求完成处理时,范围为该请求的Bean将被丢弃。

当使用注释驱动的组件或Java配置时,@RequestScope注释可用于将组件分配给请求作用域。以下示例显示了如何执行此操作:

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

              
              
Session Scope

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

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

Spring容器通过在单个HTTP会话的生命周期中使用UserPreferencesBean定义来创建UserPreferencesBean的新实例。换句话说,userPreferencesBean的作用域实际上是在HTTP会话级别。与使用请求作用域的Bean一样,您可以随心所欲地更改创建的实例的内部状态,因为您知道,也在使用从相同的用户首选项Bean定义创建的实例的其他HTTP会话实例看不到这些状态更改,因为它们是特定于单个HTTP会话的。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的Bean也将被丢弃。

当使用注释驱动的组件或Java配置时,您可以使用@SessionScope注释将组件分配给会话作用域。

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

              
              
Application Scope

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

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

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

当使用注释驱动的组件或Java配置时,可以使用@ApplicationScope注释将组件分配给应用程序作用域。以下示例显示了如何执行此操作:

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

              
              
WebSocket Scope

WebSocket作用域与WebSocket会话的生命周期相关,并适用于践踏WebSocket应用程序,有关详细信息,请参阅WebSocket Scope

Scoped Beans as Dependencies

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

您还可以在作用域为Singleton的Bean之间使用<;aop:Scope-Proxy/&,然后引用通过一个可序列化的中间代理,从而能够在反序列化时重新获得目标单例Bean。

当对Prototype作用域的Bean声明<;aop:Scope-Proxy/&时,对共享代理的每个方法调用都会导致创建一个新的目标实例,然后将调用转发到该目标实例。

此外,作用域代理并不是以生命周期安全的方式从较短作用域访问Bean的唯一方式。您还可以将注入点(即构造函数或设置器参数或自动连接的字段)声明为ObjectFactory<;MyTargetBean>;,,从而允许<代码>getObject() 调用在每次需要 - 时按需检索当前实例,而无需保留该实例或单独存储该实例。

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

它的JSR-330变体称为提供程序,并与提供程序声明和对应的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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

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

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

要创建这样的代理,您需要将子<;aop:Scope-Proxy/>;元素插入到限定了作用域的Bean定义中(请参阅选择要创建的代理的类型基于XML架构的配置)。为什么请求会话和自定义作用域级别的Bean的定义需要<;aop:Scope-Proxy/&>元素?考虑以下单例Bean定义,并将其与您需要为前述作用域定义的Bean定义进行对比(请注意,以下用户首选项Bean定义是不完整的):

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

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

在前面的示例中,单例Bean(userManager)被注入了对HTTP会话作用域Bean(userPreferences)的引用。这里的重点是userManagerBean是一个单例:每个容器只实例化一次,它的依赖项(在本例中只有一个,即userPreferencesBean)也只注入一次。这意味着userManagerBean只对完全相同的userPreferences对象(即最初注入它的那个对象)进行操作。

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

因此,在将请求会话范围的Bean注入协作对象时,您需要进行以下(正确且完整的)配置,如下例所示:

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

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
              
              
Choosing the Type of Proxy to Create

默认情况下,当Spring容器为使用<;aop:Scope-Proxy/&/code>元素标记的Bean创建代理时,将创建一个基于CGLIB的类代理。

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

或者,您可以将Spring容器配置为为此类作用域Bean创建标准的基于JDK接口的代理,方法是为<;aop:Scope-Proxy/&元素的Proxy-Target-Class属性的值指定False。使用基于JDK接口的代理意味着您不需要应用程序类路径中的其他库来影响此类代理。然而,这也意味着作用域Bean的类必须实现至少一个接口,并且注入作用域Bean的所有协作者必须通过其接口之一引用该Bean。以下示例显示了基于接口的代理:

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

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

有关选择基于类或基于接口的代理的更多详细信息,请参阅代理机制

1.5.5. Custom Scopes

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

Creating a Custom Scope

要将您的定制作用域集成到Spring容器中,您需要实现org.springframework.beans.factory.config.Scope接口,本节将对其进行描述。有关如何实现您自己的作用域的想法,请参阅Spring框架本身提供的Scope实现和Scopejavadoc,后者更详细地解释了需要实现的方法。

Scope接口有四个方法,用于从作用域中获取对象、从作用域中移除对象以及销毁对象。

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

Java
Kotlin
Object get(String name, ObjectFactory<?> objectFactory) 
              
              

例如,会话作用域实现从底层会话中删除了会话作用域的Bean。应该返回对象,但如果找不到指定名称的对象,也可以返回NULL。下面的方法将对象从基础作用域中移除:

Java
Kotlin
Object remove(String name) 
              
              

下面的方法注册一个回调,作用域在销毁它或销毁作用域中的指定对象时应调用该回调:

Java
Kotlin
void registerDestructionCallback(String name, Runnable destructionCallback) 
              
              

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

以下方法获取基础作用域的会话标识符:

Java
Kotlin
String getConversationId() 
              
              

此标识符因每个作用域而异。对于会话作用域实现,该标识符可以是会话标识符。

Using a Custom Scope

在编写和测试一个或多个定制的Scope实现之后,您需要让Spring容器知道您的新作用域。以下方法是向Spring容器注册新的作用域的主要方法:

Java
Kotlin
void registerScope(String scopeName, Scope scope);

              
              

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

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

假设您编写了定制的Scope实现,然后注册它,如下例所示。

The next example uses SimpleThreadScope, which is included with Spring but is not registered by default. The instructions would be the same for your own custom Scope implementations.
Java
Kotlin
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

              
              

然后,您可以创建符合自定义作用域的作用域规则的Bean定义,如下所示:

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

使用自定义的范围实现,您不受范围的编程注册的限制。您还可以使用CustomScopeConfigurer类以声明方式进行范围注册,如下面的示例所示:

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

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

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

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

</beans>
              
              
When you place <aop:scoped-proxy/> within a <bean> declaration for a FactoryBean implementation, it is the factory bean itself that is scoped, not the object returned from getObject().

1.6. Customizing the Nature of a Bean

Spring框架提供了许多接口,您可以使用它们来自定义Bean的性质。本节按如下方式对它们进行分组:

1.6.1. Lifecycle Callbacks

要与容器对Bean生命周期的管理进行交互,您可以实现SpringInitializingBeanDisposableBean接口。容器为前者调用After PropertiesSet(),为后者调用Destroy(),以便让Bean在初始化和销毁Bean时执行某些操作。

JSR-250@PostConstruct@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的Bean不会耦合到特定于Spring的接口。详情请参见Using@PostConstruct@PreDestroy

如果您不想使用JSR-250注释,但仍然想要删除耦合,请考虑init-方法销毁方法Bean定义元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要定制特性或其他生命周期行为,Spring默认不提供,您可以自己实现一个BeanPostProcessor。有关详细信息,请参阅容器扩展点

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

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

Initialization Callbacks

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

void afterPropertiesSet() throws Exception;

              
              

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

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

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

              
              

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

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

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

              
              

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

Destruction Callbacks

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

void destroy() throws Exception;

              
              

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PreDestroy注释或指定Bean定义支持的泛型方法。对于基于XML的配置元数据,您可以在<;Bean/>;上使用销毁方法属性。通过Java配置,您可以使用@BeandelestyMethod属性。请参阅接收生命周期回调。请考虑以下定义:

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

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

              
              

上述定义与以下定义几乎具有完全相同的效果:

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

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

              
              

然而,前面两个定义中的第一个并没有将代码耦合到Spring。

You can assign the destroy-method attribute of a <bean> element a special (inferred) value, which instructs Spring to automatically detect a public close or shutdown method on the specific bean class. (Any class that implements java.lang.AutoCloseable or java.io.Closeable would therefore match.) You can also set this special (inferred) value on the default-destroy-method attribute of a <beans> element to apply this behavior to an entire set of beans (see Default Initialization and Destroy Methods). Note that this is the default behavior with Java configuration.
Default Initialization and Destroy Methods

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

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

假设您的初始化回调方法命名为init(),销毁回调方法命名为Destroy()。然后,您的类与下例中的类类似:

Java
Kotlin
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

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

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

              
              

然后,您可以在如下所示的Bean中使用该类:

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

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

</beans>
              
              

在顶级<;Beans/&>元素属性上存在Default-init-method属性会导致Spring IOC容器将Bean类上名为init的方法识别为初始化方法回调。在创建和组装Bean时,如果Bean类具有这样的方法,则会在适当的时间调用它。

您可以通过在顶级<;Beans/>;元素上使用Default-Destroy-Method属性来配置类似的销毁方法回调(即在XML中)。

如果现有的Bean类已经具有命名与约定不同的回调方法,您可以使用<;Bean/&>本身的<;Bean/&>属性来指定(在XML中)方法名称,从而覆盖缺省值。

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

Combining Lifecycle Mechanisms

从Spring2.5开始,您有三种选择来控制Bean生命周期行为:

If multiple lifecycle mechanisms are configured for a bean and each mechanism is configured with a different method name, then each configured method is run in the order listed after this note. However, if the same method name is configured — for example, init() for an initialization method — for more than one of these lifecycle mechanisms, that method is run once, as explained in the preceding section.

为同一个Bean配置的多个生命周期机制具有不同的初始化方法,调用方式如下:

  1. 使用@PostConstruct注释的方法

  2. InitializingBean回调接口定义的postPropertiesSet()

  3. 自定义配置的init()方法

销毁方法的调用顺序相同:

  1. 使用@PreDestroy注释的方法

  2. 销毁()DisposableBean回调接口定义

  3. 自定义配置的Destroy()方法

Startup and Shutdown Callbacks

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

              
              

任何由Spring管理的对象都可以实现生命周期接口。然后,当ApplicationContext本身接收到启动和停止信号时(例如,对于运行时的停止/重新启动场景),它会将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托LifecycleProcessor来实现这一点,如下面的清单所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

              
              

注意,生命周期处理器本身就是生命周期接口的扩展。它还添加了另外两种方法,用于响应正在刷新和关闭的上下文。

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

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

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

public interface Phased {

    int getPhase();
}

              
              

下面的清单显示了SmartLifeccle接口的定义:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

              
              

启动时,具有最低阶段的对象首先启动。当停止时,遵循相反的顺序。因此,实现SmartLifeccle并且其getPhase()方法返回Integer.MIN_Value的对象将是最先启动、最后停止的对象之一。在频谱的另一端,相位值Integer.MAX_VALUE将指示对象应该最后启动并首先停止(可能是因为它依赖于其他正在运行的进程)。在考虑阶段值时,还必须知道没有实现SmartLifeccle的任何“正常”生命周期对象的默认阶段是0。因此,任何负的相位值都表示对象应在这些标准分量之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。

SmartLifeccle定义的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接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就好像已经显式调用了top(),但它发生在上下文关闭时。另一方面,“刷新”回调启用了SmartLifeccleBean的另一个功能。当刷新上下文时(在所有对象被实例化和初始化之后),该回调被调用。此时,默认的生命周期处理器检查每个SmartLifeccle对象的isAutoStartup()方法返回的布尔值。如果为True,则在该点启动对象,而不是等待显式调用上下文或其自己的Start()方法(与上下文刷新不同,标准上下文实现不会自动启动上下文)。阶段值和任何“依赖关系”决定了启动顺序,如前所述。

Shutting Down the Spring IoC Container Gracefully in Non-Web Applications

本节仅适用于非Web应用程序。Spring的基于Web的ApplicationContext实现已经有了代码,可以在相关Web应用程序关闭时优雅地关闭Spring IOC容器。

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

若要注册关闭挂钩,请调用在ConfigurableApplicationContext接口上声明的registerShutdown Hook()方法,如下面的示例所示:

Java
Kotlin
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); // add a shutdown hook for the above context... ctx.registerShutdownHook(); // app runs here... // main method exits, hook is called prior to the app shutting down... } } 
              
              

1.6.2. ApplicationContextAware and BeanNameAware

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

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

             
             

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

自动绑定是获取对ApplicationContext的引用的另一种方法。传统构造函数byType自动装配模式(如AuTower Collaborator中所述)可以分别为构造函数实参或setter方法参数提供ApplicationContext类型的依赖项。为了获得更大的灵活性,包括自动绑定字段和多参数方法的能力,可以使用基于注释的自动绑定特性。如果这样做,则ApplicationContext将自动连接到需要ApplicationContext类型的字段、构造函数实参或方法参数中,前提是有问题的字段、构造函数或方法带有@AuTower注释。有关更多信息,请参阅Using@Autwire

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

             
             

在填充普通Bean属性之后,但在初始化回调(如InitializingBean.afterPropertiesSet()或定制初始化方法)之前调用回调。

1.6.3. Other Aware Interfaces

除了ApplicationContextAwareBeanNameAware(前面已经讨论过)之外,Spring还提供了大量的感知回调接口,让Bean向容器指示它们需要某种基础设施依赖。作为一般规则,名称指示依赖项类型。下表总结了最重要的感知接口:

Table 4. Aware interfaces
Name Injected Dependency Explained in…​

ApplicationConextAware

声明ApplicationContext

ApplicationConextAwareBeanNameAware

ApplicationEventPublisherAware

封闭的ApplicationContext的事件发布者。

ApplicationContext的其他功能

BeanClassLoaderAware

用于加载Bean类的类加载器。

实例化Bean

BeanFactoryAware

声明BeanFactory

BeanFactory接口

BeanNameAware

声明Bean的名称。

ApplicationConextAwareBeanNameAware

LoadTimeWeverAware

定义的编织器,用于在加载时处理类定义。

在Spring框架中使用AspectJ进行加载时编织

MessageSourceAware

已配置解析消息的策略(支持参数化和国际化)。

ApplicationContext的其他功能

NotificationPublisherAware

Spring JMX通知发布器。

通知

ResourceLoaderAware

已配置加载程序以进行资源的低级别访问。

资源

ServletConfigAware

运行容器的当前ServletConfig。仅在可识别Web的SpringApplicationContext中有效。

Spring MVC

ServletContextAware

容器运行的当前ServletContext。仅在可识别Web的SpringApplicationContext中有效。

Spring MVC

再次注意,使用这些接口将您的代码绑定到Spring API,并且不遵循控制反转风格。因此,我们建议将它们用于需要以编程方式访问容器的基础设施Bean。

1.7. Bean Definition Inheritance

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

如果以编程方式使用ApplicationContext接口,则子Bean定义由ChildBeanDefinition类表示。大多数用户不会在此级别上使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中以声明方式配置Bean定义。当您使用基于XML的配置元数据时,您可以通过使用属性来指示子Bean定义,并将父Bean指定为该属性的值。以下示例显示了如何执行此操作:

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

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

如果未指定,子Bean定义将使用父定义中的Bean类,但也可以覆盖它。在后一种情况下,子Bean类必须与父Bean类兼容(即,它必须接受父Bean类的属性值)。

子Bean定义继承父Bean的作用域、构造函数参数值、属性值和方法覆盖,并可以选择添加新值。您指定的任何作用域、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父设置。

其余的设置总是从子定义中获取的:依赖、自动配线模式、依赖检查、单例和惰性初始化。

前面的示例通过使用抽象属性显式地将父Bean定义标记为抽象。如果父定义没有指定类,则需要将父Bean定义显式标记为抽象,如下例所示:

<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不能单独实例化,因为它是不完整的,而且它还被显式标记为抽象。当定义是抽象时,它只能用作纯模板Bean定义,作为子定义的父定义。尝试单独使用此类抽象父Bean,方法是将其引用为另一个Bean的ref属性,或者使用父级Bean ID执行显式的getBean()调用,将返回错误。同样,容器的内部preInstantiateSingletons()方法会忽略定义为抽象的Bean定义。

ApplicationContext pre-instantiates all singletons by default. Therefore, it is important (at least for singleton beans) that if you have a (parent) bean definition which you intend to use only as a template, and this definition specifies a class, you must make sure to set the abstract attribute to true, otherwise the application context will actually (attempt to) pre-instantiate the abstract bean.

1.8. Container Extension Points

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

1.8.1. Customizing Beans by Using a BeanPostProcessor

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

您可以配置多个BeanPostProcessor实例,并且可以通过设置Order属性来控制这些BeanPostProcessor实例的运行顺序。只有当BeanPostProcessor实现有序接口时,才能设置此属性。如果您编写自己的BeanPostProcessor,也应该考虑实现Order接口。有关详细信息,请参阅BeanPostProcessor有序接口的javadoc。另请参阅编程注册BeanPostProcessor实例的说明。

BeanPostProcessor实例对Bean(或对象)实例进行操作。也就是说,Spring IOC容器实例化一个Bean实例,然后BeanPostProcessor实例执行它们的工作。

BeanPostProcessor实例的作用域为每个容器。这仅在您使用容器层次结构时才相关。如果在一个容器中定义BeanPostProcessor,则它只对该容器中的Bean进行后处理。换句话说,一个容器中定义的Bean不会由另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器属于同一层次结构。

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

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

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

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

Programmatically registering BeanPostProcessor instances
While the recommended approach for BeanPostProcessor registration is through ApplicationContext auto-detection (as described earlier), you can register them programmatically against a ConfigurableBeanFactory by using the addBeanPostProcessor method. This can be useful when you need to evaluate conditional logic before registration or even for copying bean post processors across contexts in a hierarchy. Note, however, that BeanPostProcessor instances added programmatically do not respect the Ordered interface. Here, it is the order of registration that dictates the order of execution. Note also that BeanPostProcessor instances registered programmatically are always processed before those registered through auto-detection, regardless of any explicit ordering.
BeanPostProcessor instances and AOP auto-proxying

实现BeanPostProcessor接口的类是特殊的,容器以不同的方式对待它们。它们直接引用的所有BeanPostProcessor实例和Bean在启动时被实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他Bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以无论是BeanPostProcessor实例还是它们直接引用的Bean都不符合自动代理的条件,因此不包含方面。

对于任何这样的Bean,您应该看到一条信息性日志消息:Bean某个Bean不适合由所有BeanPostProcessor接口处理(例如:不适合自动代理)

如果通过使用自动装配或@Resource将Bean连接到BeanPostProcessor中(这可能会退回到自动装配),则在搜索类型匹配的依赖项候选项时,Spring可能会访问意外的Bean,从而使它们不符合自动代理或其他类型的Bean后处理的条件。例如,如果您有一个用@Resource注释的依赖项,其中字段或setter名称不直接对应于Bean声明的名称,并且没有使用名称属性,则Spring访问其他Bean以按类型匹配它们。

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

Example: Hello World, BeanPostProcessor-style

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

下面的清单显示了定制的BeanPostProcessor实现类定义:

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

下面的Bean元素使用InstantiationTracingBeanPostProcessor

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

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

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

</beans>
              
              

请注意InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,而且因为它是一个Bean,所以可以像注入任何其他Bean一样注入依赖项。(前面的配置还定义了一个由Groovy脚本支持的Bean。有关Spring动态语言支持的详细信息,请参阅动态语言支持一章。)

下面的Java应用程序运行前面的代码和配置:

Java
Kotlin
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 = ctx.getBean("messenger", Messenger.class); System.out.println(messenger); } } 
              
              

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

Bean 'messenger' created : [email protected]
[email protected]
Example: The AutowiredAnnotationBeanPostProcessor

将回调接口或注释与定制的BeanPostProcessor实现结合使用是扩展Spring IOC容器的一种常见方法。一个例子是SpringBeanPostProcessor<AutowiredAnnotationBeanPostProcessor — a>BeanPostProcessor实现,它与Spring发行版一起提供,并自动绑定带注释的字段、setter方法和任意配置方法。

1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor

我们关注的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor.此接口的语义类似于BeanPostProcessor的语义,但有一个主要区别:BeanFactoryPostProcessor操作Bean配置元数据。也就是说,Spring IOC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例以外的任何Bean之前更改配置元数据。

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

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

此外,BeanFactoryPostProcessor实例的作用域为每个容器。这仅在您使用容器层次结构时才相关。如果在一个容器中定义BeanFactoryPostProcessor,则它仅应用于该容器中的Bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使这两个容器属于同一层次结构。

当在ApplicationContext中声明Bean工厂后处理器时,它将自动运行,以便将更改应用于定义容器的配置元数据。Spring包括许多预定义的Bean工厂后处理器,例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer.例如,您还可以使用定制的BeanFactoryPostProcessor - 来注册定制的属性编辑器。

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

As with BeanPostProcessors , you typically do not want to configure BeanFactoryPostProcessors for lazy initialization. If no other bean references a Bean(Factory)PostProcessor, that post-processor will not get instantiated at all. Thus, marking it for lazy initialization will be ignored, and the Bean(Factory)PostProcessor will be instantiated eagerly even if you set the default-lazy-init attribute to true on the declaration of your <beans /> element.
Example: The Class Name Substitution PropertySourcesPlaceholderConfigurer

通过使用标准的Java<PropertySourcesPlaceholderConfigurer>Properties格式,可以使用代码将Bean定义中的属性值外部化到单独的文件中。这样做使部署应用程序的人员能够定制特定于环境的属性,如数据库URL和密码,而无需修改容器的一个或多个主XML定义文件的复杂性或风险。

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

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

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

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

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

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

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

使用Spring2.5中引入的上下文命名空间,您可以使用专用的配置元素来配置属性占位符。您可以在Location属性中以逗号分隔列表的形式提供一个或多个位置,如下例所示:

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

PropertySourcesPlaceholderConfigurer不仅在您指定的属性文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它将检查SpringEnvironment属性和常规Java系统属性。

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

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

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

如果无法在运行时将类解析为有效的类,则在将要创建该Bean时,该Bean的解析将失败,这是在非惰性初始化Bean的ApplicationContextpreInstantiateSingletons()阶段。

Example: The PropertyOverrideConfigurer

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

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

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

beanName.property=value

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

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

此示例文件可以与容器定义一起使用,该容器定义包含名为DataSource的Bean,该Bean具有Driverurl属性。

还支持复合属性名称,只要路径的每个组件(被覆盖的最后一个属性除外)都已经是非空的(假设是由构造函数初始化的)。在下面的示例中,tomBean的fred属性的bob属性的Sammy属性设置为标量值123

tom.fred.bob.sammy=123
Specified override values are always literal values. They are not translated into bean references. This convention also applies when the original value in the XML bean definition specifies a bean reference.

通过在Spring2.5中引入的上下文命名空间,可以使用专用的配置元素配置属性覆盖,如下面的示例所示:

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

1.8.3. Customizing Instantiation Logic with a FactoryBean

您可以为本身是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

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

FactoryBean<;T>;接口提供三种方法:

  • T getObject():返回该工厂创建的对象的实例。该实例可能是共享的,这取决于该工厂返回的是单例还是原型。

  • boolean isSingleton():如果这个FactoryBean返回单例,则返回True,否则返回False。此方法的默认实现返回true

  • Class<;?>;getObtType():返回getObject()方法返回的对象类型,如果事先不知道类型,则返回NULL

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

如果需要向容器请求实际的FactoryBean实例本身,而不是它生成的Bean,则在调用ApplicationContextgetBean()方法时,在Bean的id前面加上与号(&;)。因此,对于idmyBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的乘积,而调用getBean(“&;myBean”)将返回FactoryBean实例本身。

1.9. Annotation-based Container Configuration

Are annotations better than XML for configuring Spring?

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

无论选择哪种风格,Spring都可以适应这两种风格,甚至可以将它们混合在一起。值得一提的是,通过其JavaConfig选项,Spring允许以非侵入性的方式使用批注,而无需接触目标组件源代码,并且在工具方面,所有配置样式都受Spring Tools for Eclipse支持。

XML设置的另一种选择是基于注释的配置,它依赖于用于连接组件的字节码元数据,而不是尖括号声明。开发人员不是使用XML来描述Bean连接,而是通过使用相关类、方法或字段声明上的注释将配置移动到组件类本身。正如示例中提到的:将<AutowiredAnnotationBeanPostProcessor,>BeanPostProcessor与注释结合使用是扩展Spring IOC容器的一种常见方法。例如,Spring2.5引入了一种基于注释的方法来驱动Spring的依赖注入。从本质上讲,@Autwire注释提供了与AuTower Collaborator中描述的相同功能,但具有更细粒度的控制和更广泛的适用性。Spring2.5还增加了对JSR-250注释的支持,比如@PostConstruct@PreDestroy。Spring3.0添加了对jakarta.inject包中包含的JSR-330(Java的依赖项注入)批注的支持,如@Inject@命名的。有关这些注释的详细信息,请参阅相关部分

注释注入是在XML注入之前执行的。因此,XML配置覆盖了通过这两种方法连接的属性的注释。

像往常一样,您可以将后处理器注册为单独的Bean定义,但也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(请注意,包含了上下文名称空间):

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

    <context:annotation-config/>

</beans>
            
            

<;context:annotation-config/>;元素隐式注册以下后处理器:

<;context:annotation-config/>;只在定义它的同一应用程序上下文中查找Bean上的注释。这意味着,如果您将<;context:annotation-config/>;放在DispatcherServletWebApplicationContext中,它只检查控制器中的@AutwireBean,而不检查您的服务。有关详细信息,请参阅DispatcherServlet

1.9.1. Using @Autowired

在本节中包含的示例中,可以使用JSR330的@Inject批注来代替Spring的@AuTower批注。有关更多详细信息,请参阅此处

您可以将@AuTower批注应用于构造函数,如下面的示例所示:

Java
Kotlin
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

             
             

从Spring Framework4.3开始,如果目标Bean一开始只定义了一个构造函数,则不再需要在这样的构造函数上使用@AuTower注释。但是,如果有多个构造函数可用,并且没有主/默认构造函数,则必须使用@Autwire注释至少一个构造函数,以便指示容器使用哪个构造函数。有关详细信息,请参阅构造函数解析的讨论。

您还可以对传统的setter方法应用@AuTower批注,如下面的示例所示:

Java
Kotlin
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

             
             

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

Java
Kotlin
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

             
             

您也可以将@AuTower应用于字段,甚至可以将其与构造函数混合使用,如下例所示:

Java
Kotlin
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...
}

             
             

确保您的目标组件(例如,MovieCatalogCustomerPferenceDao)一致地由您用于@AuTower注解的注入点的类型声明。否则,注入可能会在运行时由于“未找到类型匹配”错误而失败。

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

您还可以指示Spring从ApplicationContext中提供特定类型的所有Bean,方法是向需要该类型数组的字段或方法添加@AuTower注释,如下面的示例所示:

Java
Kotlin
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

             
             

类型化集合也是如此,如下面的示例所示:

Java
Kotlin
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}

             
             

如果希望按特定顺序对数组或列表中的项进行排序,则目标Bean可以实现org.springfrawork.core.Orded接口,或者使用@Order或标准的@Priority注释。否则,它们的顺序遵循容器中相应目标Bean定义的注册顺序。

您可以在目标类级别和@Bean方法上声明@Order注释,可能用于单独的Bean定义(在多个定义使用同一Bean类的情况下)。@Order值可能会影响注入点处的优先级,但请注意,它们不会影响单例启动顺序,这是一个由依赖关系和@DependsOn声明确定的正交性问题。

注意,在@Bean级别上,标准的jakarta.注解注释不可用,因为它不能在方法上声明。它的语义可以通过@Order值与每种类型的单个Bean上的@Primary相结合来建模。

即使是类型化的Map实例也可以自动连接,只要预期的键类型是字符串。映射值包含预期类型的所有Bean,键包含相应的Bean名称,如下例所示:

Java
Kotlin
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

             
             

默认情况下,当没有匹配的候选Bean可用于给定的注入点时,自动装配将失败。对于声明的数组、集合或映射,需要至少一个匹配的元素。

默认行为是将带注释的方法和字段视为指示必需的依赖项。您可以更改此行为,如下面的示例所示,通过将无法满足的注入点标记为非必需(即,将@Autwire中的Required属性设置为False),使框架能够跳过该注入点:

Java
Kotlin
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

             
             

如果非必需方法的依赖项(或其依赖项之一,如果有多个参数)不可用,则根本不会调用该方法。在这种情况下,根本不会填充非必填字段,而是保留其缺省值。

换句话说,将Required属性设置为FALSE表示对应的属性对于自动连接而言是可选的,如果无法自动连接该属性,则将忽略该属性。这允许为属性分配缺省值,这些缺省值可以通过依赖项注入选择性地覆盖。

注入的构造函数和工厂方法参数是一个特例,因为由于Spring的构造函数解析算法可能会处理多个构造函数,@AuTower中的Required属性的含义略有不同。构造函数和工厂方法参数在默认情况下是有效的,但在单构造函数场景中有一些特殊规则,例如,如果没有匹配的Bean,则多元素注入点(数组、集合、映射)解析为空实例。这允许使用通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明--例如,声明为单个公共构造函数,而不使用@Autwire注释。

任何给定Bean类的只有一个构造函数可以声明@AuTower,并将Required属性设置为true,指示构造函数在用作Spring Bean时自动连接。因此,如果将Required属性保留为其缺省值true,则只有一个构造函数可以用@Autwire进行注释。如果多个构造函数声明了注释,则它们都必须声明Required=FALSE才能被视为自动装配的候选函数(类似于XML中的autwire=Construction tor)。将选择能够由Spring容器中的匹配Bean满足的依赖项数量最多的构造函数。如果所有候选函数都不能满足要求,则将使用主/默认构造函数(如果存在)。类似地,如果一个类声明了多个构造函数,但没有一个用@AuTower注释,则将使用主/默认构造函数(如果存在)。如果一个类一开始只声明了一个构造函数,那么即使没有注释,它也会一直被使用。请注意,带注释的构造函数不必是公共的。

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

public class SimpleMovieLister {

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

             
             

从Spring Framework5.0开始,您还可以使用< >@Nullable注释(任何类型的包JSR- 中的任何类型的注释,例如来自JSR-305的javax.Annotation.Nullable),或者仅仅利用Kotlin内置的空安全支持:

Java
Kotlin
public class SimpleMovieLister {

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

             
             

对于众所周知的可解析依赖的接口,您也可以使用@AuTowerBeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。这些接口及其扩展接口(如ConfigurableApplicationContextResourcePatternResolver)是自动解析的,不需要特殊设置。下面的示例自动生成一个ApplicationContext对象:

Java
Kotlin
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

             
             

@AuTower@Inject@Value@Resource注释由SpringBeanPostProcessor实现处理。这意味着您不能在您自己的BeanPostProcessorBeanFactoryPostProcessor类型(如果有)中应用这些批注。这些类型必须通过使用XML或Spring@Bean方法显式‘连接起来’。

1.9.2. Fine-tuning Annotation-based Autowiring with @Primary

由于按类型自动组合可能会导致多个候选人,因此通常有必要对选择过程进行更多控制。实现这一点的一种方法是使用Spring的@Primary注释。@Primary指示当多个Bean是自动连接到单值依赖项的候选时,应该优先考虑特定的Bean。如果候选对象中只存在一个主Bean,则它将成为自动配置值。

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

Java
Kotlin
@Configuration
public class MovieConfiguration {

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

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

    // ...
}

             
             

在上述配置中,以下MovieRecommenderFirstMovieCatalog一起自动配置:

Java
Kotlin
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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

</beans>
             
             

1.9.3. Fine-tuning Annotation-based Autowiring with Qualifiers

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

Java
Kotlin
public class MovieRecommender {

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

    // ...
}

             
             
1 The bean with the main qualifier value is wired with the constructor argument that is qualified with the same value.
2 The bean with the action qualifier value is wired with the constructor argument that is qualified with the same value.

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

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

    // ...
}

             
             
1 This line adds the @Offline annotation.

下面的示例显示了相应的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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

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

</beans>
             
             

对于备用匹配,Bean名称被视为默认限定符值。因此,您可以使用mainid而不是嵌套的限定符元素来定义Bean,从而产生相同的匹配结果。然而,尽管您可以使用此约定通过名称来引用特定的Bean,但@Autwire本质上是关于带有可选语义限定符的类型驱动注入。这意味着,即使使用Bean名称回退,限定符的值在类型匹配集中也总是具有缩小的语义。它们不在语义上表示对唯一Beanid的引用。好的限定符的值是mainEMEAPersistent,表示独立于Beanid的特定组件的特征,这些特征可以在匿名Bean定义的情况下自动生成,如上例中的定义。

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

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

也就是说,如果您打算按名称表示注释驱动的注入,则不要主要使用@Autwire,即使它能够按类型匹配的候选对象中的Bean名称进行选择。相反,可以使用JSR-250@Resource注释,该注释在语义上定义为通过唯一名称标识特定的目标组件,声明的类型与匹配过程无关。@AuTower具有非常不同的语义:在按类型选择候选Bean之后,指定的字符串限定符值只在那些类型选择的候选中考虑(例如,将Account限定符与标记了相同限定符标签的Bean进行匹配)。

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

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

尝试将@Bean方法的结果注入到相同的配置类中也是一种有效的自引用场景。要么在实际需要的方法签名中懒洋洋地解析这种引用(与配置类中的自动连接字段相反),要么将受影响的@Bean方法声明为静态,将它们从包含的配置类实例及其生命周期中分离出来。否则,此类Bean仅在备用阶段被考虑,其他配置类上的匹配Bean被选为主要候选(如果可用)。

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

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

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

    String value();
}

             
             
1 This line adds the @Offline annotation.

然后,您可以为自动连接的字段和参数提供自定义限定符,如下例所示:

Java
Kotlin
public class MovieRecommender {

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

    private MovieCatalog comedyCatalog;

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

    // ...
}

             
             
1 This element specifies the qualifier.

接下来,您可以提供候选Bean定义的信息。您可以将<;限定符/标记添加为<;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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

</beans>
             
             

Classpath Scanning and Managed Components中,您可以看到在XML中提供限定符元数据的基于注释的替代方法。具体地说,请参阅为限定符元数据提供注释

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

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

}

             
             

然后将注释添加到要自动连接的字段或属性,如下例所示:

Java
Kotlin
public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}

             
             

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

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

除了简单的属性之外,您还可以定义接受命名属性的自定义限定符批注,或者接受命名属性。如果随后在要自动连接的字段或参数上指定了多个属性值,则Bean定义必须与所有这些属性值匹配才能被视为自动连接候选对象。例如,考虑以下注释定义:

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

    String genre();

    Format format();
}

             
             

在本例中,格式是一个枚举,定义如下:

Java
Kotlin
public enum Format {
    VHS, DVD, BLURAY
}

             
             

要自动绑定的字段使用自定义限定符进行注释,并包含两个属性的值:genre格式,如下例所示:

Java
Kotlin
public class MovieRecommender {

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

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

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

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

    // ...
}

             
             

最后,Bean定义应该包含匹配的限定符值。该示例还演示了您可以使用Bean元属性来代替<;限定符/>;元素。如果可用,则优先使用<;限定符/<;元素及其属性,但如果不存在<;meta/>;标记中提供的值,则自动装配机制将依赖于此类限定符,如下例中的最后两个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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

</beans>
             
             

1.9.4. Using Generics as Autowiring Qualifiers

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

Java
Kotlin
@Configuration
public class MyConfiguration {

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

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

             
             

假设前面的Bean实现了泛型接口(即Store<;String>;Store<;Integer>;),您可以@AutwireStore接口,将泛型用作限定符,如下例所示:

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

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

             
             

当自动连接列表、映射实例和数组时,泛型限定符也适用。下面的示例自动生成泛型列表

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

             
             

1.9.5. Using CustomAutowireConfigurer

CustomAutowireConfigurer是一个 BeanFactoryPostProcessor,它允许您注册自己的定制限定符批注类型,即使它们没有用Spring的 @限定符批注进行批注。以下示例显示如何使用 CustomAuTower ireConfigurer

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

AuowireCandidate Resolver通过以下方式确定自动布线候选对象:

  • 每个Bean定义的AUTOWIRE-CADERATE值

  • <;Beans/&>元素上可用的任何默认自动配线候选模式

  • 存在@限定符批注和向CustomAuTower ireConfigurer注册的任何自定义批注

当多个Bean符合自动布线候选条件时,“主”的确定如下:如果候选中恰好有一个Bean定义的属性设置为true,则选择它。

1.9.6. Injection with @Resource

Spring还通过在字段或Bean属性设置器方法上使用JSR-250@Resource注释(jakarta.nottation.Resource)来支持注入。这是Jakarta EE中的常见模式:例如,在JSF管理的Bean和JAX-WS端点中。对于Spring管理的对象,Spring也支持这种模式。

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

Java
Kotlin
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

             
             
1 This line injects a @Resource.

如果未显式指定名称,则默认名称派生自字段名或setter方法。如果是字段,则采用字段名。对于setter方法,它接受Bean属性名。下面的示例将把名为movieFinder的Bean注入其setter方法:

Java
Kotlin
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

             
             
1 The context field is injected based on the known resolvable dependency type: ApplicationContext.
The name provided with the annotation is resolved as a bean name by the ApplicationContext of which the CommonAnnotationBeanPostProcessor is aware. The names can be resolved through JNDI if you configure Spring’s SimpleJndiBeanFactory explicitly. However, we recommend that you rely on the default behavior and use Spring’s JNDI lookup capabilities to preserve the level of indirection.

@Resource使用而未指定显式名称的独占情况下,类似于@AuTower@Resource查找主类型匹配而不是特定的命名Bean,并解析众所周知的可解析依赖项:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource接口。

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

Java
Kotlin
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}

             
             
1 The context field is injected based on the known resolvable dependency type: ApplicationContext.

1.9.7. Using @Value

@Value通常用于注入外部化属性:

Java
Kotlin
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

             
             

具有以下配置:

Java
Kotlin
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

             
             

和以下Applation.Properties文件:

catalog.name=MovieCatalog

             
             

在这种情况下,目录参数和字段将等于MovieCatalog值。

默认的宽松嵌入式值解析器是由Spring提供的。它将尝试解析属性值,如果无法解析,则将属性名称(例如${Catalog.name})作为值注入。如果您希望严格控制不存在的值,则应该声明一个PropertySourcesPlaceholderConfigurerBean,如下例所示:

Java
Kotlin
@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

             
             
When configuring a PropertySourcesPlaceholderConfigurer using JavaConfig, the @Bean method must be static.

如果无法解析任何${}占位符,使用上述配置可确保Spring初始化失败。还可以使用setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator等方法来自定义占位符。

Spring Boot configures by default a PropertySourcesPlaceholderConfigurer bean that will get properties from application.properties and application.yml files.

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,到Integerint)。多个逗号分隔值可以自动转换为字符串数组,而不需要额外的工作。

可以提供如下缺省值:

Java
Kotlin
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

             
             

SpringBeanPostProcessor在后台使用ConversionService来处理将@Value中的字符串值转换为目标类型的过程。如果您想为您自己的自定义类型提供转换支持,您可以提供您自己的ConversionServiceBean实例,如下例所示:

Java
Kotlin
@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

             
             

@Value包含Spel表达式时,将在运行时动态计算值,如下例所示:

Java
Kotlin
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

             
             

SPEL还支持使用更复杂的数据结构:

Java
Kotlin
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender( @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

             
             

1.9.8. Using @PostConstruct and @PreDestroy

Java
Kotlin
public class CachingMovieLister {

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

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

             
             

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

@Resource一样,@PostConstruct@PreDestroy注释类型是从JDK 6到8的标准Java库的一部分。然而,整个javax.注解包从JDK 9中的核心Java模块中分离出来,并最终从JDK 11中删除。从Jakarta EE 9开始,该包现在位于jakarta.notation中。如果需要,现在需要通过Maven Central获取jakarta.notation-api构件,只需像添加其他库一样将其添加到应用程序的类路径。

1.10. Classpath Scanning and Managed Components

本章中的大多数示例使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。上一节(基于注释的容器配置)演示了如何通过源代码级别的注释提供大量配置元数据。然而,即使在这些示例中,“基本”Bean定义也是在XML文件中显式定义的,而注释仅驱动依赖项注入。本节介绍通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件匹配并在容器中注册了相应的Bean定义的类。这样就不需要使用XML来执行Bean注册。相反,您可以使用注释(例如,@Component)、AspectJ类型表达式或您自己的定制筛选条件来选择哪些类注册了容器中的Bean定义。

从Spring3.0开始,由Spring JavaConfig项目提供的许多特性都是核心Spring框架的一部分。这允许您使用Java而不是使用传统的XML文件来定义Bean。查看@configuration@Bean@Import@DependsOn注释,了解如何使用这些新功能的示例。

1.10.1. @Component and Further Stereotype Annotations

@Repository注释是实现存储库角色或构造型(也称为数据访问对象或DAO)的任何类的标记。此标记的用途之一是自动转换异常,如异常转换中所述。

Spring提供了进一步的原型注释:@Component@Service@Controller@Component是任何Spring管理的组件的通用构造型。@Repository@Service@Controller@Component针对更具体用例(分别位于持久层、服务层和表示层)的专门化。因此,您可以使用@Component来注释您的组件类,但是,通过使用@Repository@Service@控制器来注释它们,您的类更适合由工具处理或与方面关联。例如,这些构造型注释是切入点的理想目标。@Repository@Service@Controller还可以在Spring框架的未来版本中提供额外的语义。因此,如果您选择使用@Component@Service作为服务层,@Service显然是更好的选择。类似地,如前所述,@Repository已经被支持作为持久层中自动异常转换的标记。

1.10.2. Using Meta-annotations and Composed Annotations

Spring提供的许多批注可以在您自己的代码中用作元批注。元批注是可以应用于其他批注的批注。例如,前面提到的@Service批注用@Component进行了元批注,如下例所示:

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

    // ...
}

             
             
1 The @Component causes @Service to be treated in the same way as @Component.

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

此外,合成批注可以选择性地重新声明元批注中的属性,以允许定制。当您只想公开元注释属性的一个子集时,这可能特别有用。例如,Spring的@SessionScope注释将作用域名硬编码为会话,但仍然允许定制proxyModel。下面的清单显示了SessionScope注释的定义:

Java
Kotlin
@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,如下所示:

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

             
             

您还可以重写proxyMode的值,如下面的示例所示:

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

             
             

有关更多详细信息,请参阅Spring Annotation Programming Model维基页面。

1.10.3. Automatically Detecting Classes and Registering Bean Definitions

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

Java
Kotlin
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

             
             
Java
Kotlin
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

             
             

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

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

             
             
For brevity, the preceding example could have used the value attribute of the annotation (that is, @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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>
             
             
The use of <context:component-scan> implicitly enables the functionality of <context:annotation-config>. There is usually no need to include the <context:annotation-config> element when using <context:component-scan>.

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

在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在模块信息描述符中导出组件类。如果您希望Spring调用类的非公共成员,请确保它们是“打开的”(即,它们在模块信息描述符中使用Open声明,而不是exports声明)。

此外,当您使用Component-Scan元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都被隐式包括在内。这意味着自动检测这两个组件并将它们连接在一起 - ,而不需要在XML语言中提供任何Bean配置元数据。

You can disable the registration of AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor by including the annotation-config attribute with a value of false.

1.10.4. Using Filters to Customize Scanning

默认情况下,使用@Component@Repository@Service@控制器@Configuration或本身使用@Component注释的自定义注释的类是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。将它们添加为@ComponentScan批注的cludeFiltersexcludeFilters属性(或作为<;上下文:Include-Filter/&><;上下文:&><;元素的子元素在XML配置中添加)。每个筛选器元素都需要类型表达式属性。下表描述了过滤选项:

Table 5. Filter Types
Filter Type Example Expression Description

批注(默认)

org.example.SomeAnnotation

要在目标组件的类型级别呈现呈现元呈现的批注。

可分配

org.example.SomeClass

目标组件可分配(扩展或实现)的类(或接口)。

AspectJ

org.Example..*服务+

目标组件要匹配的AspectJ类型表达式。

正则化

org\.Example\.Default.*

要与目标组件的类名匹配的正则表达式。

自定义

org.example.MyTypeFilter

org.springframework.core.type.TypeFilter接口的自定义实现。

以下示例显示该配置忽略所有@Repository注释,而改用“存根”存储库:

Java
Kotlin
@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>
             
             
You can also disable the default filters by setting useDefaultFilters=false on the annotation or by providing use-default-filters="false" as an attribute of the <component-scan/> element. This effectively disables automatic detection of classes annotated or meta-annotated with @Component, @Repository, @Service, @Controller, @RestController, or @Configuration.

1.10.5. Defining Bean Metadata within Components

Spring组件还可以向容器提供Bean定义元数据。您可以使用相同的@Bean注释来实现这一点,该注释用于在@configuration注释类中定义Bean元数据。以下示例显示了如何执行此操作:

Java
Kotlin
@Component
public class FactoryMethodComponent {

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

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

             
             

前面的类是一个Spring组件,它的doWork()方法中有特定于应用程序的代码。但是,它还提供了一个具有工厂方法的Bean定义,该工厂方法引用方法Public Instance()@Bean注释标识工厂方法和其他Bean定义属性,例如通过@Qualifier注释标识限定符值。可以指定的其他方法级批注有@Scope@Lazy和定制限定符批注。

In addition to its role for component initialization, you can also place the @Lazy annotation on injection points marked with @Autowired or @Inject. In this context, it leads to the injection of a lazy-resolution proxy. However, such a proxy approach is rather limited. For sophisticated lazy interactions, in particular in combination with optional dependencies, we recommend ObjectProvider<MyTargetBean> instead.

如前所述,支持自动装配的字段和方法,并额外支持@Bean方法的自动装配。以下示例显示了如何执行此操作:

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

             
             

该示例将字符串方法参数Country自动绑定为另一个名为Private ateInstance的Bean的age属性的值。Spring表达式语言元素通过符号#{<;Expression>;}定义属性的值。对于@Value注释,表达式解析器被预配置为在解析表达式文本时查找Bean名称。

从Spring Framework4.3开始,您还可以声明InjectionPoint类型的工厂方法参数(或其更具体的子类:DependencyDescriptor),以访问触发当前Bean创建的请求注入点。请注意,这仅适用于实际创建的Bean实例,而不适用于注入现有实例。因此,此功能对Prototype范围的Bean最有意义。对于其他作用域,工厂方法只看到在给定作用域中触发创建新Bean实例的注入点(例如,触发创建惰性单例Bean的依赖项)。在这样的场景中,您可以使用提供的注入点元数据和语义关怀。下面的示例说明如何使用InjectionPoint

Java
Kotlin
@Component
public class FactoryMethodComponent {

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

             
             

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

您可以将@Bean方法声明为静态,从而无需将其包含的配置类创建为实例即可调用它们。这在定义后处理器Bean(例如,类型为BeanFactoryPostProcessorBeanPostProcessor)时特别有意义,因为此类Bean在容器生命周期的早期被初始化,并且应该避免在那时触发配置的其他部分。

由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,甚至在@configuration类中也不会(如本节前面所述):CGLIB子类化只能覆盖非静态方法。因此,对另一个@Bean方法的直接调用具有标准的Java语义,导致从工厂方法本身直接返回一个独立的实例。

@Bean方法的Java语言可见性不会对Spring容器中的结果Bean定义产生直接影响。您可以在非@configuration类中自由声明您的工厂方法,也可以在任何地方声明静态方法。但是,@Configuration类中的常规@Bean方法需要是可重写的 - ,即它们不能声明为私有FINAL

@Bean方法也可以在给定组件或配置类的基类上发现,也可以在组件或配置类实现的接口中声明的Java 8默认方法上发现。这允许在组成复杂的配置安排时有很大的灵活性,甚至可以通过从Spring4.2开始的Java 8默认方法实现多重继承。

最后,单个类可以包含同一个Bean的多个@Bean方法,作为运行时根据可用的依赖项使用的多个工厂方法的排列。这与在其他配置场景中选择“最贪婪的”构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变量,类似于容器如何在多个@Autwire构造函数之间进行选择。

1.10.6. Naming Autodetected Components

当组件作为扫描过程的一部分被自动检测时,它的Bean名称由该扫描器已知的BeanNameGenerator策略生成。默认情况下,任何包含名称的Spring构造型注释(@Component@Repository@Service@Controller)都会将该名称提供给相应的Bean定义。

如果这样的注释不包含名称或任何其他检测到的组件(例如由定制筛选器发现的组件),则默认的Bean名称生成器返回未大写的非限定类名。例如,如果检测到以下组件类,则名称为myMovieListermovieFinderImpl

Java
Kotlin
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}

             
             
Java
Kotlin
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

             
             

如果您不想依赖默认的Bean命名策略,您可以提供一个定制的Bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名,如下面的示例注释和Bean定义所示。

If you run into naming conflicts due to multiple autodetected components having the same non-qualified class name (i.e., classes with identical names but residing in different packages), you may need to configure a BeanNameGenerator that defaults to the fully qualified class name for the generated bean name. As of Spring Framework 5.2.3, the FullyQualifiedAnnotationBeanNameGenerator located in package org.springframework.context.annotation can be used for such purposes.
Java
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}

             
             
<beans>
    <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" />
</beans>
             
             

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

1.10.7. Providing a Scope for Autodetected Components

与通常的Spring管理的组件一样,自动检测的组件的默认和最常见的作用域是Singleton。但是,有时您需要一个可以由@Scope注释指定的不同作用域。您可以在注释中提供作用域的名称,如下例所示:

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

             
             
@Scope annotations are only introspected on the concrete bean class (for annotated components) or the factory method (for @Bean methods). In contrast to XML bean definitions, there is no notion of bean definition inheritance, and inheritance hierarchies at the class level are irrelevant for metadata purposes.

有关特定于Web的作用域的详细信息,如Spring上下文中的“请求”或“会话”,请参阅请求、会话、应用程序和WebSocket作用域。与那些作用域的预先构建的批注一样,您也可以通过使用Spring的元批注方法来组成您自己的作用域批注:例如,使用@Scope(“Prototype”)进行元批注的定制批注,可能还声明了一个定制的作用域代理模式。

To provide a custom strategy for scope resolution rather than relying on the annotation-based approach, you can implement the ScopeMetadataResolver interface. Be sure to include a default no-arg constructor. Then you can provide the fully qualified class name when configuring the scanner, as the following example of both an annotation and a bean definition shows:
Java
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}

             
             
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
             
             

在使用某些非单一作用域时,可能需要为作用域对象生成代理。推理在作用域Bean中描述为依赖项。为此,Component-Scan元素上提供了Scope-Proxy属性。这三个可能的值是:no接口Target Class。例如,以下配置会产生标准的JDK动态代理:

Java
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}

             
             
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
             
             

1.10.8. Providing Qualifier Metadata with Annotations

带限定符的基于微调注释的自动装配中讨论了@限定符注释。该部分中的示例演示了@限定符注释和定制限定符注释的使用,以便在解析自动布线候选对象时提供细粒度的控制。因为这些示例基于XMLBean定义,所以通过使用XML中Bean元素的限定符meta子元素在候选Bean定义上提供限定符元数据。当依赖类路径扫描来自动检测组件时,您可以为限定符元数据提供候选类的类型级注释。以下三个示例演示了此技术:

Java
Kotlin
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

             
             
Java
Kotlin
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

             
             
Java
Kotlin
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

             
             
As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans of the same type to provide variations in their qualifier metadata, because that metadata is provided per-instance rather than per-class.

1.10.9. Generating an Index of Candidate Components

虽然类路径扫描非常快,但是可以通过在编译时创建候选对象的静态列表来提高大型应用程序的启动性能。在此模式下,作为组件扫描目标的所有模块都必须使用此机制。

Your existing @ComponentScan or <context:component-scan/> directives must remain unchanged to request the context to scan candidates in certain packages. When the ApplicationContext detects such an index, it automatically uses it rather than scanning the classpath.

要生成索引,请向包含作为组件扫描指令目标的组件的每个模块添加额外的依赖项。以下示例显示如何使用Maven执行此操作:

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

在Gradle 4.5及更早版本中,依赖项应在配置中声明,如下例所示:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:6.0.0"
}

             
             

对于Gradle 4.6及更高版本,应在注解处理器配置中声明依赖项,如下例所示:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:6.0.0"
}

             
             

SpringContext-Indexer构件生成包含在JAR文件中的META-INF/spring.Components文件。

When working with this mode in your IDE, the spring-context-indexer must be registered as an annotation processor to make sure the index is up-to-date when candidate components are updated.
The index is enabled automatically when a META-INF/spring.components file is found on the classpath. If an index is partially available for some libraries (or use cases) but could not be built for the whole application, you can fall back to a regular classpath arrangement (as though no index were present at all) by setting spring.index.ignore to true, either as a JVM system property or via the SpringProperties mechanism.

1.11. Using JSR 330 Standard Annotations

从Spring3.0开始,Spring支持JSR-330标准注释(依赖注入)。扫描这些注释的方式与扫描Spring注释的方式相同。要使用它们,您需要在类路径中拥有相关的JAR。

如果您使用Maven,jakarta.inject构件在标准Maven库(https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/).)中可用您可以将以下依赖项添加到文件pom.xml中:

<dependency>
    <groupId>jakarta.inject</groupId>
    <artifactId>jakarta.inject-api</artifactId>
    <version>1</version>
</dependency>
                 
                 

1.11.1. Dependency Injection with @Inject and @Named

您可以使用@jakarta.inject来代替@AuTower,如下所示:

Java
Kotlin
import jakarta.inject.Inject; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } public void listMovies() { this.movieFinder.findMovies(...); // ... } } 
             
             

@AuTower一样,您可以在字段级、方法级和构造函数参数级使用@Inject。此外,您可以将注入点声明为提供者,从而允许通过Provider.get()调用对较短作用域的Bean的按需访问或对其他Bean的延迟访问。下面的示例提供了前面示例的变体:

Java
Kotlin
import jakarta.inject.Inject; import jakarta.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注释,如下面的示例所示:

Java
Kotlin
import jakarta.inject.Inject; import jakarta.inject.Named; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(@Named("main") MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } 
             
             

@AuTower一样,@Inject也可以与java.util.Optional@Nullable一起使用。这在这里甚至更适用,因为@Inject没有必需的属性。以下两个示例说明如何使用@Inject@Nullable

public class SimpleMovieLister {

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

             
             
Java
Kotlin
public class SimpleMovieLister {

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

             
             

1.11.2. @Named and @ManagedBean: Standard Equivalents to the @Component Annotation

您可以不使用@Component,而是使用@jakarta.inint.Namejakarta.nottation.ManagedBean,如下面的示例所示:

Java
Kotlin
import jakarta.inject.Inject; import jakarta.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也可以类似的方式使用,如下例所示:

Java
Kotlin
import jakarta.inject.Inject; import jakarta.inject.Named; @Named public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } 
             
             

当您使用@Named@ManagedBean时,可以使用与使用Spring批注完全相同的方式使用组件扫描,如下面的示例所示:

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

             
             
In contrast to @Component, the JSR-330 @Named and the JSR-250 @ManagedBean annotations are not composable. You should use Spring’s stereotype model for building custom component annotations.

1.11.3. Limitations of JSR-330 Standard Annotations

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

Table 6. Spring component model elements versus JSR-330 variants
Spring jakarta.inject.* jakarta.inject restrictions / comments

@自动连接

@注入

@Inject没有‘Required’属性。可以与Java 8的可选一起使用。

@组件

@已命名/@托管Bean

JSR-330没有提供可组合的模型,只提供了一种识别命名组件的方法。

@Scope(“单例”)

@Singleton

JSR-330默认作用域类似于Spring的Prototype。然而,为了使其与Spring的常规缺省值保持一致,默认情况下,在Spring容器中声明的JSR-330 Bean是单例。为了使用Singleton以外的作用域,您应该使用Spring的@Scope注释。jakarta.inject还提供了jakarta.inject.Scope批注:然而,这个批注仅用于创建自定义批注。

@限定符

@限定符/@已命名

jakarta.inint.Qualifier只是一个用于构建自定义限定符的元注释。具体的字符串限定符(如Spring的@限定符与值)可以通过jakarta.inint.Name关联。

@Value

-

没有等价物

@懒惰

-

没有等价物

对象工厂

提供商

jakarta.inint.Provider是Spring的ObjectFactory的直接替代,只是有一个更短的get()方法名。它还可以与Spring的@AuTower结合使用,或者与非带注释的构造函数和setter方法结合使用。

1.12. Java-based Container Configuration

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

1.12.1. Basic Concepts: @Bean and @Configuration

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

@Bean注释用于指示方法实例化、配置和初始化要由Spring IOC容器管理的新对象。对于熟悉Spring的<;Beans/XML配置的人来说,@Bean注释的作用与<;Bean/>;元素的作用相同。您可以将@Bean注释的方法与任何Spring@Component一起使用。但是,它们最常与@configurationBean一起使用。

@configuration注释一个类表明它的主要用途是作为Bean定义的源。此外,@configuration类允许通过调用同一类中的其他@Bean方法来定义Bean间的依赖关系。最简单的@Configuration类如下所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

             
             

前面的AppConfig类相当于下面的Spring<;Bean/>;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方法。每个这样的方法实际上只是特定Bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是在运行时不必应用CGLIB子类化,因此在类设计方面没有限制(即,包含类可以是最终等等)。

在常见场景中,@Bean方法在@configuration类中声明,确保始终使用“Full”模式,从而将跨方法引用重定向到容器的生命周期管理。这可以防止通过常规Java调用意外调用相同的@Bean方法,这有助于减少在“lite”模式下操作时难以跟踪的细微错误。

@Bean@configuration注释将在以下小节中深入讨论。不过,首先,我们将介绍使用基于Java的配置创建Spring容器的各种方法。

1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext

以下各节介绍了Spring3.0中引入的AnnotationConfigApplicationContext,。这个通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受普通的@Component类和用JSR-330元数据注释的类。

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

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

Simple Construction

与在实例化<AnnotationConfigApplicationContext.>ClassPath XmlApplicationContext时将Spring XML文件用作输入的方式大致相同,您可以在实例化ClassPath XmlApplicationContext时使用@Configuration类作为输入这允许完全无XML地使用Spring容器,如下面的示例所示:

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

              
              

如前所述,AnnotationConfigApplicationContext不仅限于使用<代码>@配置 类。任何@Component或JSR-330注释类都可以作为输入提供给构造函数,如下面的示例所示:

Java
Kotlin
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依赖项注入注释,如@AuTower

Building the Container Programmatically by Using register(Class<?>…​)

您可以使用非参数构造函数实例化注册,然后使用<AnnotationConfigApplicationContext()方法对其进行配置。此方法在以编程方式构建AnnotationConfigApplicationContext.时特别有用以下示例显示了如何执行此操作:

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

              
              
Enabling Component Scanning with scan(String…​)

要启用组件扫描,您可以按如下方式注释@Configuration类:

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

              
              
1 This annotation enables component scanning.

有经验的Spring用户可能熟悉Spring的上下文:命名空间中的等价XML声明,如下例所示:

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

在前面的示例中,扫描com.acme包以查找任何@Component注释的类,并且这些类被注册为容器中的Spring Bean定义。AnnotationConfigApplicationContext公开<代码>扫描(字符串…​)方法以允许相同的组件扫描功能,如下面的示例所示:

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

              
              
Remember that @Configuration classes are meta-annotated with @Component, so they are candidates for component-scanning. In the preceding example, assuming that AppConfig is declared within the com.acme package (or any package underneath), it is picked up during the call to scan(). Upon refresh(), all its @Bean methods are processed and registered as bean definitions within the container.
Support for Web Applications with AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContextWebApplicationContext变体随AnnotationConfigWebApplicationContext.提供您可以在配置SpringContextLoaderListenerServlet侦听器、Spring MVCDispatcherServlet等时使用此实现。以下web.xml片段配置了一个典型的Spring MVC Web应用程序(请注意contextClass上下文参数和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>
              
              
For programmatic use cases, a GenericWebApplicationContext can be used as an alternative to AnnotationConfigWebApplicationContext. See the GenericWebApplicationContext javadoc for details.

1.12.3. Using the @Bean Annotation

@Bean是方法级批注,是XML<;Bean/>;元素的直接模拟。该批注支持<;Bean/>;提供的一些属性,例如:

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

Declaring a Bean

要声明一个Bean,您可以用@Bean注释来注释一个方法。使用此方法在指定为方法返回值的类型的ApplicationContext中注册Bean定义。默认情况下,Bean名称与方法名称相同。下面的示例显示了@Bean方法声明:

Java
Kotlin
@Configuration
public class AppConfig {

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

              
              

前面的配置完全等同于下面的Spring XML:

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

这两个声明都使名为TransService的Bean在ApplicationContext中可用,并绑定到TransferServiceImpl类型的对象实例,如下面的文本图像所示:

transferService -> com.acme.TransferServiceImpl

您还可以使用默认方法来定义Bean。这允许通过在默认方法上实现带有Bean定义的接口来组合Bean配置。

Java
public interface BaseConfig {

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

@Configuration
public class AppConfig implements BaseConfig {

}

              
              

您还可以使用接口(或基类)返回类型声明@Bean方法,如下面的示例所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

              
              

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

If you consistently refer to your types by a declared service interface, your @Bean return types may safely join that design decision. However, for components that implement several interfaces or for components potentially referred to by their implementation type, it is safer to declare the most specific return type possible (at least as specific as required by the injection points that refer to your bean).
Bean Dependencies

@Bean注释的方法可以有任意数量的参数来描述构建该Bean所需的依赖项。例如,如果我们的TransferService需要AcCountRepository,我们可以使用方法参数实例化该依赖项,如下面的示例所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

              
              

解析机制与基于构造函数的依赖注入基本相同。有关详细信息,请参阅相关小节

Receiving Lifecycle Callbacks

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

也完全支持常规的Spring生命周期回调。如果Bean实现了InitializingBeanDisposableBeanLifeccle,则容器将调用它们各自的方法。

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

@Bean注释支持指定任意的初始化和销毁回调方法,这与Spring XML在Bean元素上的init-方法销毁方法属性非常相似,如下面的示例所示:

Java
Kotlin
public class BeanOne {

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

public class BeanTwo {

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

@Configuration
public class AppConfig {

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

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

              
              

默认情况下,使用Java配置定义的具有公共CloseShutdown方法的Bean会自动登记销毁回调。如果您有一个公共的CloseShutdown方法,并且您不希望在容器关闭时调用它,您可以将@Bean(delestyMethod=“”)添加到您的Bean定义中,以禁用默认的(推断)模式。

默认情况下,您可能希望对使用JNDI获取的资源执行此操作,因为其生命周期是在应用程序外部管理的。特别是,请确保始终对DataSource执行此操作,因为众所周知,它在Jakarta EE应用服务器上是有问题的。

下面的示例说明如何阻止DataSource的自动销毁回调:

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

                   
                   

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

对于上述示例中的BeanOne,在构造过程中直接调用init()方法同样有效,如下面的示例所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

    // ...
}

              
              
When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle.
Specifying Bean Scope

Spring包括@Scope注释,以便您可以指定Bean的作用域。

Using the @Scope Annotation

您可以指定使用@Bean注释定义的Bean应该具有特定的作用域。您可以使用Bean Scope部分中指定的任何标准作用域。

默认作用域是Singleton,但您可以使用@Scope注释覆盖它,如下面的示例所示:

Java
Kotlin
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

               
               
@Scope and scoped-proxy

通过作用域代理,Spring提供了一种处理作用域依赖项的便捷方法。在使用XML配置时创建此类代理的最简单方法是<;aop:Scope-Proxy/>;元素。在Java中使用@Scope注释配置Bean可以提供与proxyMode属性相同的支持。缺省值为ScopedProxyMode.DEFAULT,这通常表示除非在组件扫描指令级别配置了不同的缺省值,否则不应创建作用域代理。可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACESScopedProxyMode.NO

如果使用Java将XML参考文档(请参阅范围代理)中的范围代理示例移植到我们的@Bean,则如下所示:

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

               
               
Customizing Bean Naming

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

Java
Kotlin
@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}

              
              
Bean Aliasing

正如命名Bean中所讨论的,有时需要为单个Bean指定多个名称,也称为Bean别名。为此,@Bean注释的name属性接受字符串数组。以下示例显示如何为Bean设置多个别名:

Java
Kotlin
@Configuration
public class AppConfig {

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

              
              
Bean Description

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

要向@Bean添加描述,可以使用@Description注释,如下例所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

              
              

1.12.4. Using the @Configuration annotation

@configuration是一个类级批注,表示对象是Bean定义的源。@configuration类通过@Bean注释的方法声明Bean。对@Configuration类的@Bean方法的调用也可用于定义Bean间的依赖关系。有关一般介绍,请参阅基本概念:@Bean@configuration

Injecting Inter-bean Dependencies

当Bean之间存在依赖关系时,表达这种依赖关系就像让一个Bean方法调用另一个Bean方法一样简单,如下例所示:

Java
Kotlin
@Configuration
public class AppConfig {

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

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

              
              

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

This method of declaring inter-bean dependencies works only when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies by using plain @Component classes.
Lookup Method Injection

如前所述,查找方法注入是一个高级特性,您应该很少使用。它在单例作用域的Bean依赖于原型作用域的Bean的情况下很有用。使用Java进行此类配置为实现此模式提供了一种自然的方法。以下示例显示如何使用查找方法注入:

Java
Kotlin
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()方法被覆盖,从而查找新的(原型)命令对象。以下示例显示了如何执行此操作:

Java
Kotlin
@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 createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

              
              
Further Information About How Java-based Configuration Works Internally

考虑以下示例,其中显示了两次调用带注释的@Bean方法:

Java
Kotlin
@Configuration
public class AppConfig {

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

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

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

              
              

clientDao()clientService1()中调用了一次,在clientService2()中调用了一次。由于此方法创建了ClientDaoImpl的一个新实例并返回它,因此通常会有两个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的Bean默认情况下有一个Singleton作用域。这就是魔术的用武之地:在启动时使用CGLIB对所有@configuration类进行子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有缓存的(作用域的)Bean。

The behavior could be different according to the scope of your bean. We are talking about singletons here.

从Spring3.2开始,不再需要将CGLIB添加到您的类路径中,因为CGLIB类已经被重新打包到org.springFrawork.cglib下,并直接包含在Spring-core JAR中。

由于CGLIB在启动时动态添加功能,因此有一些限制。特别是,配置类不能是最终的。然而,从4.3开始,配置类上允许使用任何构造函数,包括使用@Autwire或用于默认注入的单个非默认构造函数声明。

如果您希望避免CGLIB强加的任何限制,请考虑在非@Configuration类上声明您的@Bean方法(例如,在普通的@Component类上声明)。然后不会截取@Bean方法之间的跨方法调用,因此您必须在构造函数或方法级别独占依赖项注入。

1.12.5. Composing Java-based Configurations

Spring的基于Java的配置特性允许您编写注释,这可以降低配置的复杂性。

Using the @Import Annotation

就像<;import/<;元素用于在Spring XML文件中帮助模块化配置一样,@Import注释允许从另一个配置类加载@Bean定义,如下面的示例所示:

Java
Kotlin
@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即可,如下例所示:

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

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

              
              

这种方法简化了容器实例化,因为只需要处理一个类,而不需要在构造过程中记住可能大量的@configuration类。

As of Spring Framework 4.2, @Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you want to avoid component scanning, by using a few configuration classes as entry points to explicitly define all your components.
Injecting Dependencies on Imported @Bean Definitions

前面的示例是可行的,但过于简单。在大多数实际场景中,Bean在配置类之间相互依赖。在使用XML时,这不是问题,因为不涉及编译器,您可以声明ref=“omeBean”并信任Spring在容器初始化期间解决它。在使用@configuration类时,Java编译器会对配置模型施加约束,因为对其他Bean的引用必须是有效的Java语法。

幸运的是,解决这个问题很简单。正如我们已经讨论的@Bean方法可以有任意数量的参数来描述Bean依赖项。考虑以下更真实的场景,其中包含几个@Configuration类,每个类都依赖于在其他类中声明的Bean:

Java
Kotlin
@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一样利用@Autwire@Value注入和其他特性。

确保您以这种方式注入的依赖项只是最简单的一种。@Configuration类在上下文的初始化过程中很早就被处理,以这种方式强制注入依赖项可能会导致意外的提前初始化。只要有可能,就采用基于参数的注入,如上例所示。

另外,在@Bean中使用BeanPostProcessorBeanFactoryPostProcessor定义时要特别小心。这些方法通常应该声明为[email protected]方法,而不是触发其包含的配置类的实例化。否则,<代码>@自动连接 和<代码>@值 可能无法在配置类本身上工作,因为可以在AutowiredAnnotationBeanPostProcessor.之前将其创建为Bean实例

以下示例显示如何将一个Bean自动连接到另一个Bean:

Java
Kotlin
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

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

               
               
Constructor injection in @Configuration classes is only supported as of Spring Framework 4.3. Note also that there is no need to specify @Autowired if the target bean defines only one constructor.
Fully-qualifying imported beans for ease of navigation

在前面的场景中,使用@Autwire可以很好地工作,并提供所需的模块化,但是准确地确定在哪里声明了autwire的Bean定义仍然有些模棱两可。例如,作为一名查看ServiceConfig的开发人员,您如何确切知道@AuTower Account RepositoryBean的声明位置呢?它在代码中并不明确,这可能是很好的。请记住,Spring Tools for Eclipse提供了可以呈现图形的工具,这些图形显示了所有东西是如何连接的,这可能就是您所需要的。此外,您的Java IDE可以轻松找到Account Repository类型的所有声明和用法,并快速向您显示返回该类型的@Bean方法的位置。

如果这种二义性是不可接受的,并且您希望从您的IDE中直接从一个@Configuration类类导航到另一个类,请考虑自动装配配置类本身。以下示例显示了如何执行此操作:

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

                 
                 

在前面的情况中,定义了Account Repository的位置是完全显式的。但是,ServiceConfig现在与RepositoryConfig紧密耦合。这就是权衡。通过使用基于接口或基于抽象类的@Configuration类,可以在一定程度上缓解这种紧密耦合。请考虑以下示例:

Java
Kotlin
@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类及其依赖项与导航基于接口的代码的通常过程没有什么不同。

If you want to influence the startup creation order of certain beans, consider declaring some of them as @Lazy (for creation on first access instead of on startup) or as @DependsOn certain other beans (making sure that specific other beans are created before the current bean, beyond what the latter’s direct dependencies imply).
Conditionally Include @Configuration Classes or @Bean Methods

根据某些任意系统状态,有条件地启用或禁用整个@Configuration类甚至单个@Bean方法通常很有用。这方面的一个常见示例是,只有在SpringEnvironment中启用了特定的概要文件时,才使用@Profile注释来激活Bean(有关详细信息,请参阅Bean定义概要文件)。

@PROFILE注释实际上是通过使用更灵活的注释@Conditional实现的。@Conditional注释指示在注册@Bean之前应该参考的特定org.springframework.context.annotation.Condition实现。

<代码>条件 接口的实现提供<代码>匹配(…​)返回<代码>TRUE或FALSE方法。例如,下面的清单显示了用于@profile的实际条件实现:

Java
Kotlin
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 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;
}

              
              

有关详细信息,请参阅@Conditionaljavadoc。

Combining Java and XML Configuration

Spring的@configuration类支持并不是要100%完全替代Spring XML。一些工具,如Spring XML名称空间,仍然是配置容器的理想方式。如果xml是方便或必要的,您可以选择:或者通过使用ClassPath XmlApplicationContext以“以xml为中心”的方式实例化容器,或者通过使用AnnotationConfigApplicationContext@ImportResource注释根据需要以“以JAVA为中心”的方式实例化它。

XML-centric Use of @Configuration Classes

从XML引导Spring容器并以特殊方式包含@Configuration类可能更可取。例如,在使用Spring XML的大型现有代码库中,根据需要创建@configuration类并从现有的XML文件中包含它们会更容易。在这一节的后面,我们将介绍在这种“以XML为中心”的情况下使用@Configuration类的选项。

Declaring @Configuration classes as plain Spring <bean/> elements

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

以下示例显示了Java中的一个普通配置类:

Java
Kotlin
@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=
Java
Kotlin
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

                 
                 
In system-test-config.xml file, the AppConfig <bean/> does not declare an id element. While it would be acceptable to do so, it is unnecessary, given that no other bean ever refers to it, and it is unlikely to be explicitly fetched from the container by name. Similarly, the DataSource bean is only ever autowired by type, so an explicit bean id is not strictly required.
Using <context:component-scan/> to pick up @Configuration classes

因为@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 Class-centric Use of XML with @ImportResource

@configuration类是配置容器的主要机制的应用程序中,仍然可能需要至少使用一些XML。在这些场景中,您可以使用@ImportResource并根据需要定义任意数量的XML。这样做可以实现“以Java为中心”的方法来配置容器,并将XML保持在最低限度。下面的示例(包括一个配置类、一个定义Bean的XML文件、一个属性文件和main类)展示了如何使用@ImportResource注释来实现根据需要使用XML的“以Java为中心”的配置:

Java
Kotlin
@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=
Java
Kotlin
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

               
               

1.13. Environment Abstraction

接口是集成在容器中的抽象,它对应用程序环境的两个关键方面进行建模:配置文件属性

配置文件是一个命名的、逻辑组的Bean定义,只有在给定的配置文件处于活动状态时才会注册到容器中。可以将Bean分配给配置文件,无论该配置文件是在XML中定义的还是使用注释定义的。与配置文件相关的环境对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及哪些配置文件(如果有)应在默认情况下处于活动状态。

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

1.13.1. Bean Definition Profiles

Bean定义概要文件在核心容器中提供了一种机制,允许在不同环境中注册不同的Bean。“环境”这个词对不同的用户可能有不同的含义,此功能可以帮助许多用例,包括:

  • 在开发中使用内存中的数据源,而不是在QA或生产中从JNDI中查找相同的数据源。

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

  • 为客户A和客户B的部署注册定制的Bean实现。

考虑实际应用程序中需要数据源的第一个用例。在测试环境中,配置可能如下所示:

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

             
             

现在考虑如何将该应用程序部署到QA或生产环境中,假设该应用程序的数据源已注册到生产应用程序服务器的JNDI目录中。我们的DataSourceBean现在如下所示:

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

             
             

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的推移,Spring用户设计了许多方法来实现这一点,通常依赖于系统环境变量和包含${PLACEHOLDER}内标识的XML<;语句的组合,这些内标识根据环境变量的值解析为正确的配置文件路径。Bean定义概要文件是一个核心容器特性,它为这个问题提供了解决方案。

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

Using @Profile

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

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

              
              
1 The standaloneDataSource method is available only in the development profile.
2 The jndiDataSource method is available only in the production profile.
Java
Kotlin
@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");
    }
}

              
              
1 The standaloneDataSource method is available only in the development profile.
2 The jndiDataSource method is available only in the production profile.
As mentioned earlier, with @Bean methods, you typically choose to use programmatic JNDI lookups, by using either Spring’s JndiTemplate/JndiLocatorDelegate helpers or the straight JNDI InitialContext usage shown earlier but not the JndiObjectFactoryBean variant, which would force you to declare the return type as the FactoryBean type.

配置文件字符串可以包含简单的配置文件名称(例如,Products)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,Production&;us-East)。配置文件表达式中支持以下运算符:

  • :配置文件的逻辑“NOT”

  • &;:配置文件的逻辑“与”

  • |:配置文件的逻辑“或”

You cannot mix the & and | operators without using parentheses. For example, production & us-east | eu-central is not a valid expression. It must be expressed as production & (us-east | eu-central).

您可以使用@PROFILE作为元批注来创建自定义合成批注。下面的示例定义了一个自定义@Products批注,您可以使用该批注替代@Profile(“Products”)

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

              
              
If a @Configuration class is marked with @Profile, all of the @Bean methods and @Import annotations associated with that class are bypassed unless one or more of the specified profiles are active. If a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class is not registered or processed unless profiles 'p1' or 'p2' have been activated. If a given profile is prefixed with the NOT operator (!), the annotated element is registered only if the profile is not active. For example, given @Profile({"p1", "!p2"}), registration will occur if profile 'p1' is active or if profile 'p2' is not active.

还可以在方法级别声明@PROFILE,以仅包括配置类的一个特定Bean(例如,对于特定Bean的替代变体),如下面的示例所示:

Java
Kotlin
@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    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") (2)
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

              
              

@Bean方法上使用@PROFILE方法时,可能会出现特殊的情况:如果重载的@Bean方法具有相同的Java方法名称(类似于构造函数重载),则需要在所有重载的方法上一致声明@PROFILE条件。如果条件不一致,则只有重载方法中第一个声明的条件重要。因此,@PROFILE不能用于选择具有特定参数签名的重载方法而不是另一个。同一个Bean的所有工厂方法之间的解析遵循Spring在创建时的构造函数解析算法。

如果您希望定义具有不同配置文件条件的替代Bean,请使用指向相同Bean名称的不同Java方法名称,方法是使用@Bean名称属性,如前面的示例所示。如果参数签名都是相同的(例如,所有变量都没有无参数工厂方法),这是首先在有效的Java类中表示这种排列的唯一方式(因为特定名称和参数签名只能有一个方法)。

XML Bean Definition Profiles

对应的XML是<;Beans>;元素的配置文件属性。我们前面的示例配置可以在两个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>
              
              

还可以避免在同一文件中拆分和嵌套Bean/<>元素,如下例所示:

<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文件中引起混乱。

对应的XML不支持前面描述的配置文件表达式。但是,可以使用运算符对配置文件求反。还可以通过嵌套配置文件来应用逻辑“AND”,如下例所示:

<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="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>
                   
                   

在前面的示例中,如果Productsus-East配置文件都处于活动状态,则会公开DataSourceBean。

Activating a Profile

现在我们已经更新了我们的配置,我们仍然需要指示Spring哪个概要文件是活动的。如果我们现在启动样例应用程序,我们将看到抛出NoSuchBeanDefinitionException,因为容器找不到名为DataSource的Spring Bean。

激活配置文件可以通过几种方式完成,但最直接的方法是针对EnvironmentAPI以编程方式完成,该API可通过ApplicationContext获得。以下示例显示了如何执行此操作:

Java
Kotlin
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中的一个条目指定(请参阅PropertySource抽象)。在集成测试中,可以使用Spring-test模块中的@ActiveProfiles注释来声明活动概要文件(请参阅环境概要文件的上下文配置)。

请注意,配置文件不是“非此即彼”的命题。您可以一次激活多个配置文件。通过编程方式,您可以向<…>setActiveProfiles()方法提供多个配置文件名称,该方法接受<代码>字符串配置文件​变量。以下示例激活多个配置文件:

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

              
              

声明性地,spring.profiles.active可以接受逗号分隔的配置文件名称列表,如下例所示:

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

默认配置文件表示默认情况下启用的配置文件。请考虑以下示例:

Java
Kotlin
@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属性以声明方式更改默认配置文件的名称。

1.13.2. PropertySource Abstraction

Spring的Environment抽象提供了对属性源的可配置层次结构的搜索操作。请考虑以下清单:

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

             
             

在前面的代码片段中,我们看到了一种高级方法,用于询问Spring是否为当前环境定义了my-Property属性。为了回答这个问题,Environment对象对一组PropertySource对象执行搜索。PropertySource是对任何键-值对来源的简单抽象,SpringStandardEnvironment配置了两个PropertySource对象 - ,一个表示系统属性集(System.getProperties()),另一个表示系统环境变量集(System.getenv())。

These default property sources are present for StandardEnvironment, for use in standalone applications. StandardServletEnvironment is populated with additional default property sources including servlet config, servlet context parameters, and a JndiPropertySource if JNDI is available.

具体地说,当您使用StandardEnvironment时,如果运行时存在my-Property系统属性或my-Property环境变量,则对env.containsProperty(“my-property”)的调用返回TRUE。

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

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

  1. Servlet配置参数(如果适用,例如,在< >Dispatcher Servlet 上下文的情况下)

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

  3. JNDI环境变量(Java:comp/env/条目)

  4. JVM系统属性(-D命令行参数)

  5. JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许您有一个想要集成到此搜索中的自定义属性源。为此,请实现并实例化您自己的PropertySource,并将其添加到当前EnvironmentPropertySources集中。以下示例显示了如何执行此操作:

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

             
             

在前面的代码中,MyPropertySource在搜索中的优先级最高。如果它包含my-Property属性,则检测并返回该属性,以支持任何其他PropertySource中的任何my-Property属性。MutablePropertySourcesAPI公开了许多方法,允许对属性源集进行精确操作。

1.13.3. Using @PropertySource

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

给定一个名为app.properties的文件,其中包含键-值对testbean.name=myTestBean,下面的@Configuration类使用@PropertySource的方式,即调用testBean.getName()返回myTestBean

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

             
             

任何<代码>${…​}资源位置中存在的占位符将根据已针对环境注册的属性源集进行解析,如下面的示例所示:

Java
Kotlin
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

             
             

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

The @PropertySource annotation is repeatable, according to Java 8 conventions. However, all such @PropertySource annotations need to be declared at the same level, either directly on the configuration class or as meta-annotations within the same custom annotation. Mixing direct annotations and meta-annotations is not recommended, since direct annotations effectively override meta-annotations.

1.13.4. Placeholder Resolution in Statements

过去,元素中的占位符的值只能根据JVM系统属性或环境变量进行解析。情况已经不再是这样了。因为Environment抽象集成在整个容器中,所以很容易通过它传递占位符的解析。这意味着您可以以您喜欢的任何方式配置解析过程。您可以更改搜索系统特性和环境变量的优先顺序,也可以完全删除它们。您还可以根据需要将您自己的属性源添加到组合中。

具体地说,无论在哪里定义Customer属性,只要它在环境中可用,下面的语句都可以工作:

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

1.14. Registering a LoadTimeWeaver

LoadTimeWeaver被Spring用来在类被加载到Java虚拟机(JVM)时动态转换类。

若要启用加载时编织,可以将@EnableLoadTimeWeving添加到您的一个@Configuration类中,如下面的示例所示:

Java
Kotlin
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

            
            

或者,对于XML配置,您可以使用上下文:Load-time-weaver元素:

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

ApplicationContext配置后,该ApplicationContext中的任何Bean都可以实现LoadTimeWeverAware,从而接收对加载时编织器实例的引用。这在结合Spring的JPA支持时尤其有用,其中JPA类转换可能需要加载时编织。有关更多详细信息,请参考LocalContainerEntityManagerFactoryBeanjavadoc。有关AspectJ加载时编织的更多信息,请参阅在Spring框架中使用AspectJ加载时编织

1.15. Additional Capabilities of the ApplicationContext

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

  • 通过MessageSource接口访问I18N样式的消息。

  • 通过ResourceLoader接口访问资源,如URL和文件。

  • 事件发布,即通过使用ApplicationEventPublisher接口实现ApplicationListener接口的Bean。

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

1.15.1. Internationalization using MessageSource

ApplicationContext接口扩展了名为MessageSource的接口,因此提供了国际化(“I18N”)功能。Spring还提供了HierarchicalMessageSource接口,该接口可以分层解析消息。这些接口共同提供了Spring实现消息解析的基础。在这些接口上定义的方法包括:

  • 字符串getMessage(字符串code,Object[]args,字符串默认,区域设置锁定):用于从MessageSource检索消息的基本方法。如果未找到指定区域设置的消息,则使用默认消息。使用标准库提供的MessageFormat功能,传入的任何参数都将成为替换值。

  • 字符串getMessage(字符串code,Object[]args,Locale loc):本质上与前面的方法相同,但有一点不同:不能指定默认消息。如果找不到消息,则抛出NoSuchMessageException异常。

  • 字符串getMessage(MessageSourceResolable Resolable,Locale Locale):前面方法中使用的所有属性也包装在一个名为MessageSourceResolable的类中,您可以将其与此方法一起使用。

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

Spring提供了三个MessageSource实现:ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。它们都实现了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>
             
             

该示例假设您在类路径中定义了三个资源包,分别称为格式异常窗口。任何解析消息的请求都以JDK标准的方式处理,即通过ResourceBundle对象解析消息。出于本例的目的,假设上述两个资源包文件的内容如下:

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

下一个示例显示了一个运行MessageSource功能的程序。请记住,所有ApplicationContext实现也都是MessageSource实现,因此可以转换为MessageSource接口。

Java
Kotlin
public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}

             
             

上述程序的结果输出如下所示:

Alligators rock!

总而言之,MessageSource在一个名为beans.xml的文件中定义,该文件位于类路径的根目录下。MessageSourceBean定义通过其basename属性引用许多资源包。在列表中传递给basename属性的三个文件作为文件存在于类路径的根目录下,它们分别称为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.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
             
             
Java
Kotlin
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", Locale.ENGLISH);
        System.out.println(message);
    }
}

             
             

调用Execute()方法的结果如下:

The userDao argument is required.

关于国际化(“i18n”),Spring的各种MessageSource实现遵循与标准JDKResourceBundle相同的地区解析和后备规则。简而言之,继续前面定义的示例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.
Java
Kotlin
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

Because Spring’s MessageSource is based on Java’s ResourceBundle, it does not merge bundles with the same base name, but will only use the first bundle found. Subsequent message bundles with the same base name are ignored.
As an alternative to ResourceBundleMessageSource, Spring provides a ReloadableResourceBundleMessageSource class. This variant supports the same bundle file format but is more flexible than the standard JDK based ResourceBundleMessageSource implementation. In particular, it allows for reading files from any Spring resource location (not only from the classpath) and supports hot reloading of bundle property files (while efficiently caching them in between). See the ReloadableResourceBundleMessageSource javadoc for details.

1.15.2. Standard and Custom Events

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

As of Spring 4.2, the event infrastructure has been significantly improved and offers an annotation-based model as well as the ability to publish any arbitrary event (that is, an object that does not necessarily extend from ApplicationEvent). When such an object is published, we wrap it in an event for you.

下表描述了Spring提供的标准事件:

Table 7. Built-in Events
Event Explanation

上下文刷新事件

在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的刷新()方法)。在这里,“初始化”意味着加载了所有的Bean,检测并激活了后处理器Bean,预实例化了单例,并且ApplicationContext对象已经准备好可以使用了。只要上下文尚未关闭,就可以多次触发刷新,前提是所选的ApplicationContext确实支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。

ConextStartedEvent

在使用ConfigurableApplicationContext接口上的Start()方法启动ApplicationContext时发布。在这里,“已启动”意味着所有生命周期Bean都会收到一个显式的开始信号。通常,此信号用于在显式停止后重新启动Bean,但也可用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。

上下文停止事件

在使用ConfigurableApplicationContext接口上的Stop()方法停止ApplicationContext时发布。在这里,“停止”意味着所有生命周期Bean都会收到一个显式的停止信号。可以通过调用start()重新启动已停止的上下文。

ConextClosedEvent

通过使用ConfigurableApplicationContext接口上的Close()方法或通过JVM关闭挂钩关闭ApplicationContext时发布的。在这里,“关闭”意味着所有的独生豆都将被销毁。一旦关闭上下文,它就会到达其生命周期的尽头,并且无法刷新或重新启动。

RequestHandledEvent

一个特定于Web的事件,通知所有Bean某个HTTP请求已得到处理。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。

ServletRequestHandledEvent

添加特定于Servlet的上下文信息的RequestHandledEvent的子类。

您还可以创建和发布您自己的自定义事件。下面的示例显示了一个扩展了Spring的ApplicationEvent基类的简单类:

Java
Kotlin
public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

             
             

若要发布自定义ApplicationEvent,请对ApplicationEventPublisher调用PublishEvent()方法。通常,这是通过创建实现ApplicationEventPublisherAware的类并将其注册为一个Spring Bean来实现的。下面的示例显示了这样一个类:

Java
Kotlin
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

             
             

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

要接收自定义ApplicationEvent,您可以创建一个实现ApplicationListener的类,并将其注册为一个Spring Bean。下面的示例显示了这样一个类:

Java
Kotlin
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

             
             

请注意,ApplicationListener一般是使用您的自定义事件的类型(上一个示例中的BlockedListEvent)进行参数化的。这意味着onApplicationEvent()方法可以保持类型安全,从而避免任何向下转换的需要。您可以注册任意数量的事件侦听器,但请注意,默认情况下,事件侦听器同步接收事件。这意味着PublishEvent()方法在所有侦听器处理完事件之前都会阻塞。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中操作。如果需要使用另一种事件发布策略,请参阅用于Spring的ApplicationEventMulticaster接口和SimpleApplicationEventMulticaster实现的配置选项的javadoc。

以下示例显示了用于注册和配置上述每个类的Bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>
             
             

综上所述,当调用emailServiceBean的sendEmail()方法时,如果有任何应该阻止的电子邮件消息,则发布一个BlockedListEvent类型的定制事件。BLOCKEDListNotifierBean注册为ApplicationListener,并接收BlockedListEvent,此时它可以通知相应的参与方。

Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model.
Annotation-based Event Listeners

您可以使用@EventListener注释在托管Bean的任何方法上注册事件侦听器。BlockedListNotify可以重写如下:

Java
Kotlin
public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

              
              

方法签名再次声明它侦听的事件类型,但这一次使用灵活的名称,并且没有实现特定的侦听器接口。只要实际事件类型解析其实现层次结构中的泛型参数,也可以通过泛型缩小事件类型的范围。

如果您的方法应该侦听多个事件,或者如果您希望在不使用任何参数的情况下定义它,则还可以在注释本身上指定事件类型。以下示例显示了如何执行此操作:

Java
Kotlin
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

              
              

下面的示例显示如何重写我们的通知程序,以便仅在事件的content属性等于my-Event时才调用:

Java
Kotlin
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

              
              

每个Spel表达式针对专用上下文进行计算。下表列出了可供上下文使用的项目,以便您可以将其用于条件事件处理:

Table 8. Event SpEL available metadata
Name Location Description Example

事件

根对象

实际的ApplicationEvent

#root.Event事件

参数数组

根对象

用于调用该方法的参数(作为对象数组)。

#root.argsargsargs[0]访问第一个参数,依此类推。

参数名称

评估背景

任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为编译后的字节代码中没有调试信息),也可以使用#a<;#arg&>;语法获得各个参数,其中<;#arg&>;表示参数索引(从0开始)。

#blEvent#a0(也可以使用#p0#p<;#arg&>;参数表示法作为别名)

请注意,#root.event允许您访问底层事件,即使您的方法签名实际上引用了已发布的任意对象。

如果需要将一个事件作为处理另一个事件的结果发布,则可以更改方法签名以返回应该发布的事件,如下面的示例所示:

Java
Kotlin
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

              
              
This feature is not supported for asynchronous listeners.

handleBlockedListEvent()方法为它处理的每个BlockedListEvent发布一个新的ListUpdateEvent。如果需要发布多个事件,则可以返回集合或事件数组。

Asynchronous Listeners

如果希望特定的侦听器异步处理事件,可以重用常规@async支持。以下示例显示了如何执行此操作:

Java
Kotlin
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

              
              

使用异步事件时,请注意以下限制:

  • 如果异步事件侦听器引发异常,则不会将其传播到调用方。有关更多详细信息,请参阅AsyncUncaughtExceptionHandler

  • 异步事件侦听器方法不能通过返回值发布后续事件。如果您需要发布另一个事件作为处理的结果,则注入ApplicationEventPublisher以手动发布该事件。

Ordering Listeners

如果您需要先调用一个监听器,然后再调用另一个监听器,则可以在方法声明中添加@order注释,如下例所示:

Java
Kotlin
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

              
              
Generic Events

您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<;T&>,其中T是创建的实际实体的类型。例如,您可以创建以下侦听器定义,以便仅接收人员EntityCreatedEvent

Java
Kotlin
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}

              
              

由于类型擦除,仅当激发的事件解析事件侦听器筛选的通用参数(即,类似于类PersonCreatedEvent扩展EntiyCreatedEvent<;Person>;{…;​})。

在某些情况下,如果所有事件都遵循相同的结构(上例中的事件应该是这种情况),这可能会变得非常乏味。在这种情况下,您可以实现ResolvableTypeProvider来引导框架超出运行时环境所提供的范围。以下事件说明了如何做到这一点:

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

              
              
This works not only for ApplicationEvent but any arbitrary object that you send as an event.

1.15.3. Convenient Access to Low-level Resources

为了更好地使用和理解应用程序上下文,您应该熟悉Spring的资源抽象,如参考资料中所述。

应用程序上下文是ResourceLoader,可用于加载资源对象。资源本质上是JDKjava.net.URL类的一个功能更丰富的版本。事实上,在适当的情况下,资源的实现包装了java.net.URL的实例。资源可以透明的方式从几乎任何位置获取低级资源,包括从类路径、文件系统位置、任何可用标准URL描述的位置以及其他一些变体。如果资源位置字符串是不带任何特殊前缀的简单路径,则这些资源来自的位置是特定的,并且适合实际的应用程序上下文类型。

您可以将部署到应用程序上下文中的Bean配置为实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动回调,同时将应用程序上下文本身作为ResourceLoader传入。您还可以公开用于访问静态资源的Resource类型的属性。它们被注入其中,就像任何其他属性一样。您可以将这些资源属性指定为简单的字符串路径,并在部署Bean时依赖于从这些文本字符串到实际资源对象的自动转换。

提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,在简单的形式中,会根据特定的上下文实现进行适当的处理。例如,ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义,而不考虑实际的上下文类型。

1.15.4. Application Startup Tracking

ApplicationContext管理Spring应用程序的生命周期,并围绕组件提供丰富的编程模型。因此,复杂的应用程序可能具有同样复杂的组件图和启动阶段。

使用特定指标跟踪应用程序启动步骤可以帮助了解启动阶段的时间花在哪里,但它也可以用作更好地了解整个环境生命周期的一种方式。

AbstractApplicationContext(及其子类)使用ApplicationStartup进行检测,它收集有关各个启动阶段的StartupStep数据:

  • 应用程序上下文生命周期(基包扫描、配置类管理)

  • Bean生命周期(实例化、智能初始化、后处理)

  • 应用程序事件处理

以下是AnnotationConfigApplicationContext:中的插装示例

Java
Kotlin
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();

             
             

应用程序上下文已经使用多个步骤进行了检测。一旦记录下来,就可以使用特定工具收集、显示和分析这些启动步骤。有关现有启动步骤的完整列表,您可以查看专用附录部分。

默认的ApplicationStartup实现是无操作的变体,开销最小。这意味着默认情况下,在应用程序启动期间不会收集任何指标。Spring框架附带了一个使用Java飞行记录器跟踪启动步骤的实现:FlightRecorderApplicationStartup.要使用此变体,必须在创建后立即将其实例配置为ApplicationContext

如果开发人员正在提供他们自己的AbstractApplicationContext子类,或者如果他们希望收集更准确的数据,也可以使用ApplicationStartup基础结构。

ApplicationStartup is meant to be only used during application startup and for the core container; this is by no means a replacement for Java profilers or metrics libraries like Micrometer.

要开始收集自定义StartupStep,组件可以直接从应用程序上下文获取ApplicationStartup实例,使其组件实现ApplicationStartupAware,或者在任何注入点上请求ApplicationStartup类型。

Developers should not use the "spring.*" namespace when creating custom startup steps. This namespace is reserved for internal Spring usage and is subject to change.

1.15.5. Convenient ApplicationContext Instantiation for Web Applications

例如,您可以使用ConextLoader以声明方式创建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/ApplationConext.xml。当参数确实存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔字符串,并使用这些值作为搜索应用程序上下文的位置。还支持蚂蚁样式的路径模式。例如/WEB-INF/*Conext.xml(对于名称以Conext.xml结尾且位于WEB-INF目录中的所有文件)和/WEB-INF/**/*Conext.xml(对于WEB-INF的任何子目录中的所有此类文件)。

1.15.6. Deploying a Spring ApplicationContext as a Jakarta EE RAR File

可以将SpringApplicationContext部署为RAR文件,将上下文及其所有必需的Bean类和库JAR封装在Jakarta EE RAR部署单元中。这相当于引导一个独立的ApplicationContext(仅托管在Jakarta EE环境中)能够访问Jakarta EE服务器设施。与实际上部署无头WAR文件 - 的场景相比,RAR部署是一种更自然的替代方案,该WAR文件没有任何HTTP入口点,仅用于在Jakarta EE环境中引导SpringApplicationContext

RAR部署非常适合不需要HTTP入口点而只包含消息端点和计划作业的应用程序上下文。这样的上下文中的 可以使用应用服务器资源,比如JTA事务管理器和绑定到JNDI的JDBCDataSource实例和JMSConnectionFactory实例,还可以通过Spring的标准事务管理以及jndi和jmx支持工具向平台的jmx服务器 - 注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCAWorkManager交互。

有关href=“0”>SpringContextResourceAdapter部署中涉及的配置详细信息,请参阅

对于将Spring ApplicationContext作为Jakarta EE RAR文件的简单部署:

  1. 将所有应用程序类打包到RAR文件中(这是一个具有不同文件扩展名的标准JAR文件)。

  2. 将所有必需的库JAR添加到RAR归档的根目录中。

  3. 添加META-INF/ra.xml部署描述符(如SpringContextResourceAdapter)的javadoc和相应的Spring XML Bean定义文件(通常为META-INF/applicationContext.xml).

  4. 将生成的RAR文件放到应用程序服务器的部署目录中。

Such RAR deployment units are usually self-contained. They do not expose components to the outside world, not even to other modules of the same application. Interaction with a RAR-based ApplicationContext usually occurs through JMS destinations that it shares with other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs or react to new files in the file system (or the like). If it needs to allow synchronous access from the outside, it could (for example) export RMI endpoints, which may be used by other application modules on the same machine.

1.16. The BeanFactory API

BeanFactoryAPI为Spring的IoC功能提供了底层基础。它的特定契约主要用于与Spring和相关第三方框架的其他部分集成,其DefaultListableBeanFactory实现是更高层的GenericApplicationContext容器中的一个关键委托。

BeanFactory及相关接口(如BeanFactoryAwareInitializingBeanDisposableBean)是其他框架组件的重要集成点。由于不需要任何注释,甚至不需要反射,它们允许容器及其组件之间非常高效的交互。应用程序级别的Bean可以使用相同的回调接口,但通常更喜欢声明性依赖注入,或者通过注释或者通过编程配置。

请注意,核心BeanFactoryAPI级别及其DefaultListableBeanFactory实现并不假设要使用的配置格式或任何组件批注。所有这些特性都是通过扩展(如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor))实现的,并作为核心元数据表示对共享的BeanDefinition对象进行操作。这就是使Spring的容器如此灵活和可扩展的本质。

1.16.1. BeanFactory or ApplicationContext?

本节解释BeanFactoryApplicationContext容器级别之间的差异以及对引导的影响。

您应该使用ApplicationContext,除非您有充分的理由不这样做,GenericApplicationContext及其子类AnnotationConfigApplicationContext是自定义引导的常见实现。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件、触发类路径扫描、以编程方式注册Bean定义和带注释的类,以及(从5.0开始)注册功能Bean定义。

由于ApplicationContext包括BeanFactory的所有功能,因此通常推荐使用它而不是普通的BeanFactory,除非需要完全控制Bean处理。在ApplicationContext(如GenericApplicationContext实现)中,通过约定(即按Bean名称或Bean类型 - ,特别是后处理器)检测几种Bean,而普通的DefaultListableBeanFactory不知道任何特殊的Bean。

对于许多扩展容器特性,例如批注处理和AOP代理,BeanPostProcessor扩展点是必不可少的。如果只使用普通的DefaultListableBeanFactory,默认情况下不会检测和激活这样的后处理器。这种情况可能会令人困惑,