核心

1. IoC 容器

本章介绍 Spring 的控制反转 (IoC) 容器。spring-doc.cn

1.1. Spring IoC 容器和 bean 简介

本章介绍了 Inversion of Control 的 Spring Framework 实现 (IoC) 原则。IoC 也称为依赖关系注入 (DI)。这是一个过程 对象只能通过 constructor 参数、工厂方法的参数或在 Object 实例。容器 然后在创建 bean 时注入这些依赖项。这个过程从根本上说是 bean 本身的逆函数(因此得名 Inversion of Control) 使用 Direct 控制其依赖项的实例化或位置 类的构造或机制,例如 Service Locator 模式。spring-doc.cn

和 packages 是基础 用于 Spring Framework 的 IoC 容器。BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的 对象。ApplicationContext 是 的子接口。它补充说:org.springframework.beansorg.springframework.contextBeanFactoryspring-doc.cn

简而言之,提供了配置框架和基本的 功能,并添加更多特定于企业的功能。 是 和 的完全超集 仅在本章对 Spring 的 IoC 容器的描述中。了解更多 有关使用 而不是 的信息,请参阅 The BeanFactoryBeanFactoryApplicationContextApplicationContextBeanFactoryBeanFactoryApplicationContext,spring-doc.cn

在 Spring 中,构成应用程序主干并受管理的对象 被 Spring IoC 容器称为 bean。bean 是一个对象,它是 由 Spring IoC 容器实例化、组装和管理。否则,一个 Bean 只是应用程序中的众多对象之一。Bean 和依赖项 其中,它们反映在容器使用的配置元数据中。spring-doc.cn

1.2. 容器概述

该接口表示 Spring IoC 容器,并负责实例化、配置和组装 豆。容器获取有关要 通过读取配置元数据来实例化、配置和组装。这 配置元数据以 XML、Java 注释或 Java 代码表示。它让 您表达了组成应用程序的对象和丰富的相互依赖关系 在这些对象之间。org.springframework.context.ApplicationContextspring-doc.cn

提供了该接口的几种实现 与Spring。在独立应用程序中,通常会创建一个 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的实例。 虽然 XML 是定义配置元数据的传统格式,但您可以 指示容器使用 Java 注释或代码作为元数据格式 提供少量的 XML 配置以声明方式启用对这些 其他元数据格式。ApplicationContextspring-doc.cn

在大多数应用程序场景中,不需要显式用户代码来实例化一个或 Spring IoC 容器的更多实例。例如,在 Web 应用程序场景中, 文件中简单的八行(左右)样板 Web 描述符 XML 通常就足够了(参见 Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一种由 Eclipse 提供支持的开发 环境中),您可以通过单击几下鼠标或 击 键。web.xmlspring-doc.cn

下图显示了 Spring 工作原理的高级视图。您的应用程序类 与配置元数据结合使用,以便在 created 并初始化,则您拥有一个完全配置且可执行的系统,或者 应用。ApplicationContextspring-doc.cn

容器魔术
图 1.Spring IoC 容器

1.2.1. 配置元数据

如上图所示, Spring IoC 容器使用一种形式的 配置元数据。此配置元数据表示您作为 application developer,告诉 Spring 容器实例化、配置和组装 应用程序中的对象。spring-doc.cn

传统上,配置元数据以简单直观的 XML 格式提供。 这是本章大部分用来传达 Spring IoC 容器。spring-doc.cn

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

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

Spring 配置由至少一个 bean 组成,通常由多个 bean 组成 定义。基于 XML 的配置元数据配置这些 bean 作为顶级元素内的元素。Java 配置通常在类中使用 -annotated 方法。<bean/><beans/>@Bean@Configurationspring-doc.cn

这些 Bean 定义对应于组成应用程序的实际对象。 通常,您可以定义服务层对象、数据访问对象 (DAO)、表示 对象(如 Struts 实例)、基础结构对象(如 Hibernate、JMS 等)。通常,不配置 fine-grained domain 对象,因为它通常是 的 DAO 和业务逻辑来创建和加载域对象。但是,您可以使用 Spring 与 AspectJ 的集成,用于配置已在外部创建的对象 IoC 容器的控件。参见 使用 AspectJ 来 dependency-inject 域对象ActionSessionFactoriesQueuesspring-doc.cn

以下示例显示了基于 XML 的配置元数据的基本结构:spring-doc.cn

<?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 该属性是标识单个 Bean 定义的字符串。id
2 该属性定义 bean 的类型,并使用完全限定的 classname 的class

该属性的值是指协作对象。的 XML 此示例中未显示对协作对象的引用。有关更多信息,请参阅依赖项idspring-doc.cn

1.2.2. 实例化容器

位置路径或路径 提供给构造函数的是资源字符串,这些字符串让 容器加载来自各种外部资源的配置元数据,例如 作为本地文件系统、Java 等。ApplicationContextCLASSPATHspring-doc.cn

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

在了解了 Spring 的 IoC 容器之后,您可能希望更多地了解 Spring 的抽象(如 参考资料 中所述),它提供了一个方便的 从 URI 语法中定义的位置读取 InputStream 的机制。具体而言,路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。ResourceResourcespring-doc.cn

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

<?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.xmlspring-doc.cn

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

在前面的示例中,服务层由类 以及两个数据类型为 和 (基于 在 JPA Object-Relational Mapping 标准上)。该元素引用 name,并且该元素引用另一个 bean 的名称 定义。和 元素之间的这种联系表达了 协作对象。有关配置对象依赖项的详细信息,请参阅依赖项PetStoreServiceImplJpaAccountDaoJpaItemDaoproperty namerefidrefspring-doc.cn

编写基于 XML 的配置元数据

让 Bean 定义跨多个 XML 文件可能很有用。通常,每个个体 XML 配置文件表示体系结构中的逻辑层或模块。spring-doc.cn

你可以使用应用程序上下文构造函数从所有这些 bean 中加载 bean 定义 XML 片段。此构造函数采用多个位置,如上一节所示。或者,使用一个或多个 从另一个文件加载 Bean 定义的元素的出现次数,或者 文件。以下示例显示了如何执行此操作:Resource<import/>spring-doc.cn

<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 定义是从三个文件加载的:、 、 和 。所有位置路径都是 相对于执行导入的定义文件,因此必须位于 与执行导入的文件相同的目录或 Classpath 位置,而 和 必须位于 导入文件的位置。如您所见,前导斜杠将被忽略。然而,鉴于 这些路径是相对的,最好根本不使用斜杠。这 正在导入的文件的内容(包括 top level 元素)必须 根据 Spring Schema 是有效的 XML bean 定义。services.xmlmessageSource.xmlthemeSource.xmlservices.xmlmessageSource.xmlthemeSource.xmlresources<beans/>spring-doc.cn

可以使用 相对 “../“ 路径。这样做会创建对当前 应用。特别是,不建议将此引用用于 URL(对于 example, ),其中运行时解析进程选择 “nearest” classpath 根目录,然后查看其父目录。类路径 配置更改可能会导致选择不同的错误目录。classpath:classpath:../services.xmlspring-doc.cn

您始终可以使用完全限定的资源位置而不是相对路径:对于 example 或 .但是,be 知道您正在将应用程序的配置耦合到特定的 absolute 地点。通常最好为这种绝对的 locations — 例如,通过针对 JVM 解析的 “${...}” 占位符 系统属性。file:C:/config/services.xmlclasspath:/config/services.xmlspring-doc.cn

命名空间本身提供了 import 指令功能。进一步 除了普通 Bean 定义之外的配置功能在 SELECTION 中可用 Spring 提供的 XML 命名空间 — 例如,和 命名空间。contextutilspring-doc.cn

Groovy Bean 定义 DSL

作为外部化配置元数据的另一个示例,bean 定义还可以 用 Spring 的 Groovy Bean 定义 DSL 表示,如 Grails 框架所示。 通常,此类配置位于“.groovy”文件中,其结构如 以下示例:spring-doc.cn

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

这种配置样式在很大程度上等同于 XML bean 定义,甚至 支持 Spring 的 XML 配置命名空间。它还允许导入 XML bean 定义文件。importBeansspring-doc.cn

1.2.3. 使用容器

这是能够维护 不同 bean 及其依赖项的注册表。通过使用 method ,您可以检索 bean 的实例。ApplicationContextT getBean(String name, Class<T> requiredType)spring-doc.cn

允许您读取 bean 定义并访问它们,如下所示 示例显示:ApplicationContextspring-doc.cn

Java
// 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();
Kotlin
import org.springframework.beans.factory.getBean

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

// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")

// use configured instance
var userList = service.getUsernameList()

使用 Groovy 配置,引导看起来非常相似。它有不同的背景 implementation 类,该类是 Groovy 感知的(但也理解 XML bean 定义)。 以下示例显示了 Groovy 配置:spring-doc.cn

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

最灵活的变体是与阅读器结合使用 delegates — 例如,对于 XML 文件,如下所示 示例显示:GenericApplicationContextXmlBeanDefinitionReaderspring-doc.cn

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

您还可以使用 for Groovy 文件,如下所示 示例显示:GroovyBeanDefinitionReaderspring-doc.cn

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

您可以在同一个 上混合和匹配此类 Reader 委托 。 从不同的配置源读取 bean 定义。ApplicationContextspring-doc.cn

然后,您可以使用 来检索 bean 的实例。该接口有一些其他方法用于检索 bean,但理想情况下,您的应用程序 代码永远不应该使用它们。事实上,你的应用程序代码应该根本不调用该方法,因此根本不依赖于 Spring API。例如 Spring 与 Web 框架的集成为各种 Web 提供了依赖注入 框架组件(例如控制器和 JSF 托管 Bean),允许您声明 通过元数据(例如自动装配 Comments)对特定 bean 的依赖。getBeanApplicationContextgetBean()spring-doc.cn

1.3. Bean 概述

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

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

  • 包限定的类名:通常是 bean 被定义。spring-doc.cn

  • Bean 行为配置元素,这些元素表示 Bean 在 容器(范围、生命周期回调等)。spring-doc.cn

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

  • 要在新创建的对象中设置的其他配置设置 — 例如,大小 pool 的限制或要在管理 连接池。spring-doc.cn

此元数据转换为构成每个 Bean 定义的一组属性。 下表描述了这些属性:spring-doc.cn

表 1.bean 定义
财产 解释于...

spring-doc.cn

实例化 Beanspring-doc.cn

名字spring-doc.cn

命名 Beanspring-doc.cn

范围spring-doc.cn

Bean 作用域spring-doc.cn

构造函数参数spring-doc.cn

依赖关系注入spring-doc.cn

性能spring-doc.cn

依赖关系注入spring-doc.cn

自动装配模式spring-doc.cn

自动装配协作者spring-doc.cn

延迟初始化模式spring-doc.cn

延迟初始化的 Beanspring-doc.cn

初始化方法spring-doc.cn

初始化回调spring-doc.cn

销毁方法spring-doc.cn

销毁回调spring-doc.cn

除了包含有关如何创建特定 bean,则实现还允许注册现有的 在容器外部创建的对象(由用户创建)。这是通过访问 ApplicationContext 的 BeanFactory,该方法返回 BeanFactory 实现。 支持通过 and 方法进行此注册。但是,典型的应用程序仅使用 bean 通过常规 bean 定义元数据定义。ApplicationContextgetBeanFactory()DefaultListableBeanFactoryDefaultListableBeanFactoryregisterSingleton(..)registerBeanDefinition(..)spring-doc.cn

Bean 元数据和手动提供的单例实例需要注册为 early 为了让容器在自动装配期间正确地推断它们 和其他内省步骤。在覆盖现有元数据和现有 在某种程度上支持单例实例,在 运行时(与工厂的实时访问同时)不受官方支持,可能会 导致并发访问异常、Bean 容器中的状态不一致,或两者兼而有之。spring-doc.cn

1.3.1. 命名 Bean

每个 bean 都有一个或多个标识符。这些标识符在 托管 Bean 的容器。一个 bean 通常只有一个标识符。但是,如果它 需要多个,额外的可以被视为别名。spring-doc.cn

在基于 XML 的配置元数据中,您可以使用 attribute、 attribute 或 both 来指定 bean 标识符。该属性允许您指定 恰好是一个 ID。通常,这些名称是字母数字('myBean'、 'someService' 等),但它们也可以包含特殊字符。如果您想 引入 Bean 的其他别名,您也可以在属性中指定它们,用逗号 () 、分号 () 或空格分隔。作为 历史说明,在 Spring 3.1 之前的版本中,该属性为 定义为类型,用于约束可能的字符。从 3.1 开始, 它被定义为一个类型。请注意,bean 唯一性仍然是 由容器强制执行,但不再由 XML 解析器强制执行。idnameidname,;idxsd:IDxsd:stringidspring-doc.cn

您不需要为 bean 提供 a 或 an。如果未显式提供 or,则容器将为该 bean 生成唯一名称。然而 如果要按名称引用该 bean,请使用元素或 Service Locator 样式查找,您必须提供名称。 不提供名称的动机与使用 inner 有关 beanautowiring collaboratorsnameidnameidrefspring-doc.cn

Bean 命名约定

约定是在以下情况下对实例字段名称使用标准 Java 约定 命名 bean。也就是说,Bean 名称以小写字母开头,并且是驼峰式大小写的 从那里开始。此类名称的示例包括 、 等。accountManageraccountServiceuserDaologinControllerspring-doc.cn

一致地命名 bean 使您的配置更易于阅读和理解。 此外,如果你使用 Spring AOP,那么在将 advice 应用于一组 bean 时,它会有很大帮助 按名称相关。spring-doc.cn

通过在 Classpath 中进行组件扫描, Spring 会为 unnamed 生成 bean 名称 组件,遵循前面描述的规则:本质上,采用简单的类名 并将其初始字符转换为小写。然而,在(不寻常的)特别 当有多个字符并且同时具有第一个和第二个字符时 为大写,则保留原始大小写。这些规则与 定义者(Spring 在此处使用)。java.beans.Introspector.decapitalize
在 Bean 定义之外为 Bean 设置别名

在 bean 定义本身中,您可以通过使用 属性指定的最多一个名称与任意数量的其他名称的组合 names 在属性中。这些名称可以是同一 bean 的等效别名 ,并且在某些情况下很有用,例如让应用程序中的每个组件 使用特定于该组件的 Bean 名称来引用公共依赖项 本身。idnamespring-doc.cn

指定实际定义 bean 的所有别名并不总是足够的, 然而。有时需要为定义的 bean 引入别名 别处。在配置拆分的大型系统中,通常会出现这种情况 在每个子系统中,每个子系统都有自己的一组对象定义。 在基于 XML 的配置元数据中,您可以使用 Element 来完成 这。以下示例显示了如何执行此操作:<alias/>spring-doc.cn

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

在这种情况下,名为 使用此别名定义后,称为 。fromNametoNamespring-doc.cn

例如,子系统 A 的配置元数据可以通过 的名称。子系统 B 的配置元数据可以引用 名称为 .编写主应用程序时 使用这两个子系统,则主应用程序通过 的名称。要让所有三个名称都引用同一个对象,您可以 将以下别名定义添加到配置元数据中:subsystemA-dataSourcesubsystemB-dataSourcemyApp-dataSourcespring-doc.cn

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

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

Java 配置

如果使用 Javaconfiguration,则 Comments 可用于提供别名。 有关详细信息,请参阅使用 @Bean 注释@Beanspring-doc.cn

1.3.2. 实例化 Bean

bean 定义本质上是创建一个或多个对象的配方。这 容器在询问时查看命名 bean 的配方,并使用配置 元数据来创建(或获取)实际对象。spring-doc.cn

如果使用基于 XML 的配置元数据,请指定对象的类型(或类) 即在 Element 的属性中实例化。此属性(在内部是实例上的属性)通常是必需的。(有关异常,请参见使用实例工厂方法实例化Bean 定义继承。 您可以通过以下两种方式之一使用该属性:class<bean/>classClassBeanDefinitionClassspring-doc.cn

  • 通常,在容器 它本身通过反射性地调用其构造函数来直接创建 Bean 等效于带有运算符的 Java 代码。newspring-doc.cn

  • 要指定包含工厂方法的实际类,即 invoked 来创建对象,在不太常见的情况下,容器在类上调用工厂方法来创建 Bean。返回的对象类型 从工厂方法的调用可以是同一个类,也可以是另一个类 类。staticstaticstaticspring-doc.cn

嵌套类名

如果要为嵌套类配置 bean 定义,可以使用 binary name 或嵌套类的源名称。spring-doc.cn

例如,如果您在包中有一个名为 class 的 set,并且 此类有一个名为 的嵌套类,它们可以是 用美元符号 () 或点 () 分隔。因此,在 Bean 定义将是 或 。SomeThingcom.exampleSomeThingstaticOtherThing$.classcom.example.SomeThing$OtherThingcom.example.SomeThing.OtherThingspring-doc.cn

使用 Constructor 进行实例化

当你通过构造函数方法创建一个 bean 时,所有普通类都可以被 和 与 Spring 兼容。也就是说,正在开发的类不需要实现 任何特定接口或以特定方式编码。只需指定 bean 类应该就足够了。但是,具体取决于您为该特定 bean,则可能需要一个默认的(空的)构造函数。spring-doc.cn

Spring IoC 容器几乎可以管理您希望它管理的任何类。是的 不限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢带有 仅对默认 (无参数) 构造函数和适当的 setter 和 getter 进行建模 在容器中的属性之后。你也可以有更多异国情调的非 bean 样式 类。例如,如果您需要使用遗留连接池 绝对不遵守 JavaBean 规范,Spring 可以将其管理为 井。spring-doc.cn

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

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

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

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

使用 Static Factory Method 进行实例化

定义使用静态工厂方法创建的 Bean 时,使用该属性指定包含该工厂方法的类和一个属性 named 来指定工厂方法本身的名称。您应该 能够调用此方法(使用可选参数,如下所述)并返回一个实时的 object,该对象随后被视为通过构造函数创建的。 这种 bean 定义的一个用途是在遗留代码中调用工厂。classstaticfactory-methodstaticspring-doc.cn

以下 Bean 定义指定通过调用 Factory 方法。定义没有指定返回对象的类型(类), 仅包含 Factory 方法的类。在此示例中,该方法必须是静态方法。下面的示例演示如何指定工厂方法:createInstance()spring-doc.cn

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

下面的示例展示了一个将与前面的 bean 定义一起使用的类:spring-doc.cn

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

    public static ClientService createInstance() {
        return clientService;
    }
}
Kotlin
class ClientService private constructor() {
    companion object {
        private val clientService = ClientService()
        fun createInstance() = clientService
    }
}

有关向工厂方法提供(可选)参数的机制的详细信息 以及在从工厂返回对象后设置对象实例属性, 详见 依赖关系和配置spring-doc.cn

使用实例工厂方法进行实例化

类似于通过静态 工厂方法,使用实例工厂方法进行实例化会调用非静态 方法创建一个新的 bean。要使用此功能 机制中,将属性留空,并在属性 在当前 (或 parent) 容器中指定 bean 的名称,其中包含 要调用以创建对象的 instance 方法。设置 factory 方法本身具有 attribute。以下示例显示了 如何配置这样的 bean:classfactory-beanfactory-methodspring-doc.cn

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

以下示例显示了相应的类:spring-doc.cn

Java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
Kotlin
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
    }
    fun createClientServiceInstance(): ClientService {
        return clientService
    }
}

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

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

以下示例显示了相应的类:spring-doc.cn

Java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
Kotlin
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
        private val accountService = AccountServiceImpl()
    }

    fun createClientServiceInstance(): ClientService {
        return clientService
    }

    fun createAccountServiceInstance(): AccountService {
        return accountService
    }
}

这种方法表明,工厂 Bean 本身可以通过 依赖注入 (DI)。请参阅 依赖项 和 配置的详细信息spring-doc.cn

在 Spring 文档中,“工厂 Bean”是指在 Spring 容器,它通过实例静态工厂方法创建对象。相比之下,(注意大写)指的是特定于 Spring 的 FactoryBean 实现类。FactoryBean
确定 Bean 的运行时类型

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

了解特定 Bean 的实际运行时类型的推荐方法是 对指定 Bean 名称的调用。这需要以上所有 cases 中,并返回调用的对象类型 将返回相同的 bean 名称。BeanFactory.getTypeBeanFactory.getBeanspring-doc.cn

1.4. 依赖项

典型的企业应用程序不是由单个对象(或 Spring 用语)。即使是最简单的应用程序也有一些对象可以协同工作 呈现最终用户看到的连贯应用程序。下一节将介绍如何 您将从定义许多独立的 bean 定义到完全实现 对象协作实现目标的应用程序。spring-doc.cn

1.4.1. 依赖注入

依赖关系注入 (DI) 是对象定义其依赖关系的过程 (即,它们使用的其他对象)仅通过构造函数参数, 工厂方法的参数,或在 它是从工厂方法构造或返回的。然后,容器会注入这些 dependencies 的 Dependencies 创建。这个过程基本上是相反的(因此 名称 Inversion of Control) 控制实例化的 bean 本身 或通过使用类的直接构造来定位其依赖项,或者 Service Locator 模式。spring-doc.cn

使用 DI 原则,代码更简洁,当对象 提供它们的依赖项。对象不查找其依赖项,并且 不知道依赖项的位置或类。因此,您的课程变得更加容易 进行测试时,特别是当依赖项位于接口或抽象基类上时, 允许在单元测试中使用 stub 或 mock 实现。spring-doc.cn

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

基于构造函数的 DI 是通过容器调用带有 个参数,每个参数表示一个依赖项。调用工厂方法 使用特定的参数来构造 bean 几乎是等效的,并且此讨论 将参数视为构造函数和工厂方法。这 以下示例显示了一个只能使用 constructor 进行依赖项注入的类 注射:staticstaticspring-doc.cn

Java
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...
}
Kotlin
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
    // business logic that actually uses the injected MovieFinder is omitted...
}

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

构造函数参数解析

构造函数参数解析匹配通过使用参数的类型进行。如果没有 在 bean 定义的构造函数参数中存在潜在的歧义, 在 Bean 定义中定义构造函数参数的顺序是 Order 其中,当 bean 为 正在实例化。请考虑以下类:spring-doc.cn

Java
package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
Kotlin
package x.y

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)

假设 和 类没有通过继承相关,则没有 可能存在歧义。因此,以下配置工作正常,而您不会 需要在元素中显式指定 constructor 参数 indexes 或 types。ThingTwoThingThree<constructor-arg/>spring-doc.cn

<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 时,类型是已知的,并且可以进行匹配(就像 case 与前面的示例一起)。当使用简单类型(如 , Spring 无法确定值的类型)时,因此无法匹配 按类型,无需帮助。请考虑以下类:<value>true</value>spring-doc.cn

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

class ExampleBean(
    private val years: Int, // Number of years to calculate the Ultimate Answer
    private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
构造函数参数类型匹配

在前面的场景中,容器可以使用与简单类型的类型匹配,如果 您可以使用属性 如下例所示:typespring-doc.cn

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
构造函数参数索引

你可以使用 attribute 来显式指定构造函数参数的索引。 如下例所示:indexspring-doc.cn

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

除了解决多个简单值的歧义外,指定索引 解决构造函数具有两个相同类型的参数时的歧义。spring-doc.cn

索引从 0 开始。
构造函数参数名称

您还可以使用构造函数参数名称来消除值歧义,如下所示 示例显示:spring-doc.cn

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

请记住,要实现开箱即用的工作,您的代码必须使用 debug 标志,以便 Spring 可以从构造函数中查找参数名称。 如果您不能或不想使用 debug 标志编译代码,则可以使用 @ConstructorProperties JDK 注释显式命名构造函数参数。sample 类将 然后必须查看如下:spring-doc.cn

Java
package examples;

public class ExampleBean {

    // Fields omitted

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

class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
基于 Setter 的依赖注入

基于 setter 的 DI 是通过容器调用 setter 方法完成的 bean 在调用无参数构造函数或无参数工厂方法后将其更改为 实例化你的 bean。staticspring-doc.cn

以下示例显示了一个只能使用 pure setter 注入。此类是传统的 Java。它是一个没有依赖项的 POJO 在特定于容器的接口、基类或注释上。spring-doc.cn

Java
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...
}
Kotlin
class SimpleMovieLister {

    // a late-initialized property so that the Spring container can inject a MovieFinder
    lateinit var movieFinder: MovieFinder

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

它支持基于构造函数和基于 setter 的 bean 的 DI 管理。它还支持基于设置程序的 DI,因为某些依赖项已经 通过 constructor 方法注入。您可以以 a ,将其与实例结合使用 将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不工作 直接使用这些类(即以编程方式),而是使用 XML 定义、带注释的组件(即用 、 等注释的类)或基于 Java 的类中的方法。 然后,这些源在内部转换为 的实例并用于 加载整个 Spring IoC 容器实例。ApplicationContextBeanDefinitionPropertyEditorbean@Component@Controller@Bean@ConfigurationBeanDefinitionspring-doc.cn

基于构造函数还是基于 setter 的 DI?

由于您可以混合使用基于构造函数和基于 setter 的 DI,因此 将构造函数用于强制依赖项和 setter 方法或配置方法 对于可选依赖项。请注意,在 setter 方法上使用 @Required 注解可用于使该属性成为必需的依赖项; 但是,最好使用对参数进行编程验证的构造函数注入。spring-doc.cn

Spring 团队通常提倡构造函数注入,因为它允许您实现 application 组件作为不可变对象,并确保所需的依赖项 不是 。此外,构造函数注入的组件总是返回给客户端 (调用)处于完全初始化状态的代码。顺便说一句,大量的 constructor arguments 是一种不良的代码味道,这意味着该类可能有太多 责任,应该重构以更好地解决适当的关注点分离问题。nullspring-doc.cn

Setter 注入应主要仅用于可选的依赖项,这些依赖项可以是 在类中分配了合理的默认值。否则,非 null 校验必须为 在代码使用依赖项的任何地方执行。setter 注入的一个好处是 setter 方法使该类的对象适合重新配置或重新注入 后。因此,通过 JMX MBean 进行管理是一个引人注目的 setter 注入的用例。spring-doc.cn

使用对特定类最有意义的 DI 样式。有时,在处理 对于您没有源的第三方类,可以为您做出选择。 例如,如果第三方类不公开任何 setter 方法,则 constructor 注射可能是 DI 唯一可用的形式。spring-doc.cn

依赖项解析过程

容器按如下方式执行 Bean 依赖关系解析:spring-doc.cn

  • 使用配置元数据创建和初始化 描述所有 Bean 的配置元数据可以通过 XML、Java 代码或 附注。ApplicationContextspring-doc.cn

  • 对于每个 bean,其依赖项以 properties 的形式表示,constructor arguments,或者 static-factory 方法的参数(如果你使用它而不是 normal 构造函数)。当 Bean 为 实际创建。spring-doc.cn

  • 每个 property 或 constructor 参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。spring-doc.cn

  • 作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以 String 格式提供的值转换为所有内置类型,例如 、 等。intlongStringbooleanspring-doc.cn

Spring 容器在创建容器时验证每个 bean 的配置。 但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。 创建 singleton 范围并设置为预实例化(默认值)的 bean 创建容器时。作用域在 Bean Scopes 中定义。否则 仅当请求 Bean 时,才会创建 Bean。创建 Bean 可能会导致 要创建的 bean 图,作为 bean 的依赖项及其依赖项' 创建并分配依赖项(依此类推)。请注意,之间的分辨率不匹配 这些依赖项可能会延迟显示 — 即,在首次创建受影响的 bean 时。spring-doc.cn

循环依赖关系

如果你主要使用构造函数注入,则可以创建一个 unresolvable 循环依赖关系方案。spring-doc.cn

例如:类 A 通过构造函数注入需要类 B 的实例,并且 类 B 需要通过构造函数注入的类 A 的实例。如果配置 类 A 和 B 的 bean 相互注入,则 Spring IoC 容器 在运行时检测到此循环引用,并引发 .BeanCurrentlyInCreationExceptionspring-doc.cn

一种可能的解决方法是编辑一些类的源代码,以便由 setter 而不是构造函数。或者,避免构造函数注入并使用 仅 setter 注入。换句话说,虽然不建议这样做,但您可以配置 使用 setter 注入的循环依赖项。spring-doc.cn

与典型情况(没有循环依赖关系)不同,循环依赖关系 在 bean A 和 bean B 之间强制将其中一个 bean 注入到另一个 bean 中 自身完全初始化(典型的先有鸡还是先有蛋的情况)。spring-doc.cn

您通常可以相信 Spring 会做正确的事情。它检测配置问题, 例如对不存在的 bean 和循环依赖项的引用,在 Container load-time 的Spring 会尽可能晚地设置属性并解析依赖关系,当 Bean 实际上是创建的。这意味着已加载的 Spring 容器 如果存在 创建该对象或其依赖项之一时出现问题,例如,Bean 会抛出 异常。这可能会延迟 可见性是 implementation by 默认的 pre-instantiate singleton bean。以一些前期时间和内存为代价 在实际需要这些 bean 之前创建它们,则会发现配置问题 当 创建时,而不是之后。您仍然可以覆盖此默认值 行为,以便单例 bean 惰性初始化,而不是 agerly 预实例化。ApplicationContextApplicationContextspring-doc.cn

如果不存在循环依赖关系,则当一个或多个协作 bean 正在 注入到依赖 bean 中,每个协作 bean 都完全在 注入到依赖 bean 中。这意味着,如果 Bean A 依赖于 bean B 时,Spring IoC 容器会在调用 在 bean A 上执行 setter 方法。换句话说,bean 被实例化(如果它不是 预先实例化的单例),设置其依赖项,以及相关的生命周期 方法(例如配置的 init 方法InitializingBean 回调方法) 被调用。spring-doc.cn

依赖关系注入示例

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 的一部分指定了一些 bean 定义,如下所示:spring-doc.cn

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

以下示例显示了相应的类:ExampleBeanspring-doc.cn

Java
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;
    }
}
Kotlin
class ExampleBean {
    lateinit var beanOne: AnotherBean
    lateinit var beanTwo: YetAnotherBean
    var i: Int = 0
}

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

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

以下示例显示了相应的类:ExampleBeanspring-doc.cn

Java
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;
    }
}
Kotlin
class ExampleBean(
        private val beanOne: AnotherBean,
        private val beanTwo: YetAnotherBean,
        private val i: Int)

在 bean 定义中指定的构造函数参数用作 的构造函数。ExampleBeanspring-doc.cn

现在考虑此示例的一个变体,其中 Spring 不是使用构造函数,而是 Spring 告知调用工厂方法以返回对象的实例:staticspring-doc.cn

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

以下示例显示了相应的类:ExampleBeanspring-doc.cn

Java
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;
    }
}
Kotlin
class ExampleBean private constructor() {
    companion object {
        // 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.
        fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
            val eb = ExampleBean (...)
            // some other operations...
            return eb
        }
    }
}

工厂方法的参数由元素 与实际使用构造函数完全相同。类的类型为 返回的 API API 的 API API 的 API 请求 包含 Factory 方法(尽管在此示例中,它是)。实例 (非静态)Factory 方法可以以基本相同的方式使用(除了 使用 attribute 而不是 attribute),因此我们 不要在这里讨论这些细节。static<constructor-arg/>staticfactory-beanclassspring-doc.cn

1.4.2. 依赖项和配置详解

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

Straight 值(Primitives、Strings 等)

元素的属性指定属性或构造函数 参数作为人类可读的字符串表示形式。Spring 的转换服务用于转换这些 值从 a 更改为属性或参数的实际类型。 以下示例显示了正在设置的各种值:value<property/>Stringspring-doc.cn

<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-namespace 以获得更简洁的 XML 配置:spring-doc.cn

<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 更简洁。但是,拼写错误是在运行时发现的,而不是 设计时,除非您使用 IDE(例如 IntelliJ IDEASpring Tools for Eclipse) 支持在创建 Bean 定义时自动完成属性。这样的 IDE 强烈建议您提供帮助。spring-doc.cn

您还可以配置实例,如下所示:java.util.Propertiesspring-doc.cn

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

Spring 容器使用 JavaBeans 机制将元素内的文本转换为实例。这 是一个不错的快捷方式,并且是 Spring 团队确实喜欢使用 Attribute 样式上的嵌套元素。<value/>java.util.PropertiesPropertyEditor<value/>valuespring-doc.cn

元素idref

该元素只是一种防错的方式,用于将 (字符串值 - 而不是 一个引用)指向 or 元素。以下示例演示如何使用它:idrefid<constructor-arg/><property/>spring-doc.cn

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

前面的 bean 定义代码片段完全等价(在运行时)用于 以下代码段:spring-doc.cn

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一种形式比第二种形式更可取,因为使用标签可以让 容器在部署时验证引用的名为 Bean 的实际 存在。在第二个变体中,不对传递的值执行验证 添加到 Bean 的属性中。拼写错误仅被发现(大多数 可能是致命的结果)。如果 Bean 是原型 Bean,则此拼写错误和结果异常 只有在部署容器很久之后才能被发现。idreftargetNameclientclientclientspring-doc.cn

4.0 bean 中不再支持元素上的属性 XSD 的引用,因为它不再提供优于常规引用的价值。改变 升级到 4.0 架构时对的现有引用。localidrefbeanidref localidref bean

一个常见的位置(至少在 Spring 2.0 之前的版本中),其中元素 带来价值的是在 bean 定义中配置 AOP 拦截器。在指定 拦截器 名称 可防止您拼写错误的拦截器 ID。<idref/>ProxyFactoryBean<idref/>spring-doc.cn

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

该元素是 or 定义元素内的最后一个元素。在这里,您将 bean 的指定属性的值设置为 对容器管理的另一个 Bean(协作者)的引用。引用的 Bean 是要设置其属性的 Bean 的依赖项,并且按需初始化 根据需要。(如果协作者是单例 bean,则它可能会 已由容器初始化。所有引用最终都是对 另一个对象。范围界定和验证取决于您是指定 other 对象。ref<constructor-arg/><property/>beanparentspring-doc.cn

通过 tag 的属性指定目标 bean 是最多的 general 形式,并允许创建对同一容器中任何 bean 的引用,或者 父容器,无论它是否在同一个 XML 文件中。该属性的值可以与目标 Bean 的属性相同,也可以相同 作为目标 Bean 属性中的值之一。以下示例 演示如何使用元素:bean<ref/>beanidnamerefspring-doc.cn

<ref bean="someBean"/>

通过该属性指定目标 Bean 会创建对 Bean 的引用 即在当前容器的父容器中。该属性的值可以与目标 Bean 的属性相同,也可以与目标 Bean 的 值。目标 Bean 必须位于 当前容器的父容器。您应该主要使用此 bean 引用变体 当您具有容器层次结构并且希望将现有 bean 包装在父 bean 中时 容器,其代理与父 Bean 同名。以下一对 listings 显示了如何使用该属性:parentparentidnameparentspring-doc.cn

<!-- 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>
4.0 bean 中不再支持元素上的属性 XSD 的引用,因为它不再提供优于常规引用的价值。改变 升级到 4.0 架构时对的现有引用。localrefbeanref localref bean
内部 Bean

or 元素中的元素定义了一个 inner bean,如下例所示:<bean/><property/><constructor-arg/>spring-doc.cn

<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 中。scopespring-doc.cn

作为一种极端情况,可以从自定义范围接收销毁回调 — 例如,对于包含在单例 bean 中的请求范围的内部 bean。创作 的 Bean 实例绑定到其包含的 Bean,但销毁回调允许它 参与请求范围的生命周期。这不是常见情况。内豆 通常只是共享其包含的 bean 的 scope。spring-doc.cn

收集

、、 、 和 元素设置属性 和 Java 类型 、 、 和 的参数 分别。以下示例显示了如何使用它们:<list/><set/><map/><props/>CollectionListSetMapPropertiesspring-doc.cn

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

map 键或值的值,或者一个 set 值,也可以是 以下元素:spring-doc.cn

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

Spring 容器还支持合并集合。应用程序 开发人员可以定义父级、 或 元素 并让子、 、 或 元素继承和 覆盖父集合中的值。也就是说,子集合的值为 将父集合和子集合的元素与子集合的 collection 元素覆盖父集合中指定的值。<list/><map/><set/><props/><list/><map/><set/><props/>spring-doc.cn

本节关于合并讨论了父子 Bean 机制。不熟悉的读者 with 父 Bean 和 Child Bean 定义可能希望在继续之前阅读相关部分spring-doc.cn

以下示例演示了集合合并:spring-doc.cn

<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 定义的 property 的 element 上使用 attribute。解析 Bean 时 并由 Container 实例化,则生成的实例具有一个 collection,其中包含将 child 的集合与 parent的集合合并的结果。以下清单 显示结果:merge=true<props/>adminEmailschildchildadminEmailsPropertiesadminEmailsadminEmailsspring-doc.cn

子集合的值集从 parent 中,并且该值的 child 值将覆盖 父集合。Properties<props/>supportspring-doc.cn

此合并行为类似于 、 和 集合类型。在元素的特定情况下,语义 与集合类型(即值集合的概念)相关联。parent的值位于所有child列表的 值。对于 、 和 集合类型,无排序 存在。因此,对于作为基础的集合类型,没有排序语义 容器 内部使用。<list/><map/><set/><list/>ListorderedMapSetPropertiesMapSetPropertiesspring-doc.cn

集合合并的限制

您无法合并不同的集合类型(如 a 和 a )。如果你 尝试这样做,则会引发 appropriate 。该属性必须为 在较低的继承子定义上指定。指定 on 属性 父集合定义是冗余的,不会导致所需的合并。MapListExceptionmergemergespring-doc.cn

强类型集合

在 Java 5 中引入泛型类型后,您可以使用强类型集合。 也就是说,可以声明一个类型,使其只能包含 (例如)元素。如果您使用 Spring 依赖注入 强类型化为 bean,您可以利用 Spring 的 type-conversion 支持,以便在将强类型实例的元素添加到 . 下面的 Java 类和 bean 定义显示了如何做到这一点:CollectionStringCollectionCollectionCollectionspring-doc.cn

Java
public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
Kotlin
class SomeClass {
    lateinit var accounts: Map<String, Float>
}
<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>

当 bean 的属性准备好进行注入时,泛型 有关强类型的元素类型的信息为 由 Reflection 提供。因此,Spring 的类型转换基础结构识别 各种 value 元素为 type ,字符串值 (, 和 ) 将转换为 actual 类型。accountssomethingMap<String, Float>Float9.992.753.99Floatspring-doc.cn

Null 和空字符串值

Spring 将 properties 等的空参数视为 empty 。这 以下基于 XML 的配置元数据代码段将属性设置为空值 (“”)。StringsemailStringspring-doc.cn

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

前面的示例等效于以下 Java 代码:spring-doc.cn

Java
exampleBean.setEmail("");
Kotlin
exampleBean.email = ""

元素处理值。下面的清单显示了一个示例:<null/>nullspring-doc.cn

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

上述配置等效于以下 Java 代码:spring-doc.cn

Java
exampleBean.setEmail(null);
Kotlin
exampleBean.email = null
带有 p 命名空间的 XML 快捷方式

p 命名空间允许您使用元素的属性(而不是嵌套元素)来描述您的属性值、协作 bean 或两者。bean<property/>spring-doc.cn

Spring 支持带有命名空间的可扩展配置格式, 它们基于 XML 架构定义。中讨论的配置格式 本章在 XML Schema 文档中定义。但是,未定义 p 命名空间 在 XSD 文件中,并且仅存在于 Spring 的核心中。beansspring-doc.cn

以下示例显示了两个 XML 代码段(第一个使用 标准 XML 格式,第二个使用 p-namespace),它们解析为相同的结果:spring-doc.cn

<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 定义中调用的 p-namespace 中的一个属性。 这告诉 Spring 包含一个属性声明。如前所述, p-namespace 没有 schema 定义,因此您可以设置属性的名称 添加到属性名称。emailspring-doc.cn

下一个示例包括另外两个 bean 定义,这两个定义都引用了 另一个 bean:spring-doc.cn

<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-namespace 的属性值 但也使用特殊格式来声明属性引用。而第一个 bean 定义用于创建从 bean 到 bean 的引用,第二个 bean 定义用作 属性来执行完全相同的操作。在本例中,是属性名称 而 part 表示这不是一个直接值,而是一个 引用另一个 bean。<property name="spouse" ref="jane"/>johnjanep:spouse-ref="jane"spouse-refspring-doc.cn

p 命名空间不如标准 XML 格式灵活。例如,格式 用于声明属性引用与以 结尾的属性冲突,而 标准 XML 格式则不需要。我们建议您仔细选择方法,并 将此内容传达给您的团队成员,以避免生成使用全部 同时有三种方法。Ref
带有 c-namespace 的 XML 快捷方式

类似于 Spring 中引入的带有 p-namespace 的 XML Shortcut、c-namespace 3.1 中,允许内联属性来配置构造函数参数,而不是 然后是嵌套元素。constructor-argspring-doc.cn

以下示例使用命名空间执行与 from Constructor-based Dependency Injection 相同的操作:c:spring-doc.cn

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

命名空间使用与命名空间相同的约定( Bean 引用)来按名称设置构造函数参数。同样地 它需要在 XML 文件中声明,即使它未在 XSD 架构中定义 (它存在于 Spring 核心中)。c:p:-refspring-doc.cn

对于构造函数参数名称不可用的极少数情况(通常如果 字节码在没有调试信息的情况下编译),您可以使用 fallback 到 ARGUMENT 索引,如下所示:spring-doc.cn

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="[email protected]"/>
由于 XML 语法的原因,索引表示法需要存在前导 , 因为 XML 属性名称不能以数字开头(即使某些 IDE 允许)。 相应的索引表示法也可用于元素,但 不常用,因为 plain order of declaration 通常就足够了。_<constructor-arg>

在实践中,构造函数解析机制在匹配方面非常有效 参数,因此,除非你真的需要,否则我们建议使用 name 表示法 在整个配置中。spring-doc.cn

复合属性名称

在设置 Bean 属性时,可以使用复合或嵌套属性名称,只要 除 final property name 之外,path 的所有组件都不是 。考虑一下 遵循 bean 定义:nullspring-doc.cn

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

bean 具有一个 property,该 property 具有一个 property,该 property 又具有一个 property,并且该 final 属性被设置为值 。为了 this 要工作,属性 和 属性 不得 be 在 bean 构建之后。否则,将引发 a。somethingfredbobsammysammy123fredsomethingbobfrednullNullPointerExceptionspring-doc.cn

1.4.3. 使用depends-on

如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为 另一个人的财产。通常,您可以使用基于 XML 的配置元数据中的 <ref/> 元素来实现此目的。但是,有时 豆子就不那么直接了。例如,当类中的 static 初始值设定项需要 triggered,例如用于数据库驱动程序注册。该属性可以 在使用此元素的 bean 之前显式强制初始化一个或多个 bean 已初始化。以下示例使用该属性来表示 对单个 bean 的依赖:depends-ondepends-onspring-doc.cn

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

要表示对多个 bean 的依赖关系,请提供一个 bean 名称列表作为 属性(逗号、空格和分号有效 delimiters):depends-onspring-doc.cn

<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" />
该属性可以指定初始化时依赖项和 在仅 singleton bean 的情况下,相应的 销毁时间依赖性。定义关系的依赖 Bean 在给定的 bean 本身被销毁之前,首先销毁给定的 bean。 因此,还可以控制关机顺序。depends-ondepends-ondepends-on

1.4.4. 延迟初始化的 Bean

默认情况下,实现在初始化过程中急切地创建和配置所有 singleton bean 过程。通常,这种预实例化是可取的,因为 配置或周围环境会立即被发现,而不是几个小时 甚至几天后。当此行为不可取时,您可以阻止 通过将 Bean 定义标记为 lazy-initialized 初始化的。延迟初始化的 bean 告诉 IoC 容器创建一个 bean instance,而不是在启动时。ApplicationContextspring-doc.cn

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

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

当前面的配置被 、 bean 使用 在启动时不会预先实例化, 而 bean 是预先实例化的。ApplicationContextlazyApplicationContextnot.lazyspring-doc.cn

但是,当延迟初始化的 bean 是单例 bean 的依赖项时,即 不是 lazy-initialized,则在 startup,因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到其他位置未进行延迟初始化的单例 bean 中。ApplicationContextspring-doc.cn

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

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

1.4.5. 自动装配协作者

Spring 容器可以自动连接协作 bean 之间的关系。您可以 让 Spring 自动解析 bean 的协作者(其他 bean) 正在检查 .Autowiring 具有以下功能 优势:ApplicationContextspring-doc.cn

  • 自动装配可以显著减少指定属性或构造函数的需要 参数。(其他机制(例如本章其他地方讨论的 bean 模板)也很有价值 在这方面。spring-doc.cn

  • Autowiring 可以随着对象的发展而更新配置。例如,如果您需要 要向类添加依赖项,则可以自动满足该依赖项,而无需 您需要修改配置。因此,自动装配可能特别有用 在开发过程中,在以下情况下不否定切换到显式布线的选项 代码库变得更加稳定。spring-doc.cn

当使用基于 XML 的配置元数据时(参见 依赖关系注入),您可以 可以使用 Element 的属性为 bean 定义指定 autowire 模式。自动装配功能有四种模式。您指定自动装配 每个 bean,因此可以选择哪些 bean 进行 autowire。下表描述了 四种自动装配模式:autowire<bean/>spring-doc.cn

表 2.自动装配模式
模式 解释

nospring-doc.cn

(默认)没有自动接线。Bean 引用必须由元素定义。改变 对于较大的部署,不建议使用默认设置,因为指定 collaborators 明确提供了更大的控制权和清晰度。在某种程度上,它 记录系统的结构。refspring-doc.cn

byNamespring-doc.cn

按属性名称自动装配。Spring 查找与 需要自动装配的属性。例如,如果将 Bean 定义设置为 autowire,并且它包含一个属性(即,它有一个方法),Spring 会查找一个名为 bean 的定义,并使用 it 来设置属性。mastersetMaster(..)masterspring-doc.cn

byTypespring-doc.cn

如果 中正好存在一个属性类型的 bean,则允许自动连接属性 容器。如果存在多个异常,则会引发致命异常,该异常指示 你不能对那个 bean 使用 autowired。如果没有匹配项 beans 时,什么都不会发生(属性未设置)。byTypespring-doc.cn

constructorspring-doc.cn

类似于构造函数参数,但适用于构造函数参数。如果没有 容器中 constructor 参数类型的一个 bean,则会引发致命错误。byTypespring-doc.cn

使用 或 autowiring 模式,您可以连接数组和 typed collections 的集合。在这种情况下,容器内所有 match 预期的类型来满足依赖项。您可以自动装配 强类型实例(如果预期的键类型为 .自动装配实例的值由与预期类型匹配的所有 bean 实例组成,并且实例的键包含相应的 bean 名称。byTypeconstructorMapStringMapMapspring-doc.cn

自动装配的局限性和缺点

当 Autowiring 在整个项目中一致地使用时,它的效果最佳。如果 autowiring 是 通常不使用,则开发人员可能会混淆仅使用它来连接一个 或 两个 bean 定义。spring-doc.cn

考虑自动装配的限制和缺点:spring-doc.cn

  • 和 settings 中的显式依赖项始终覆盖 autowiring 的您不能自动装配简单属性,例如 primitives, , 和 (以及此类简单属性的数组)。此限制为 设计使然。propertyconstructor-argStringsClassesspring-doc.cn

  • 自动装配不如显式装配精确。虽然,如前面的表格所示, Spring 会小心避免猜测,以防出现可能意想不到的歧义 结果。Spring 管理的对象之间的关系不再是 记录下来。spring-doc.cn

  • 连接信息可能不可用于可能从 一个 Spring 容器。spring-doc.cn

  • 容器中的多个 bean 定义可能与 setter 方法或构造函数参数进行自动装配。对于数组、集合或实例,这不一定是问题。但是,对于 expect 单个值,则此歧义不会任意解决。如果没有唯一的 bean 定义可用,则会引发异常。Mapspring-doc.cn

在后一种情况下,您有以下几种选择:spring-doc.cn

  • 放弃自动装配,转而使用显式布线。spring-doc.cn

  • 通过设置 Bean 定义的属性来避免自动装配 Bean 定义 更改为 ,如下一节所述。autowire-candidatefalsespring-doc.cn

  • 通过将单个 bean 定义的元素属性设置为 来指定单个 bean 定义作为主要候选者。primary<bean/>truespring-doc.cn

  • 通过基于 Comments 的配置实现更精细的控制, 如基于注释的容器配置中所述。spring-doc.cn

从 Autowiring 中排除 Bean

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

该属性旨在仅影响基于类型的自动装配。 它不会影响按名称的显式引用,即使 指定的 Bean 未标记为 autowire 候选项。因此,自动装配 尽管如此,如果名称匹配,则 by name 会注入一个 bean。autowire-candidate

您还可以根据与 Bean 名称的模式匹配来限制自动装配候选项。这 top-level 元素接受其 attribute 中的一个或多个模式。例如,要限制 autowire 候选状态 对于名称以 结尾的任何 bean,请提供值 。自 提供多个模式,在逗号分隔的列表中定义它们。bean 定义属性的显式值 or 始终采用 优先。对于此类 bean,模式匹配规则不适用。<beans/>default-autowire-candidatesRepository*Repositorytruefalseautowire-candidatespring-doc.cn

这些技术对于您永远不想注入到其他 bean 中的 bean 非常有用 bean 的 bean 中。这并不意味着被排除的 bean 本身不能由 使用自动装配。相反,bean 本身不是自动装配其他 bean 的候选者。spring-doc.cn

1.4.6. 方法注入

在大多数应用程序场景中,容器中的大多数 bean 都是单例的。当单例 bean 需要 与另一个单例 bean 协作或非单例 bean 需要协作 对于另一个非单例 bean,通常通过定义一个 bean 作为另一个的属性。当 bean 生命周期为 不同。假设单例 bean A 需要使用非单例(原型)bean B, 可能在 A 上的每个方法调用时。容器仅创建单例 bean A 一次,因此只有一次设置属性的机会。容器不能 每次需要 bean B 时,都会向 bean A 提供一个新的 bean B 实例。spring-doc.cn

一个解决方案是放弃一些倒置的控制。您可以使 bean A 通过实现接口 并通过对容器进行 getBean(“B”) 调用来请求 (a 通常是 new) bean B 实例。以下示例 显示了这种方法:ApplicationContextAwarespring-doc.cn

Java
// 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;
    }
}
Kotlin
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple

// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

class CommandManager : ApplicationContextAware {

    private lateinit var applicationContext: ApplicationContext

    fun process(commandState: Map<*, *>): Any {
        // grab a new instance of the appropriate Command
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // notice the Spring API dependency!
    protected fun createCommand() =
            applicationContext.getBean("command", Command::class.java)

    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
    }
}

前面的内容是不可取的,因为业务代码知道并耦合到 Spring 框架。方法注入,Spring IoC 的一个有点高级的功能 容器,让您能够干净利落地处理此用例。spring-doc.cn

您可以在此博客文章中阅读有关 Method Injection 动机的更多信息。spring-doc.cn

查找方法注入

Lookup 方法注入是容器覆盖 container-managed bean 中,并返回 容器。查找通常涉及原型 Bean,如所描述的场景所示 在上一节中。Spring 框架 通过使用 CGLIB 库中的字节码生成来实现此方法注入 动态生成覆盖该方法的 subclass。spring-doc.cn

  • 要使这个动态子类化工作,Spring bean 容器 子类不能是 ,要覆盖的方法也不能是 。finalfinalspring-doc.cn

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

  • 对于组件扫描来说,具体方法也是必要的,这需要具体的 课程。spring-doc.cn

  • 另一个关键限制是 lookup 方法不能与工厂方法一起使用,并且 特别是对于配置类中的方法,因为在这种情况下, 容器不负责创建实例,因此无法创建 运行时生成的动态子类。@Beanspring-doc.cn

对于上一个代码片段中的类, Spring 容器动态覆盖该方法的实现。该类没有任何 Spring 依赖项,因为 重新设计的示例显示:CommandManagercreateCommand()CommandManagerspring-doc.cn

Java
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();
}
Kotlin
package fiona.apple

// no more Spring imports!

abstract class CommandManager {

    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // okay... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}

在包含要注入的方法的客户端类中(在此 case),则要注入的方法需要以下形式的签名:CommandManagerspring-doc.cn

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

如果方法是 ,则动态生成的子类实现该方法。 否则,动态生成的子类将覆盖 原始类。请考虑以下示例:abstractspring-doc.cn

<!-- 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 调用其自己的方法 每当它需要 bean 的新实例时。您必须小心部署 如果这确实是需要的,那么 bean 作为原型。如果是 一个 singleton,则每次都返回相同的 bean 实例。commandManagercreateCommand()myCommandmyCommandmyCommandspring-doc.cn

或者,在基于注释的组件模型中,您可以声明一个查找 方法,如下例所示:@Lookupspring-doc.cn

Java
public abstract class CommandManager {

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

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup("myCommand")
    protected abstract fun createCommand(): Command
}

或者,更地说,您可以依靠目标 Bean 与 Lookup 方法的 declared return 类型:spring-doc.cn

Java
public abstract class CommandManager {

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

    @Lookup
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup
    protected abstract fun createCommand(): Command
}

请注意,您通常应该使用具体的 stub 实现,以便它们与 Spring 的组件兼容 默认情况下忽略抽象类的扫描规则。此限制不会 应用于显式注册或显式导入的 Bean 类。spring-doc.cn

访问不同范围的目标 bean 的另一种方法是 / 注入点。请参见将范围限定的 Bean 作为依赖项ObjectFactoryProviderspring-doc.cn

您可能还会发现 (在包中) 很有用。ServiceLocatorFactoryBeanorg.springframework.beans.factory.configspring-doc.cn

任意方法替换

与查找方法注入相比,方法注入的一种不太有用的形式是能够 将托管 Bean 中的任意方法替换为另一个方法实现。你 可以安全地跳过本节的其余部分,直到您真正需要此功能。spring-doc.cn

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

Java
public class MyValueCalculator {

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

    // some other methods...
}
Kotlin
class MyValueCalculator {

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

    // some other methods...
}

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

Java
/**
 * 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 ...;
    }
}
Kotlin
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
class ReplacementComputeValue : MethodReplacer {

    override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
        // get the input value, work with it, and return a computed result
        val input = args[0] as String;
        ...
        return ...;
    }
}

用于部署原始类并指定方法覆盖的 bean 定义将 类似于以下示例:spring-doc.cn

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

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

java.lang.String
String
Str

因为参数的数量通常足以区分每种可能的 选项,此快捷方式可以节省大量键入,因为允许您只键入 与参数类型匹配的最短字符串。spring-doc.cn

1.5. Bean 作用域

创建 Bean 定义时,将创建用于创建实际实例的配方 由该 Bean 定义定义的类。bean 定义是 recipe 很重要,因为它意味着,与 class 一样,您可以创建多个对象 实例。spring-doc.cn

您不仅可以控制各种依赖项和配置值,这些依赖项和配置值 插入到从特定 bean 定义创建的对象中,但也插入到控件 从特定 Bean 定义创建的对象的范围。这种方法是 功能强大且灵活,因为您可以选择所创建对象的范围 通过配置,而不必在 Java 类级别。可以将 Bean 定义为部署在多个范围之一中。 Spring 框架支持六个范围,其中四个范围仅在 您使用 Web 感知 .您还可以创建自定义范围。ApplicationContextspring-doc.cn

下表描述了支持的范围:spring-doc.cn

表 3.Bean 作用域
范围 描述

单身 人士spring-doc.cn

(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。spring-doc.cn

原型spring-doc.cn

将单个 Bean 定义的范围限定为任意数量的对象实例。spring-doc.cn

请求spring-doc.cn

将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是 每个 HTTP 请求都有自己的 bean 实例,该实例是从单个 bean 的背面创建的 定义。仅在 web-aware Spring 的上下文中有效。ApplicationContextspring-doc.cn

会期spring-doc.cn

将单个 bean 定义的范围限定为 HTTP 的生命周期。仅在 Web 感知 Spring 的上下文。SessionApplicationContextspring-doc.cn

应用spring-doc.cn

将单个 Bean 定义的范围限定为 .仅在 Web 感知 Spring 的上下文。ServletContextApplicationContextspring-doc.cn

WebSocket 浏览器spring-doc.cn

将单个 Bean 定义的范围限定为 .仅在 Web 感知 Spring 的上下文。WebSocketApplicationContextspring-doc.cn

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

1.5.1. 单例范围

仅管理单例 bean 的一个共享实例,以及 bean 的所有请求 替换为与该 Bean 定义的一个或多个 ID 匹配,则生成一个特定的 Bean 实例。spring-doc.cn

换句话说,当你定义一个 bean 定义并且它被定义为 singleton 时,Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个 single 实例存储在 singleton bean 以及该命名 bean 的所有后续请求和引用 返回缓存的对象。下图显示了单一实例范围的工作原理:spring-doc.cn

单身 人士

Spring 的单例 bean 概念不同于 Gang of Four (GoF) 模式书。GoF 单例对 Object 中,以便每个 ClassLoader 的Spring 单例的范围最好描述为每个容器 和每 bean 的 bean 中。这意味着,如果您在 单个 Spring 容器,则 Spring 容器将创建一个且仅创建一个实例 由该 Bean 定义定义的类。singleton scope 是默认范围 在Spring。要在 XML 中将 bean 定义为单例,可以定义一个 bean,如 以下示例:spring-doc.cn

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

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

1.5.2. 原型范围

bean 部署的非单例原型范围会导致创建一个新的 bean 实例。即 bean 被注入到另一个 bean 中,或者您通过在 容器。通常,您应该对所有有状态 bean 使用 prototype 范围,并且 singleton 作用域。getBean()spring-doc.cn

下图说明了 Spring 原型范围:spring-doc.cn

原型

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有 任何对话状态。我们更容易重用 单例图。spring-doc.cn

下面的示例将 Bean 定义为 XML 中的原型:spring-doc.cn

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

与其他范围相比, Spring 不管理 prototype bean 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 Bean 后处理器,它包含对 需要清理的豆子。spring-doc.cn

在某些方面, Spring 容器在原型范围的 bean 中的作用是 替换 Java 运算符。超过该点的所有生命周期管理都必须 由客户端处理。(有关 Spring 中 bean 生命周期的详细信息 容器,请参阅 生命周期回调newspring-doc.cn

1.5.3. 具有原型 bean 依赖项的单例 bean

当您使用依赖于原型 bean 的单例作用域 bean 时,请注意 依赖项在实例化时解析。因此,如果你依赖注入一个 原型范围的 bean 转换为单例范围的 bean,则会实例化一个新的原型 bean 然后 dependency-injection 到单例 bean 中。prototype 实例是唯一的 实例。spring-doc.cn

但是,假设您希望单例范围的 bean 获取 prototype 范围的 bean。您不能依赖注入 prototype 作用域的 bean 添加到你的单例 bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 bean 并解析 并注入其依赖项。如果您需要原型 bean 的新实例,网址为 运行时,请参阅 Method Injectionspring-doc.cn

1.5.4. 请求、会话、应用程序和 WebSocket 作用域

、 、 和 范围 仅可用 如果使用 Web 感知的 Spring 实现(例如 )。如果你将这些作用域与常规的 Spring IoC 容器一起使用, 比如 , an 抱怨 关于未知的 bean 范围。requestsessionapplicationwebsocketApplicationContextXmlWebApplicationContextClassPathXmlApplicationContextIllegalStateExceptionspring-doc.cn

初始 Web 配置

为了支持在 、 和 级别(Web 范围的 bean)界定 bean 的范围,一些次要的初始配置是 在定义 bean 之前是必需的。(此初始设置不是必需的 对于标准范围:和 。requestsessionapplicationwebsocketsingletonprototypespring-doc.cn

如何完成此初始设置取决于特定的 Servlet 环境。spring-doc.cn

如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在 由 Spring 处理,无需特殊设置。 already 公开所有相关状态。DispatcherServletDispatcherServletspring-doc.cn

如果使用 Servlet 2.5 Web 容器,并且请求在 Spring 之外处理(例如,使用 JSF 或 Struts 时),则需要注册 . 对于 Servlet 3.0+,这可以通过使用接口以编程方式完成。或者,或者对于较旧的容器,将以下声明添加到 您的 Web 应用程序的文件:DispatcherServletorg.springframework.web.context.request.RequestContextListenerServletRequestListenerWebApplicationInitializerweb.xmlspring-doc.cn

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

或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的 .过滤器映射取决于周围的 Web application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单 显示了 Web 应用程序的 Filter 部分:RequestContextFilterspring-doc.cn

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

DispatcherServlet、 和 all 都完全执行 同样的事情,即将 HTTP 请求对象绑定到正在服务的 那个请求。这使得请求和会话范围的 bean 进一步可用 沿着调用链向下。RequestContextListenerRequestContextFilterThreadspring-doc.cn

请求范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

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

Spring 容器通过对每个 HTTP 请求使用 bean 定义来创建 bean 的新实例。也就是说,Bean 的范围限定在 HTTP 请求级别。您可以将内部的 state (状态),因为其他实例 从同一 bean 定义创建的 state 中看不到这些更改。 它们特定于单个请求。当请求完成处理时, 范围限定为请求的 bean 将被丢弃。LoginActionloginActionloginActionloginActionspring-doc.cn

当使用注解驱动的组件或 Java 配置时,注解 可用于将组件分配给范围。以下示例显示了如何操作 为此,请执行以下操作:@RequestScoperequestspring-doc.cn

Java
@RequestScope
@Component
public class LoginAction {
    // ...
}
Kotlin
@RequestScope
@Component
class LoginAction {
    // ...
}
会话范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

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

Spring 容器在单个 HTTP 的生命周期中使用 bean 定义来创建 bean 的新实例。在其他 words,则 bean 的作用域实际上是在 HTTP 级别。如 使用请求范围的 bean,您可以更改实例的内部状态,即 根据需要创建尽可能多的 HTTP 实例,要知道其他 HTTP 实例也是 使用从同一 bean 定义创建的实例不会看到这些 状态更改,因为它们特定于单个 HTTP 。当 HTTP 最终被丢弃,范围限定为该特定 HTTP 的 bean 也被丢弃。UserPreferencesuserPreferencesSessionuserPreferencesSessionSessionuserPreferencesSessionSessionSessionspring-doc.cn

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。@SessionScopesessionspring-doc.cn

Java
@SessionScope
@Component
public class UserPreferences {
    // ...
}
Kotlin
@SessionScope
@Component
class UserPreferences {
    // ...
}
应用范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

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

Spring 容器通过对整个 Web 应用程序使用一次 Bean 定义来创建 Bean 的新实例。也就是说,Bean 的范围限定在 level 上,并存储为 regular 属性。这有点类似于 Spring 单例 bean,但 在两个重要方面有所不同:它是 per , 而不是 per Spring 的单例(在任何给定的 Web 应用程序中都可能有多个), 它实际上是公开的,因此作为一个属性可见。AppPreferencesappPreferencesappPreferencesServletContextServletContextServletContextApplicationContextServletContextspring-doc.cn

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。这 以下示例显示了如何执行此操作:@ApplicationScopeapplicationspring-doc.cn

Java
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
Kotlin
@ApplicationScope
@Component
class AppPreferences {
    // ...
}
作为依赖项的作用域 Bean

Spring IoC 容器不仅管理对象 (bean) 的实例化, 但也包括合作者(或依赖项)的联系。如果要注入 (对于 example) 将一个 HTTP 请求范围的 bean 转换为另一个寿命较长的 bean 中,您可以 选择注入 AOP 代理来代替作用域 Bean。也就是说,您需要注入 一个代理对象,它公开与 Scoped 对象相同的公共接口,但可以 还可以从相关范围(如 HTTP 请求)中检索真正的目标对象 并将方法调用委托到真实对象上。spring-doc.cn

您还可以在范围为 、 然后,引用通过可序列化的中间代理 因此能够在反序列化时重新获取目标 singleton bean。<aop:scoped-proxy/>singletonspring-doc.cn

当针对 scope 的 bean 进行声明时,每个方法 调用会导致创建一个新的目标实例,该实例的 然后,呼叫将被转发。<aop:scoped-proxy/>prototypespring-doc.cn

此外,作用域代理并不是从较短的作用域访问 bean 的唯一方法 生命周期安全时尚。您还可以声明注入点(即 constructor 或 setter 参数或 autowired 字段)设置为 , 允许调用以按需检索当前实例 时间 — 无需保留实例或单独存储实例。ObjectFactory<MyTargetBean>getObject()spring-doc.cn

作为扩展变体,您可以声明 which deliver 其他几种访问变体,包括 和 .ObjectProvider<MyTargetBean>getIfAvailablegetIfUniquespring-doc.cn

此的 JSR-330 变体被调用,并与声明和每次检索尝试的相应调用一起使用。 有关 JSR-330 的更多详细信息,请参阅此处ProviderProvider<MyTargetBean>get()spring-doc.cn

以下示例中的配置只有一行,但对于 了解其背后的 “为什么” 和 “如何”:spring-doc.cn

<?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 定义代理的行。

要创建这样的代理,请将子元素插入到作用域 Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。 为什么 bean 的定义范围在 、 和 custom-scope 级别需要元素吗? 考虑以下单例 bean 定义,并将其与 您需要为上述范围定义什么(请注意,以下 bean 定义并不完整):<aop:scoped-proxy/>requestsession<aop:scoped-proxy/>userPreferencesspring-doc.cn

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

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

在前面的示例中,单例 bean () 注入了一个引用 添加到 HTTP 范围的 Bean () 中。这里的要点是 bean 是一个单例:它每个 container 及其依赖项(在本例中只有一个 bean)是 也只注射了一次。这意味着 Bean 仅在 完全相同的对象(即最初注入的对象)。userManagerSessionuserPreferencesuserManageruserPreferencesuserManageruserPreferencesspring-doc.cn

当将生存期较短的作用域 bean 注入 生存期较长的作用域 bean(例如,注入 HTTP 作用域的协作 bean 作为单例 bean 的依赖项)。相反,您需要一个对象,并且在 HTTP 的生命周期中,您需要一个对象 特定于 HTTP .因此,容器会创建一个对象,该对象 公开与类完全相同的公共接口(理想情况下是 对象,它是一个实例),它可以从范围机制(HTTP 请求、 等 forth)。容器将此代理对象注入到 bean 中,即 不知道此引用是代理。在此示例中,当实例在依赖项注入的对象上调用方法时,它实际上是在代理上调用方法。然后,代理从 HTTP(在本例中)获取真实对象,并将 方法调用到检索到的 Real Object 上。SessionuserManagerSessionuserPreferencesSessionUserPreferencesUserPreferencesUserPreferencesSessionuserManagerUserPreferencesUserManagerUserPreferencesUserPreferencesSessionUserPreferencesspring-doc.cn

因此,在将 bean 注入协作对象时,您需要以下(正确和完整的)配置,如下例所示 显示:request-session-scopedspring-doc.cn

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

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

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

CGLIB 代理仅拦截公共方法调用!不要调用非公共方法 在这样的代理上。它们不会委托给实际的 Scoped 目标对象。spring-doc.cn

或者,您可以将 Spring 容器配置为创建标准 JDK 基于接口的代理,通过为 元素的属性。使用 JDK 基于接口的代理意味着您的 application classpath 来影响此类代理。但是,这也意味着 作用域 Bean 必须至少实现一个接口,并且所有协作者 作用域 bean 注入到其中,必须通过其 接口。以下示例显示了基于接口的代理:falseproxy-target-class<aop:scoped-proxy/>spring-doc.cn

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

有关选择基于类或基于接口的代理的更多详细信息, 请参见代理机制spring-doc.cn

1.5.5. 自定义范围

Bean 范围机制是可扩展的。您可以定义自己的 范围,甚至重新定义现有范围,尽管后者被认为是不好的做法 并且你不能覆盖 built-in 和 scopes。singletonprototypespring-doc.cn

创建自定义范围

要将自定义范围集成到 Spring 容器中,您需要实现接口,如下所述 部分。有关如何实现自己的范围的想法,请参阅 Spring 框架本身提供的实现和 Scope javadoc。 其中更详细地解释了您需要实现的方法。org.springframework.beans.factory.config.ScopeScopespring-doc.cn

该接口有四种方法从 scope 中获取对象,从 范围,然后销毁它们。Scopespring-doc.cn

例如,会话范围的实现返回会话范围的 Bean(如果它 不存在,则该方法在将 Bean 绑定到 会话以供将来参考)。以下方法从 底层范围:spring-doc.cn

Java
Object get(String name, ObjectFactory<?> objectFactory)
Kotlin
fun get(name: String, objectFactory: ObjectFactory<*>): Any

例如,会话范围实现从 基础会话。应该返回该对象,但如果 找不到具有指定名称的对象。以下方法从 底层范围:nullspring-doc.cn

Java
Object remove(String name)
Kotlin
fun remove(name: String): Any

以下方法注册一个回调,范围应在 destroyed 或当 scope 中的指定对象被销毁时:spring-doc.cn

Java
void registerDestructionCallback(String name, Runnable destructionCallback)
Kotlin
fun registerDestructionCallback(name: String, destructionCallback: Runnable)

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

以下方法获取基础范围的聊天标识符:spring-doc.cn

Java
String getConversationId()
Kotlin
fun getConversationId(): String

此标识符对于每个范围都不同。对于会话范围的实现,此 identifier 可以是会话标识符。spring-doc.cn

使用自定义范围

在编写和测试一个或多个自定义实现后,您需要将 Spring 容器知道您的新范围。以下方法是中心 向 Spring 容器注册 new 的方法:ScopeScopespring-doc.cn

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

此方法在接口上声明,该接口可用 通过 Spring 附带的大多数具体实现上的属性。ConfigurableBeanFactoryBeanFactoryApplicationContextspring-doc.cn

该方法的第一个参数是与 一个范围。Spring 容器本身中此类名称的示例是 和 。该方法的第二个参数是实际实例 。registerScope(..)singletonprototyperegisterScope(..)Scopespring-doc.cn

假设您编写了自定义实现,然后按所示注册它 在下一个示例中。Scopespring-doc.cn

下一个示例使用 Spring 中包含的 ,但不是 registered (默认)。对于您自己的自定义实现,说明是相同的。SimpleThreadScopeScope
Java
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
Kotlin
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)

然后,您可以创建符合 custom 的 scopeping rules 的 bean 定义,如下所示:Scopespring-doc.cn

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

使用自定义实现,您不仅限于编程注册 的范围。您还可以使用类以声明方式进行注册,如下例所示:ScopeScopeCustomScopeConfigurerspring-doc.cn

<?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>
当你放置在实现的声明中时,确定范围的是工厂 Bean 本身,而不是对象 从 返回。<aop:scoped-proxy/><bean>FactoryBeangetObject()

1.6. 自定义 bean 的性质

Spring Framework 提供了许多接口,您可以使用这些接口来自定义 豆子。本节将它们分组如下:spring-doc.cn

1.6.1. 生命周期回调

要与容器对 bean 生命周期的 Management 进行交互,您可以实现 Spring 和接口。容器调用前者,而后者则让 bean 在初始化和销毁 bean 时执行某些操作。InitializingBeanDisposableBeanafterPropertiesSet()destroy()spring-doc.cn

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

如果您不想使用 JSR-250 注解,但仍希望删除 coupling、consider 和 bean 定义元数据。init-methoddestroy-methodspring-doc.cn

在内部, Spring 框架使用 implementation 来处理任何 callback 接口,它可以找到并调用适当的方法。如果您需要自定义 功能或其他生命周期行为 Spring 默认不提供,您可以 实施 YOURSELF。有关更多信息,请参阅容器扩展点BeanPostProcessorBeanPostProcessorspring-doc.cn

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

生命周期回调接口将在 此部分 中介绍。spring-doc.cn

初始化回调

该接口允许 bean 在容器在 豆。该接口指定单个方法:org.springframework.beans.factory.InitializingBeanInitializingBeanspring-doc.cn

void afterPropertiesSet() throws Exception;

我们建议您不要使用该接口,因为它 不必要地将代码耦合到 Spring。或者,我们建议使用 @PostConstruct 注解或 指定 POJO 初始化方法。对于基于 XML 的配置元数据, 您可以使用该属性指定具有 void 的方法的名称 no-argument 签名。对于 Java 配置,您可以使用 .请参阅 接收生命周期回调。请考虑以下示例:InitializingBeaninit-methodinitMethod@Beanspring-doc.cn

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

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

    fun init() {
        // do some initialization work
    }
}

前面的示例与以下示例的效果几乎完全相同 (由两个列表组成):spring-doc.cn

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

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
Kotlin
class AnotherExampleBean : InitializingBean {

    override fun afterPropertiesSet() {
        // do some initialization work
    }
}

但是,前面两个示例中的第一个示例并没有将代码耦合到 Spring。spring-doc.cn

销毁回调

实现该接口后,可以让 bean 在包含它的容器被销毁时获取回调。该接口指定单个方法:org.springframework.beans.factory.DisposableBeanDisposableBeanspring-doc.cn

void destroy() throws Exception;

我们建议您不要使用回调接口,因为它 不必要地将代码耦合到 Spring。或者,我们建议使用 @PreDestroy 注解或 指定 Bean 定义支持的泛型方法。使用基于 XML 的 configuration 元数据中,您可以使用 . 对于 Java 配置,您可以使用 .请参阅 接收生命周期回调。请考虑以下定义:DisposableBeandestroy-method<bean/>destroyMethod@Beanspring-doc.cn

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

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

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

前面的定义与下面的定义几乎完全相同:spring-doc.cn

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

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
Kotlin
class AnotherExampleBean : DisposableBean {

    override fun destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

但是,上述两个定义中的第一个并没有将代码耦合到 Spring。spring-doc.cn

你可以为元素的属性分配一个特殊值,该值指示 Spring 自动检测特定 bean 类上的 public 或 method。(实现或因此匹配的任何类。您还可以设置 this 特殊值应用于此行为,以将此行为应用于整个 bean 集(请参见默认初始化和销毁方法)。请注意,这是 default behavior 与 Java 配置一起使用。destroy-method<bean>(inferred)closeshutdownjava.lang.AutoCloseablejava.io.Closeable(inferred)default-destroy-method<beans>
默认 Initialization 和 Destroy 方法

当您编写不使用 特定于 Spring 的接口和回调接口,您 通常编写名称为 、 等 上。理想情况下,此类生命周期回调方法的名称在 project 中,以便所有开发人员都使用相同的方法名称并确保一致性。InitializingBeanDisposableBeaninit()initialize()dispose()spring-doc.cn

你可以将 Spring 容器配置为“查找”命名初始化和销毁 回调方法名称。这意味着您作为应用程序 开发人员可以编写应用程序类并使用名为 的初始化回调,而不必为每个 bean 配置属性 定义。Spring IoC 容器在创建 Bean 时调用该方法(在 根据前面描述的标准生命周期回调协定)。此功能还对 initialization 和 destroy 方法回调。init()init-method="init"spring-doc.cn

假设您的初始化回调方法已命名,并且您的 destroy 回调方法命名为 。然后,您的类类似于 以下示例:init()destroy()spring-doc.cn

Java
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.");
        }
    }
}
Kotlin
class DefaultBlogService : BlogService {

    private var blogDao: BlogDao? = null

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

然后,您可以在类似于以下内容的 bean 中使用该类:spring-doc.cn

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

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

</beans>

顶级元素上是否存在该属性 属性使 Spring IoC 容器识别在 Bean 上调用的方法 class 作为初始化方法回调。在创建和组装 bean 时,如果 Bean 类具有这样一个方法,则会在适当的时间调用它。default-init-method<beans/>initspring-doc.cn

您可以通过使用 top-level 元素上的属性以类似方式(即在 XML 中)配置 destroy 方法回调。default-destroy-method<beans/>spring-doc.cn

现有 Bean 类已经具有以 Variance 命名的回调方法 使用约定,您可以通过指定(在 XML 中,即)来覆盖默认值 method name 来使用本身的 and 属性。init-methoddestroy-method<bean/>spring-doc.cn

Spring 容器保证调用配置的初始化回调 紧接着为 bean 提供所有依赖项。因此,初始化 callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等 forth 尚未应用于 Bean。首先完全创建目标 Bean,然后 然后应用 AOP 代理(例如)及其拦截器链。如果目标 bean 和 proxy 是单独定义的,你的代码甚至可以与原始的 target bean,绕过代理。因此,应用 interceptor 添加到该方法中,因为这样做会耦合 将 bean 绑定到其代理或拦截器,并在您的代码 直接与原始目标 Bean 交互。initspring-doc.cn

组合生命周期机制

从 Spring 2.5 开始,您有三个选项来控制 bean 生命周期行为:spring-doc.cn

如果为 Bean 配置了多个生命周期机制,并且每个机制都是 配置了不同的方法名称,则每个配置的方法都会在 在此说明之后列出的顺序。但是,如果为这些生命周期机制中的多个配置了相同的方法名称(例如,对于初始化方法),则 该方法运行一次,如上一节所述。init()

为同一 bean 配置了多个生命周期机制,具有不同的 初始化方法的调用方式如下:spring-doc.cn

  1. 注释有@PostConstructspring-doc.cn

  2. afterPropertiesSet()由回调接口定义InitializingBeanspring-doc.cn

  3. 自定义配置的方法init()spring-doc.cn

Destroy 方法的调用顺序相同:spring-doc.cn

  1. 注释有@PreDestroyspring-doc.cn

  2. destroy()由回调接口定义DisposableBeanspring-doc.cn

  3. 自定义配置的方法destroy()spring-doc.cn

启动和关闭回调

该接口为任何具有自己的 生命周期要求(例如启动和停止某些后台进程):Lifecyclespring-doc.cn

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 Spring Management 的对象都可以实现该接口。然后,当 本身收到启动和停止信号(例如,对于停止/重启 scenario 的 intent 中),它会将这些调用级联到所有实现 在该上下文中定义。它通过委托给 , 所示 在下面的清单中:LifecycleApplicationContextLifecycleLifecycleProcessorspring-doc.cn

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

请注意,它本身是接口的扩展。它还添加了另外两种方法来响应正在刷新的上下文 并关闭。LifecycleProcessorLifecyclespring-doc.cn

请注意,常规接口是普通的 显式启动和停止通知的协定,并不意味着在上下文中自动启动 刷新时间。为了对特定 bean 的自动启动(包括启动阶段)进行细粒度控制, 请考虑改为实施。org.springframework.context.Lifecycleorg.springframework.context.SmartLifecyclespring-doc.cn

另外,请注意,不保证在销毁之前收到停止通知。 在常规关闭时,所有 bean 首先收到停止通知 正在传播常规销毁回调。但是,在 context 的生命周期或停止的刷新尝试时,仅调用 destroy 方法。Lifecyclespring-doc.cn

启动和关闭调用的顺序可能很重要。如果 “depends-on” 关系存在于任意两个对象之间,则依赖端在其 依赖项,并且它在依赖项之前停止。然而,有时,直接的 依赖项是未知的。您可能只知道特定类型的对象应该启动 在其他类型的对象之前。在这些情况下,接口定义 另一个选项,即在其超级接口 .下面的清单显示了接口的定义:SmartLifecyclegetPhase()PhasedPhasedspring-doc.cn

public interface Phased {

    int getPhase();
}

下面的清单显示了接口的定义:SmartLifecyclespring-doc.cn

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

开始时,阶段最低的对象首先启动。停止时, 遵循 Reverse Order。因此,实现 和 其方法 returns 将是第一个开始的 也是最后一个停下来的。在频谱的另一端,相位值 表示对象应最后启动并停止 first (可能是因为它依赖于其他进程运行)。在考虑 phase 值,同样重要的是要知道,任何未实现的 “normal” 对象的默认 phase 是 。因此,任何 负相位值表示对象应在这些标准之前开始 组件(并在它们之后停止)。对于任何正相位值,情况正好相反。SmartLifecyclegetPhase()Integer.MIN_VALUEInteger.MAX_VALUELifecycleSmartLifecycle0spring-doc.cn

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

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

如前所述,该接口为 刷新和关闭上下文。后者驱动关闭 进程,就好像已经被显式调用一样,但它发生在上下文为 关闭。另一方面,'refresh' 回调启用了 bean 的另一个功能。刷新上下文时(在所有对象都已 instantiated 和 initialized),则会调用该回调。此时, default lifecycle processor 检查每个对象的方法返回的布尔值。如果 ,则该对象为 启动,而不是等待显式调用上下文的 OR 它自己的方法(与上下文刷新不同,上下文启动不会发生 automatically 用于标准上下文实现)。值和任何 “depends-on” 关系确定 Startup Sequence,如前所述。LifecycleProcessorstop()SmartLifecycleSmartLifecycleisAutoStartup()truestart()phasespring-doc.cn

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

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

如果你在非 Web 应用程序环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),使用 JVM 的 JVM 中。这样做可以确保正常关闭,并在 singleton bean 的实例,以便释放所有资源。您仍必须配置 并正确实现这些 destroy 回调。spring-doc.cn

要注册 shutdown 钩子,请调用 在接口上声明,如下例所示:registerShutdownHook()ConfigurableApplicationContextspring-doc.cn

Java
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...
    }
}
Kotlin
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = 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. 和ApplicationContextAwareBeanNameAware

当 an 创建实现该接口的对象实例时,将提供该实例 并引用该 .下面的清单显示了定义 的界面中:ApplicationContextorg.springframework.context.ApplicationContextAwareApplicationContextApplicationContextAwarespring-doc.cn

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

自动装配是获取对 .传统模式和自动装配模式 (如 Autowiring Collaborators 中所述)可以为构造函数参数或 setter 方法参数提供 type 的依赖项, 分别。为了提高灵活性,包括自动装配字段和 多个参数方法,使用基于注释的自动装配功能。如果这样做, 自动连接到字段、构造函数参数或方法 参数,如果字段、构造函数或 有问题的方法带有注释。有关更多信息,请参阅使用 @AutowiredApplicationContextconstructorbyTypeApplicationContextApplicationContextApplicationContext@Autowiredspring-doc.cn

当 an 创建实现该接口的类时,该类会附带 对其关联对象定义中定义的名称的引用。以下清单 显示了 BeanNameAware 接口的定义:ApplicationContextorg.springframework.beans.factory.BeanNameAwarespring-doc.cn

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

该回调在填充普通 bean 属性之后但在 初始化回调,例如 OR 自定义 init-method 的InitializingBean.afterPropertiesSet()spring-doc.cn

1.6.3. 其他接口Aware

除了 and (前面讨论过), Spring 提供了广泛的回调接口,让 bean 向容器指示 它们需要一定的基础设施依赖性。作为一般规则,该名称表示 dependency 类型。下表总结了最重要的接口:ApplicationContextAwareBeanNameAwareAwareAwarespring-doc.cn

表 4.感知接口
名字 注入的依赖项 解释于...

ApplicationContextAwarespring-doc.cn

声明。ApplicationContextspring-doc.cn

ApplicationContextAwareBeanNameAwarespring-doc.cn

ApplicationEventPublisherAwarespring-doc.cn

封闭 的事件发布者 .ApplicationContextspring-doc.cn

ApplicationContext 的其他功能spring-doc.cn

BeanClassLoaderAwarespring-doc.cn

用于加载 bean 类的类加载器。spring-doc.cn

实例化 Beanspring-doc.cn

BeanFactoryAwarespring-doc.cn

声明。BeanFactoryspring-doc.cn

BeanFactory 餐厅spring-doc.cn

BeanNameAwarespring-doc.cn

声明 Bean 的名称。spring-doc.cn

ApplicationContextAwareBeanNameAwarespring-doc.cn

BootstrapContextAwarespring-doc.cn

容器运行的资源适配器。通常仅在 JCA 感知实例。BootstrapContextApplicationContextspring-doc.cn

JCA CCIspring-doc.cn

LoadTimeWeaverAwarespring-doc.cn

定义了 weaver,用于在加载时处理类定义。spring-doc.cn

在 Spring 框架中使用 AspectJ 进行加载时编织spring-doc.cn

MessageSourceAwarespring-doc.cn

配置的消息解析策略(支持参数化和 国际化)。spring-doc.cn

ApplicationContext 的其他功能spring-doc.cn

NotificationPublisherAwarespring-doc.cn

Spring JMX 通知发布者。spring-doc.cn

通知spring-doc.cn

ResourceLoaderAwarespring-doc.cn

配置了 loader ,用于对资源的低级访问。spring-doc.cn

资源spring-doc.cn

ServletConfigAwarespring-doc.cn

容器运行的当前情况。仅在 web-aware Spring 中有效。ServletConfigApplicationContextspring-doc.cn

Spring MVCspring-doc.cn

ServletContextAwarespring-doc.cn

容器运行的当前情况。仅在 web-aware Spring 中有效。ServletContextApplicationContextspring-doc.cn

Spring MVCspring-doc.cn

再次注意,使用这些接口会将您的代码绑定到 Spring API,并且不会 遵循 Inversion of Control 样式。因此,我们建议将它们用于基础设施 需要以编程方式访问容器的 bean。spring-doc.cn

1.7. Bean 定义继承

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

如果以编程方式使用接口,则子 Bean 定义由 class 表示。大多数用户不工作 与他们在这个层面上。相反,它们在类中以声明方式配置 bean 定义 例如 .使用基于 XML 的配置时 metadata,您可以使用属性 指定父 Bean 作为此属性的值。以下示例显示了如何操作 为此,请执行以下操作:ApplicationContextChildBeanDefinitionClassPathXmlApplicationContextparentspring-doc.cn

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

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  (1)
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
1 请注意该属性。parent

子 Bean 定义使用父定义中的 Bean 类(如果没有 指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须是 与 Parent 兼容(即,它必须接受 Parent的 Property 值)。spring-doc.cn

子 Bean 定义继承 scope、constructor argument 值、property 值和 method 覆盖父级,并可选择添加新值。任何范围、初始化 您指定的 method、destroy method 或 factory method 设置 覆盖相应的父设置。staticspring-doc.cn

其余设置始终取自子定义:depends on, autowire 模式、依赖关系检查、Singleton 和 Lazy init。spring-doc.cn

前面的示例通过使用 属性。如果父定义未指定类,则显式 根据需要标记父 Bean 定义,如下例所示 显示:abstractabstractspring-doc.cn

<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 返回错误。同样,容器的内部方法会忽略定义为 抽象。abstractabstractabstractgetBean()preInstantiateSingletons()spring-doc.cn

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

1.8. 容器扩展点

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

1.8.1. 使用BeanPostProcessor

该接口定义了您可以实现的回调方法 提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项 resolution logic,依此类推。如果你想在 Spring 容器完成实例化、配置和初始化 bean 后,您可以 插入一个或多个自定义实现。BeanPostProcessorBeanPostProcessorspring-doc.cn

您可以配置多个实例,并且可以控制顺序 其中这些实例通过设置属性 仅当 实现接口时,才能设置此属性。如果您编写自己的 ,您应该考虑实现 界面也是如此。有关更多详细信息,请参阅 BeanPostProcessorOrdered 接口的 javadoc。另见注释 在程序化 BeanPostProcessor 实例的注册BeanPostProcessorBeanPostProcessororderBeanPostProcessorOrderedBeanPostProcessorOrderedspring-doc.cn

BeanPostProcessor实例在 Bean(或对象)实例上运行。那是 Spring IoC 容器实例化一个 bean 实例,然后实例执行它们的工作。BeanPostProcessorspring-doc.cn

BeanPostProcessor实例的范围是按容器划分的。这仅在您 使用容器层次结构。如果您在一个容器中定义 它仅对该容器中的 bean 进行后处理。换句话说,是 定义的 API 不会由 另一个容器,即使两个容器都属于同一层次结构。BeanPostProcessorBeanPostProcessorspring-doc.cn

要更改实际的 Bean 定义(即定义 Bean 的 Blueprint),请执行以下操作: 相反,你需要使用一个 ,如使用BeanFactoryPostProcessor自定义配置元数据中所述。BeanFactoryPostProcessorspring-doc.cn

该接口包括 恰好是两个回调方法。当此类注册为后处理器时,使用 容器中,对于容器创建的每个 bean 实例, 后处理器在容器之前从容器获取回调 初始化方法(例如 OR declared method),并在任何 bean 初始化回调之后调用。 后处理器可以对 bean 实例执行任何操作,包括忽略 callback 的 Quin 函数。bean 后处理器通常会检查回调接口, 或者它可能用代理包装 bean。一些 Spring AOP 基础设施类是 作为 Bean 后处理器实现,以提供代理包装逻辑。org.springframework.beans.factory.config.BeanPostProcessorInitializingBean.afterPropertiesSet()initspring-doc.cn

An 会自动检测在 实现接口的配置元数据。将这些 bean 注册为后处理器,以便可以调用它们 稍后,在 bean 创建时。Bean 后处理器可以部署在容器中的 与任何其他Beans一样时尚。ApplicationContextBeanPostProcessorApplicationContextspring-doc.cn

请注意,当在 configuration 类,则工厂方法的返回类型应为 implementation 类本身或至少是接口,清楚地表明该 bean 的后处理器性质。否则,在完全创建它之前,无法按类型自动检测它。 由于 a 需要提前实例化才能应用于 初始化上下文中的其他 bean,这种早期类型检测至关重要。BeanPostProcessor@Beanorg.springframework.beans.factory.config.BeanPostProcessorApplicationContextBeanPostProcessorspring-doc.cn

以编程方式注册实例BeanPostProcessor
虽然推荐的注册方法是通过自动检测(如前所述),但您可以注册它们 使用 该方法以编程方式对 a 进行攻击。当您需要在 注册,甚至用于跨层次结构中的上下文复制 Bean 后处理器。 但请注意,以编程方式添加的实例不遵循 界面。在这里,是注册顺序决定了顺序 的执行。另请注意,实例以编程方式注册 始终在通过自动检测注册的 URL 之前处理,无论 显式排序。BeanPostProcessorApplicationContextConfigurableBeanFactoryaddBeanPostProcessorBeanPostProcessorOrderedBeanPostProcessor
BeanPostProcessor实例和 AOP 自动代理

实现该接口的类是特殊的,并被视为 容器不同。它们 直接引用在启动时实例化,作为特殊启动阶段的一部分 的 .接下来,注册所有实例 以排序的方式,并应用于容器中的所有其他 bean。因为 AOP 自动代理是作为自身实现的,实例和它们直接引用的 bean 都没有资格进行自动代理,并且, 因此,不要将 aspects 编织到其中。BeanPostProcessorBeanPostProcessorApplicationContextBeanPostProcessorBeanPostProcessorBeanPostProcessorspring-doc.cn

对于任何此类 Bean,您应该会看到一条信息性日志消息:。Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)spring-doc.cn

如果你通过使用自动装配或(可能会回退到自动装配)将 bean 连接到你的 bean 中,Spring 可能会访问意外的 bean 在搜索类型匹配的依赖项候选项时,因此,请将其 不符合自动代理或其他类型的 bean 后处理的条件。例如,如果你 具有 Comments 的依赖项,其中字段或 setter 名称没有 直接对应 bean 的声明名称,不使用 name 属性, Spring 访问其他 bean 以按类型匹配它们。BeanPostProcessor@Resource@Resourcespring-doc.cn

以下示例演示如何编写、注册和使用实例 在 .BeanPostProcessorApplicationContextspring-doc.cn

示例:Hello World、-styleBeanPostProcessor

第一个示例说明了基本用法。该示例显示了一个自定义实现,该实现将每个 bean 的方法作为 它由容器创建,并将生成的字符串打印到系统控制台。BeanPostProcessortoString()spring-doc.cn

下面的清单显示了自定义实现类定义:BeanPostProcessorspring-doc.cn

Java
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;
    }
}
Kotlin
import org.springframework.beans.factory.config.BeanPostProcessor

class InstantiationTracingBeanPostProcessor : BeanPostProcessor {

    // simply return the instantiated bean as-is
    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
        return bean // we could potentially return any object reference here...
    }

    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
        println("Bean '$beanName' created : $bean")
        return bean
    }
}

以下元素使用 :beansInstantiationTracingBeanPostProcessorspring-doc.cn

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

请注意 the 是如何被定义的。它没有 甚至有一个名称,并且,因为它是一个 bean,所以它可以像任何 bean 一样被依赖注入 其他豆子。(前面的配置还定义了一个由 Groovy 支持的 bean 脚本。Spring 动态语言支持在标题为“动态语言支持”的章节中进行了详细介绍。InstantiationTracingBeanPostProcessorspring-doc.cn

以下 Java 应用程序运行上述代码和配置:spring-doc.cn

Java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
    val messenger = ctx.getBean<Messenger>("messenger")
    println(messenger)
}

上述应用程序的输出类似于以下内容:spring-doc.cn

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:AutowiredAnnotationBeanPostProcessor

将回调接口或 Comments 与自定义实现结合使用是扩展 Spring IoC 容器的常用方法。一个例子是 Spring 的 — 一个实现 它附带了 Spring 发行版和自动装配的注释字段、setter 方法、 和任意配置方法。BeanPostProcessorAutowiredAnnotationBeanPostProcessorBeanPostProcessorspring-doc.cn

1.8.2. 使用BeanFactoryPostProcessor

我们要看的下一个扩展点是 .的语义 此接口类似于 的 ,具有一个 Major 差异:对 Bean 配置元数据进行操作。 也就是说,Spring IoC 容器允许读取 配置元数据,并可能在容器实例化之前对其进行更改 除实例以外的任何 bean。org.springframework.beans.factory.config.BeanFactoryPostProcessorBeanPostProcessorBeanFactoryPostProcessorBeanFactoryPostProcessorBeanFactoryPostProcessorspring-doc.cn

您可以配置多个实例,并且可以控制 这些实例通过设置属性来运行。 但是,只有在 实现接口时,才能设置此属性。如果你自己写 ,你应该 也请考虑实现接口。有关更多详细信息,请参见BeanFactoryPostProcessorOrdered接口的javadoc。BeanFactoryPostProcessorBeanFactoryPostProcessororderBeanFactoryPostProcessorOrderedBeanFactoryPostProcessorOrderedspring-doc.cn

如果要更改实际的 Bean 实例(即创建的对象 ),那么你需要改用一个(在前面的使用 BeanPostProcessor 定制 Bean 中描述)。虽然这在技术上是可行的 要在 a 中使用 bean 实例(例如,通过使用),这样做会导致 bean 过早实例化,违反 标准容器生命周期。这可能会导致负面的副作用,例如旁通 Bean 后处理。BeanPostProcessorBeanFactoryPostProcessorBeanFactory.getBean()spring-doc.cn

此外,实例的范围是按容器划分的。这仅相关 如果您使用容器层次结构。如果您定义 a 合一 container,则它仅适用于该容器中的 bean 定义。Bean 定义 不被另一个容器中的实例进行后处理 容器,即使两个容器属于同一层次结构。BeanFactoryPostProcessorBeanFactoryPostProcessorBeanFactoryPostProcessorspring-doc.cn

当 bean 工厂后处理器在 ,当它在 中声明时,它会自动运行,以便将更改应用于配置元数据 定义容器。Spring 包含许多预定义的 bean factory 后处理器,例如 和 .您还可以使用自定义 — 例如,注册自定义属性编辑器。ApplicationContextPropertyOverrideConfigurerPropertySourcesPlaceholderConfigurerBeanFactoryPostProcessorspring-doc.cn

An 会自动检测部署到其中的任何 bean 实现接口。它将这些 bean 用作 bean factory 后处理器。您可以将这些后处理器 bean 部署为 你会喜欢任何其他豆子。ApplicationContextBeanFactoryPostProcessorspring-doc.cn

与 s 一样,您通常不希望为 s 延迟初始化配置 s。如果没有其他 bean 引用 a ,则该后处理器将根本不被实例化。 因此,将其标记为延迟初始化将被忽略,并且即使您在元素的声明上将属性设置为 ,也会急切地实例化 。BeanPostProcessorBeanFactoryPostProcessorBean(Factory)PostProcessorBean(Factory)PostProcessordefault-lazy-inittrue<beans />
示例:类名替换PropertySourcesPlaceholderConfigurer

您可以使用 to externalize 属性值 使用标准 Java 格式从单独文件中的 bean 定义。 这样做使部署应用程序的人员能够自定义特定于环境的 属性(例如数据库 URL 和密码),而不会产生 修改容器的一个或多个主 XML 定义文件。PropertySourcesPlaceholderConfigurerPropertiesspring-doc.cn

请考虑以下基于 XML 的配置元数据片段,其中定义了 a with placeholder 值:DataSourcespring-doc.cn

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

该示例显示了从外部文件配置的属性。在运行时, a 应用于元数据,该元数据将替换某些 属性。要替换的值指定为 form ,它遵循 Ant 和 log4j 以及 JSP EL 样式。PropertiesPropertySourcesPlaceholderConfigurer${property-name}spring-doc.cn

实际值来自标准 Java 格式的另一个文件:Propertiesspring-doc.cn

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

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

使用 Spring 2.5 中引入的名称空间,您可以配置属性占位符 替换为专用的配置元素。您可以将一个或多个位置作为 逗号分隔的列表,如下例所示:contextlocationspring-doc.cn

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

不仅在指定的文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,则 它检查 Spring 属性和常规 Java 属性。PropertySourcesPlaceholderConfigurerPropertiesEnvironmentSystemspring-doc.cn

您可以使用 to 替换类名,该类名 当您必须在运行时选择特定的实现类时,有时很有用。 以下示例显示了如何执行此操作:PropertySourcesPlaceholderConfigurerspring-doc.cn

<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 的阶段。preInstantiateSingletons()ApplicationContextspring-doc.cn

示例:PropertyOverrideConfigurer

的 ,另一个 bean factory 后处理器类似于 ,但与后者不同的是,原始定义 可以为 Bean 属性设置默认值或根本没有值。如果覆盖文件没有某个 Bean 属性的条目,则默认的 上下文定义。PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurerPropertiesspring-doc.cn

请注意,bean 定义不知道被覆盖,因此它不是 从 XML 定义文件中可以立即明显看出 override configurer 正在 使用。如果多个实例定义了不同的 值,由于覆盖机制,最后一个 Bean 属性优先。PropertyOverrideConfigurerspring-doc.cn

Properties 文件配置行采用以下格式:spring-doc.cn

beanName.property=value

下面的清单显示了格式的示例:spring-doc.cn

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

此示例文件可以与容器定义一起使用,该容器定义包含名为 has 和 properties 的 bean。dataSourcedriverurlspring-doc.cn

还支持复合属性名称,只要路径的每个组件 除了被覆盖的最终属性已经是非 null 的(大概是初始化的 由构造函数)。在以下示例中,bean 的 property 的 property 的 设置为 scalar 值 :sammybobfredtom123spring-doc.cn

tom.fred.bob.sammy=123
指定的覆盖值始终是文本值。它们不会被翻译成 bean 引用。当 XML Bean 中的原始值 definition 指定 bean 引用。

使用 Spring 2.5 中引入的名称空间,可以配置 属性替换为专用的 configuration 元素,如下例所示:contextspring-doc.cn

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

1.8.3. 使用FactoryBean

您可以为 本身就是工厂。org.springframework.beans.factory.FactoryBeanspring-doc.cn

该接口是 Spring IoC 容器的 实例化逻辑。如果你有复杂的初始化代码,最好用 Java 而不是(可能)冗长的 XML,您可以创建自己的 ,在该类中编写复杂的初始化,然后将 custom 添加到容器中。FactoryBeanFactoryBeanFactoryBeanspring-doc.cn

该接口提供了三种方法:FactoryBean<T>spring-doc.cn

  • T getObject():返回此工厂创建的对象的实例。这 实例可以共享,具体取决于此工厂是否返回单例 或原型。spring-doc.cn

  • boolean isSingleton(): 如果返回单例或其他,则返回。此方法的默认实现返回 。trueFactoryBeanfalsetruespring-doc.cn

  • Class<?> getObjectType():返回方法返回的对象类型 或者如果事先不知道类型。getObject()nullspring-doc.cn

概念和接口在 Spring 中的许多地方使用 框架。Spring 附带了 50 多个接口实现 本身。FactoryBeanFactoryBeanspring-doc.cn

当您需要向容器请求实际实例本身而不是 它生成的 bean 在 bean 前面加上 & 符号 () 时 调用 .因此,对于具有 of 的给定,在容器上调用将返回 product 的 ,而调用则返回实例本身。FactoryBeanid&getBean()ApplicationContextFactoryBeanidmyBeangetBean("myBean")FactoryBeangetBean("&myBean")FactoryBeanspring-doc.cn

1.9. 基于注解的容器配置

在配置 Spring 方面,注释是否比 XML 更好?

基于 annotation 的配置的引入提出了一个问题,即这是否 方法比 XML “更好”。简短的回答是“视情况而定”。长一点的回答是 每种方法都有其优点和缺点,通常,开发人员 决定哪种策略更适合他们。由于它们的定义方式,注释 在他们的声明中提供大量上下文,从而使内容更简短 配置。但是,XML 擅长在不接触其源代码的情况下连接组件 代码或重新编译它们。一些开发人员更喜欢将布线靠近源头 而其他人则认为带注解的类不再是 POJO,此外, 配置变得分散且更难控制。spring-doc.cn

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

XML 设置的替代方案由基于 annotation 的配置提供,它依赖于 用于连接组件的字节码元数据,而不是尖括号声明。 开发人员没有使用 XML 来描述 Bean 连接,而是移动了配置 到组件类本身中,方法是在相关的类、方法或 field 声明。如示例中所述:AutowiredAnnotationBeanPostProcessor,使用 a 与注解结合使用是扩展 Spring IoC 容器。例如,Spring 2.0 引入了强制执行 required 属性替换为 @Required 注解。Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖项成为可能 注射。从本质上讲,注解提供了与 在 Autowiring Collaborators 中描述,但具有更精细的控制和更广泛的 适用性。Spring 2.5 还添加了对 JSR-250 注释的支持,例如 和 。Spring 3.0 增加了对 JSR-330(依赖性 Injection for Java) 注释(如 和 .有关这些注释的详细信息,请参阅相关部分BeanPostProcessor@Autowired@PostConstruct@PreDestroyjavax.inject@Inject@Namedspring-doc.cn

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

与往常一样,您可以将后处理器注册为单独的 bean 定义,但是它们 也可以通过在基于 XML 的 Spring 中包含以下标记来隐式注册 configuration(注意包含命名空间):contextspring-doc.cn

<?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/>spring-doc.cn

<context:annotation-config/>仅在相同的 bean 上查找注释 定义它的应用程序上下文。这意味着,如果您输入 a for a , 它只检查控制器中的 bean,而不检查服务中的 bean。有关更多信息,请参阅 DispatcherServlet<context:annotation-config/>WebApplicationContextDispatcherServlet@Autowiredspring-doc.cn

1.9.1. @Required

该注释适用于 Bean 属性 setter 方法,如下所示 例:@Requiredspring-doc.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
class SimpleMovieLister {

    @Required
    lateinit var movieFinder: MovieFinder

    // ...
}

此注释指示受影响的 Bean 属性必须在 配置时间,通过 Bean 定义中的显式属性值或通过 autowiring 的如果受影响的 Bean 属性尚未 填充。这允许 Aged 和 explicit 失败,避免以后出现实例或类似情况。我们仍然建议您将断言放入 Bean 类本身(例如,放入 init 方法中)。这样做会强制执行那些必需的 引用和值,即使您在容器外部使用类也是如此。NullPointerExceptionspring-doc.cn

必须将RequiredAnnotationBeanPostProcessor注册为 Bean 才能启用对 Comments 的支持。@Requiredspring-doc.cn

注释 和 are 正式 从 Spring Framework 5.1 开始弃用,取而代之的是 使用 constructor injection for 必需的设置(或者自定义实现或自定义方法以及 Bean 属性 setter 方法)。@RequiredRequiredAnnotationBeanPostProcessorInitializingBean.afterPropertiesSet()@PostConstructspring-doc.cn

1.9.2. 使用@Autowired

JSR 330 的注解可以代替 Spring 的注解。 本节中包含的示例。有关更多详细信息,请参阅此处@Inject@Autowiredspring-doc.cn

您可以将注释应用于构造函数,如下例所示:@Autowiredspring-doc.cn

Java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
Kotlin
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao)

从 Spring Framework 4.3 开始,这种构造函数上的 Comments 不再是 如果目标 Bean 一开始只定义了一个构造函数,则为 necessary。但是,如果 有几个构造函数可用,至少没有主/默认构造函数 必须对其中一个构造函数进行注释,才能指示 container 使用哪一个。有关详细信息,请参阅有关构造函数解析的讨论。@Autowired@Autowiredspring-doc.cn

您还可以将 Comments 应用于传统的 setter 方法, 如下例所示:@Autowiredspring-doc.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
class SimpleMovieLister {

    @Autowired
    lateinit var movieFinder: MovieFinder

    // ...

}

您还可以将注释应用于具有任意名称和多个 参数,如下例所示:spring-doc.cn

Java
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
Kotlin
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}

您也可以应用于字段,甚至可以将其与构造函数混合使用,例如 以下示例显示:@Autowiredspring-doc.cn

Java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...
}
Kotlin
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao) {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

确保您的目标组件(例如,或 ) 始终由您用于 -annotated 的类型声明 注射点。否则,注入可能会因运行时的 “no type match found” 错误而失败。MovieCatalogCustomerPreferenceDao@Autowiredspring-doc.cn

对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器 通常预先知道混凝土类型。但是,对于工厂方法,您需要 以确保 Declared 的 return 类型具有足够的表现力。对于组件 实现多个接口,或者对于可能由其 implementation 类型,请考虑在 Factory 上声明最具体的返回类型 方法(至少与引用 bean 的注入点所要求一样具体)。@Beanspring-doc.cn

你还可以通过将 Comments 添加到字段或方法中来指示 Spring 提供特定类型的所有 bean,该字段或方法如下: 需要该类型的数组,如下例所示:ApplicationContext@Autowiredspring-doc.cn

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalogs: Array<MovieCatalog>

    // ...
}

这同样适用于类型化集合,如下例所示:spring-doc.cn

Java
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Set<MovieCatalog>

    // ...
}

您的目标 bean 可以实现接口或使用 的 OR 标准注解(如果您希望数组或列表中的项目) 以按特定顺序排序。否则,他们的顺序将遵循注册 容器中相应目标 bean 定义的顺序。org.springframework.core.Ordered@Order@Priorityspring-doc.cn

您可以在目标类级别和方法上声明注释。 可能对于单个 bean 定义(在多个定义的情况下, 使用相同的 bean 类)。 值可能会影响注入点的优先级, 但请注意,它们不会影响单例启动顺序,即 由依赖关系和声明确定的正交关注点。@Order@Bean@Order@DependsOnspring-doc.cn

请注意,标准注释在 level 上不可用,因为它不能在方法上声明。它的语义可以建模 through 值与 on each type 的单个 bean 的组合。javax.annotation.Priority@Bean@Order@Primaryspring-doc.cn

只要预期的键类型是 ,即使是类型化实例也可以自动装配。 map 值包含预期类型的所有 bean,键包含 相应的 bean 名称,如下例所示:MapStringspring-doc.cn

Java
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Map<String, MovieCatalog>

    // ...
}

默认情况下,当给定的 bean 没有匹配的候选 bean 可用时,自动装配将失败 注射点。对于声明的数组、集合或 map,至少有一个 匹配元素。spring-doc.cn

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

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
class SimpleMovieLister {

    @Autowired(required = false)
    var movieFinder: MovieFinder? = null

    // ...
}

如果非必需方法的依赖项(或其 dependencies)不可用。非必填字段将 在这种情况下,根本不填充,保留其默认值。spring-doc.cn

注入的构造函数和工厂方法参数是一种特殊情况,因为由于 Spring 的构造函数,属性 in 的含义略有不同 Resolution 算法,该算法可能会处理多个构造函数。构造 函数 和工厂方法参数在默认情况下实际上是必需的,但有一些特殊的 单构造函数方案中的规则,例如多元素注入点(数组、 collections, map) 解析为空实例。这 允许使用通用的实现模式,其中所有依赖项都可以在 unique 多参数构造函数 — 例如,声明为单个公共构造函数 没有注释。required@Autowired@Autowiredspring-doc.cn

任何给定 bean 类只有一个构造函数可以声明属性设置为 ,指示构造函数在用作 Spring 时要自动装配 豆。因此,如果该属性保留为其默认值, 只有一个构造函数可以用 .如果多个构造函数 declare 注解,它们都必须声明才能 被视为 autowiring 的候选者(类似于 XML 中)。 通过匹配可以满足的依赖项数量最多的构造函数 将选择 Spring 容器中的 bean。如果所有候选人都不能满足, 然后将使用 primary/default 构造函数(如果存在)。同样,如果类 声明了多个构造函数,但没有一个用 进行注释,那么 primary/default 构造函数(如果存在)。如果一个类只声明一个 constructor 开头,它将始终被使用,即使没有注解。请注意, 带注释的构造函数不必是 public。@Autowiredrequiredtruerequiredtrue@Autowiredrequired=falseautowire=constructor@Autowiredspring-doc.cn

在 setter 方法上,建议使用 of 属性而不是已弃用的 Comments。将属性设置为表示 该属性对于自动装配不是必需的,如果该属性 不能自动装配。,则更强大,因为它强制执行 属性,如果未定义值,则 引发相应的异常。required@Autowired@Requiredrequiredfalse@Requiredspring-doc.cn

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

public class SimpleMovieLister {

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

从 Spring Framework 5.0 开始,您还可以使用 Comments(任何类型的 在任何包中 — 例如,来自 JSR-305)或仅利用 Kotlin 内置 null-safety 支持:@Nullablejavax.annotation.Nullablespring-doc.cn

Java
public class SimpleMovieLister {

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

    @Autowired
    var movieFinder: MovieFinder? = null

    // ...
}

您还可以用于众所周知的 resolvable 接口 依赖项:、、、、 和 .这些接口及其扩展 接口(如 或 )是 自动解析,无需特殊设置。以下示例 autowires 一个对象:@AutowiredBeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSourceConfigurableApplicationContextResourcePatternResolverApplicationContextspring-doc.cn

Java
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var context: ApplicationContext

    // ...
}

, , , 和 Comments 由 Spring 实现处理。这意味着您无法应用这些注释 在您自己的 OR 类型(如果有)中。 这些类型必须使用 XML 或 Spring 方法显式地“连接”。@Autowired@Inject@Value@ResourceBeanPostProcessorBeanPostProcessorBeanFactoryPostProcessor@Beanspring-doc.cn

1.9.3. 微调基于 Annotation 的自动装配@Primary

因为按类型自动装配可能会导致多个候选者,所以通常需要 对选择过程的更多控制。实现此目的的一种方法是使用 Spring 的 Comments。 指示应给定一个特定的 bean 当多个 bean 是自动连接到单值的候选者时,首选项 Dependency。如果候选者中正好存在一个主 bean,则它将成为 autowired 值。@Primary@Primaryspring-doc.cn

考虑以下配置为 主要:firstMovieCatalogMovieCatalogspring-doc.cn

Java
@Configuration
public class MovieConfiguration {

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

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

    // ...
}
Kotlin
@Configuration
class MovieConfiguration {

    @Bean
    @Primary
    fun firstMovieCatalog(): MovieCatalog { ... }

    @Bean
    fun secondMovieCatalog(): MovieCatalog { ... }

    // ...
}

在前面的配置中,以下内容使用 :MovieRecommenderfirstMovieCatalogspring-doc.cn

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

相应的 bean 定义如下:spring-doc.cn

<?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.4. 使用限定符微调基于 Annotation 的自动装配

@Primary是在多个实例中使用 autowiring by type 的有效方法,当一个实例 可以确定主要候选人。当您需要对选择过程进行更多控制时, 你可以使用 Spring 的 Comments。您可以关联限定符值 使用特定参数时,缩小类型匹配的集合,以便特定的 bean 为 为每个参数选择。在最简单的情况下,这可以是一个简单的描述性值,如 如以下示例所示:@Qualifierspring-doc.cn

Java
public class MovieRecommender {

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

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

您还可以在单个构造函数参数上指定注释,或者 method 参数,如以下示例所示:@Qualifierspring-doc.cn

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

    // ...
}
Kotlin
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}

以下示例显示了相应的 Bean 定义。spring-doc.cn

<?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>
1 具有 qualifier 值的 bean 与 constructor 参数连接,该 constructor 参数 使用相同的值进行限定。main
2 具有 qualifier 值的 bean 与 constructor 参数连接,该 constructor 参数 使用相同的值进行限定。action

对于回退匹配,Bean 名称被视为默认限定符值。因此,您 可以使用 of 而不是嵌套的限定符元素来定义 bean,将 更改为相同的匹配结果。但是,尽管您可以使用此约定来引用 特定的 bean 从根本上讲是关于类型驱动的注入的 可选的语义限定符。这意味着限定符值,即使 bean 名称为 fallback 在类型匹配集中始终具有缩小语义。他们没有 在语义上表示对唯一 bean 的引用。好的限定符值为 or 或 ,表示特定组件的特征,这些 独立于 bean ,在匿名 bean 的情况下可以自动生成 定义,例如前面示例中的定义。idmain@AutowiredidmainEMEApersistentidspring-doc.cn

如前所述,限定符也适用于类型化集合 — 例如,to .在这种情况下,所有匹配的 bean 都会根据声明的 限定符作为集合注入。这意味着限定符不必是 独特。相反,它们构成了筛选标准。例如,您可以定义 具有相同限定符值 “action” 的多个 bean,所有这些 bean 都是 注入到带有 .Set<MovieCatalog>MovieCatalogSet<MovieCatalog>@Qualifier("action")spring-doc.cn

在类型匹配中,让限定符值根据目标 Bean 名称进行选择 candidates)不需要在注入点进行注释。 如果没有其他分辨率指示符(例如限定符或主标记), 对于非唯一依赖项情况, Spring 匹配注入点名称 (即字段名称或参数名称),并选择 同名候选人(如果有)。@Qualifierspring-doc.cn

也就是说,如果您打算按名称表示注解驱动的注入,请不要 主要使用 ,即使它能够在其中按 bean 名称进行选择 类型匹配的候选项。相反,请使用 JSR-250 注解,即 语义上定义以通过其唯一名称标识特定的目标组件,其中 声明的类型与匹配过程无关。 有 不同的语义:按类型选择候选 bean 后,仅在那些类型选定的候选 bean 中考虑指定的限定符值(例如, 将限定符与标有相同限定符标签的 bean 匹配)。@Autowired@Resource@AutowiredStringaccountspring-doc.cn

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

从 4.3 开始,还会考虑用于注入的自引用(即引用 返回到当前注入的 bean)。请注意,自注入是一种后备。 对其他组件的常规依赖项始终具有优先权。从这个意义上说,自我 参考文献不参与常规的候选选择,因此位于 特别 从 不 主要。相反,它们总是以最低优先级结束。 在实践中,您应该仅将自引用作为最后的手段(例如,对于 通过 Bean 的事务代理调用同一实例上的其他方法)。 在这种情况下,请考虑将受影响的方法分解为单独的委托 Bean。 或者,您可以使用 ,这可能会获得返回到当前 bean 的代理 通过其唯一名称。@Autowired@Resourcespring-doc.cn

尝试从同一配置类上的方法注入结果是 实际上也是一种自引用场景。要么延迟解析此类引用 在实际需要它的方法签名中(而不是 autowired 字段 )或将受影响的方法声明为 , 将它们与包含的 Configuration 类实例及其生命周期分离。 否则,仅在回退阶段考虑此类 bean,并具有匹配的 bean 在选择为主要候选项的其他配置类上(如果可用)。@Bean@Beanstaticspring-doc.cn

@Autowired应用于字段、构造函数和多参数方法,允许 通过参数级别的限定符注释缩小范围。相反,仅支持具有单个参数的字段和 bean 属性 setter 方法。 因此,如果您的注入目标是 constructor 或多参数方法。@Resourcespring-doc.cn

您可以创建自己的自定义限定符注释。为此,请定义一个 annotation 和 在定义中提供注释,如下例所示:@Qualifierspring-doc.cn

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

    String value();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)

然后,您可以在自动装配的字段和参数上提供自定义限定符,如 以下示例显示:spring-doc.cn

Java
public class MovieRecommender {

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

    private MovieCatalog comedyCatalog;

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

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    @Genre("Action")
    private lateinit var actionCatalog: MovieCatalog

    private lateinit var comedyCatalog: MovieCatalog

    @Autowired
    fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
        this.comedyCatalog = comedyCatalog
    }

    // ...
}

接下来,您可以提供候选 Bean 定义的信息。您可以将标记添加为标记的子元素,然后指定 and 以匹配您的自定义限定符注释。该类型与 注解的完全限定类名。或者,为了方便,如果没有 存在冲突的名称,则可以使用短类名。以下示例 演示了这两种方法:<qualifier/><bean/>typevaluespring-doc.cn

<?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 和 Managed Components 中,您可以看到 在 XML 中提供限定符元数据。具体而言,请参阅提供带有注释的限定符元数据spring-doc.cn

在某些情况下,使用没有值的 Comments 可能就足够了。这可以是 当注释用于更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有 Internet 连接可用时可以搜索。首先,定义 简单注释,如下例所示:spring-doc.cn

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

}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline

然后将注释添加到要自动装配的字段或属性中,如 以下示例:spring-doc.cn

Java
public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}
1 此行添加注释。@Offline
Kotlin
class MovieRecommender {

    @Autowired
    @Offline (1)
    private lateinit var offlineCatalog: MovieCatalog

    // ...
}
1 此行添加注释。@Offline

现在 bean 定义只需要一个 qualifier ,如以下示例所示:typespring-doc.cn

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

您还可以定义自定义限定符注释,这些注释接受 addition 或 instead to the simple 属性。如果多个属性值为 然后在要自动装配的字段或参数上指定,则 Bean 定义必须匹配 所有此类属性值都被视为 Autowire 候选项。例如, 请考虑以下注释定义:valuespring-doc.cn

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

    String genre();

    Format format();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)

在本例中是一个枚举,定义如下:Formatspring-doc.cn

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

要自动装配的字段使用 custom 限定符进行批注,并包含值 对于这两个属性:和 ,如下例所示:genreformatspring-doc.cn

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

    // ...
}
Kotlin
class MovieRecommender {

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

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

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

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

    // ...
}

最后,bean 定义应包含匹配的限定符值。这个例子 还演示了您可以使用 Bean 元属性而不是元素。如果可用,则元素及其属性采用 优先级,但是如果不存在这样的限定符,则自动装配机制会回退到标记中提供的值,就像 以下示例:<qualifier/><qualifier/><meta/>spring-doc.cn

<?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.5. 使用泛型作为自动装配限定符

除了 Comments 之外,您还可以使用 Java 泛型类型 作为限定的隐含形式。例如,假设您有以下 配置:@Qualifierspring-doc.cn

Java
@Configuration
public class MyConfiguration {

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

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
Kotlin
@Configuration
class MyConfiguration {

    @Bean
    fun stringStore() = StringStore()

    @Bean
    fun integerStore() = IntegerStore()
}

假设前面的 bean 实现了一个泛型接口(即 and),你可以将接口和泛型接口 用作限定符,如下例所示:Store<String>Store<Integer>@AutowireStorespring-doc.cn

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

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
Kotlin
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean

@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean

泛型限定符也适用于自动装配列表、实例和数组。这 以下示例自动装配一个类属 :MapListspring-doc.cn

Java
// 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;
Kotlin
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>

1.9.6. 使用CustomAutowireConfigurer

CustomAutowireConfigurer 是一个允许您注册自己的自定义限定符 Comments 类型,即使它们没有使用 Spring 的 Comments 进行 Comments。 以下示例演示如何使用:BeanFactoryPostProcessor@QualifierCustomAutowireConfigurerspring-doc.cn

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

通过以下方式确定 autowire 候选项:AutowireCandidateResolverspring-doc.cn

  • 每个 bean 定义的值autowire-candidatespring-doc.cn

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

  • 存在注释和注册的任何自定义注释 使用@QualifierCustomAutowireConfigurerspring-doc.cn

当多个 bean 符合自动装配候选者的条件时,“主要”的确定是 如下所示:如果候选者中只有一个 bean 定义将属性设置为 ,则将其选中。primarytruespring-doc.cn

1.9.7. 使用@Resource

Spring 还通过使用 JSR-250 注解来支持注入 () 在 fields 或 bean 属性 setter 方法上。 这是 Java EE 中的一种常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 中 端点。Spring 也支持 Spring Management 的对象使用这种模式。@Resourcejavax.annotation.Resourcespring-doc.cn

@Resource接受 name 属性。默认情况下,Spring 将该值解释为 要注入的 bean 名称。换句话说,它遵循 by-name 语义, 如以下示例所示:spring-doc.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    @Resource(name="myMovieFinder") (1)
    private lateinit var movieFinder:MovieFinder
}
1 此行注入 .@Resource

如果未明确指定名称,则默认名称派生自字段名称或 setter 方法。如果是字段,则采用字段名称。对于 setter 方法, 它采用 Bean 属性 name。以下示例将具有 bean named 注入到其 setter 方法中:movieFinderspring-doc.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    @Resource
    private lateinit var movieFinder: MovieFinder

}
随 Comments 提供的名称被解析为 bean 名称,由该名称 知道。 如果你显式地配置了 Spring 的 SimpleJndiBeanFactory,那么可以通过 JNDI 解析这些名称。但是,我们建议您依赖默认行为和 使用 Spring 的 JNDI 查找功能来保持间接级别。ApplicationContextCommonAnnotationBeanPostProcessor

在未指定显式名称的 usage 和类似情况下 to 查找主要类型匹配项,而不是特定的命名 bean 并解析众所周知的可解析依赖项:、、、 和 接口。@Resource@Autowired@ResourceBeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSourcespring-doc.cn

因此,在下面的示例中,该字段首先查找 bean 命名为 “customerPreferenceDao”,然后回退到该类型的主要类型匹配项:customerPreferenceDaoCustomerPreferenceDaospring-doc.cn

Java
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}
1 该字段根据已知的可解析依赖项类型注入: 。contextApplicationContext
Kotlin
class MovieRecommender {

    @Resource
    private lateinit var customerPreferenceDao: CustomerPreferenceDao


    @Resource
    private lateinit var context: ApplicationContext (1)

    // ...
}
1 该字段根据已知的可解析依赖项类型注入: 。contextApplicationContext

1.9.8. 使用@Value

@Value通常用于注入外部化属性:spring-doc.cn

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)

使用以下配置:spring-doc.cn

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

以及以下文件:application.propertiesspring-doc.cn

catalog.name=MovieCatalog

在这种情况下,parameter 和 field 将等于该值。catalogMovieCatalogspring-doc.cn

Spring 提供了默认的宽松嵌入值解析器。它将尝试解析 属性值,如果无法解析,则为属性名称(例如 ) 将作为值注入。如果您想对不存在的 值,您应该声明一个 Bean,如下所示 示例显示:${catalog.name}PropertySourcesPlaceholderConfigurerspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
使用 JavaConfig 配置 时,方法必须为 .PropertySourcesPlaceholderConfigurer@Beanstatic

如果无法解析任何占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用 , 等方法或进行自定义 占位符。${}setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparatorspring-doc.cn

Spring Boot 默认配置一个 bean,该 bean 将从 和 文件中获取属性。PropertySourcesPlaceholderConfigurerapplication.propertiesapplication.yml

Spring 提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为或)。多个逗号分隔值可以是 无需额外操作即可自动转换为 Array。IntegerintStringspring-doc.cn

可以按如下方式提供默认值:spring-doc.cn

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)

Spring 在后台使用 process 将值转换为 target 类型。如果您想 为您自己的自定义类型提供转换支持,您可以提供自己的 bean 实例,如下例所示:BeanPostProcessorConversionServiceString@ValueConversionServicespring-doc.cn

Java
@Configuration
public class AppConfig {

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

    @Bean
    fun conversionService(): ConversionService {
        return DefaultFormattingConversionService().apply {
            addConverter(MyCustomConverter())
        }
    }
}

当包含 SPEL 表达式时,该值将是动态的 在运行时计算,如下例所示:@Valuespring-doc.cn

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(
    @Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)

SPEL 还支持使用更复杂的数据结构:spring-doc.cn

Java
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

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

1.9.9. 使用 和@PostConstruct@PreDestroy

不仅识别 annotation 以及 JSR-250 生命周期注释:和 .在 Spring 2.5 中引入,对这些 annotations 提供了初始化回调销毁回调中描述的生命周期回调机制的替代方案。前提是 在 Spring 中注册, 带有这些 Comments 之一的方法在生命周期的同一点被调用 作为相应的 Spring 生命周期接口方法或显式声明的回调 方法。在以下示例中,缓存在初始化时预先填充,并且 销毁时清除:CommonAnnotationBeanPostProcessor@Resourcejavax.annotation.PostConstructjavax.annotation.PreDestroyCommonAnnotationBeanPostProcessorApplicationContextspring-doc.cn

Java
public class CachingMovieLister {

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

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

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

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

有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制spring-doc.cn

与 一样,和 注释类型是 的标准 Java 库从 JDK 6 到 8。但是,整个包与 JDK 9 中的核心 Java 模块分离,并最终在 JDK 11 的。如果需要,需要通过 Maven 获取工件 Central 现在,只需像任何其他库一样添加到应用程序的 Classpath 中即可。@Resource@PostConstruct@PreDestroyjavax.annotationjavax.annotation-apispring-doc.cn

1.10. Classpath 扫描和托管组件

本章中的大多数示例都使用 XML 来指定生成 每个都包含在 Spring 容器中。上一节 (基于注释的容器配置)演示了如何提供大量配置 元数据。然而,即使在这些例子中,“基础” Bean 定义在 XML 文件中显式定义,而 Comments 仅驱动 依赖项注入。本节介绍用于隐式检测 candidate 组件。候选组件是 匹配过滤条件,并在 容器。这样就不需要使用 XML 来执行 Bean 注册。相反,您 可以使用注解(例如)、AspectJ 类型表达式或您自己的 自定义过滤条件,用于选择哪些类注册了 Bean 定义 容器。BeanDefinition@Componentspring-doc.cn

从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能包括 核心 Spring Framework 的一部分。这允许您使用 Java 而不是 Java 定义 bean 而不是使用传统的 XML 文件。查看 、 、 和 注释 ,了解如何使用这些新功能的示例。@Configuration@Bean@Import@DependsOnspring-doc.cn

1.10.1. 和进一步的 Stereotype 注解@Component

注解是满足角色或 存储库的构造型(也称为数据访问对象或 DAO)。用途 的标记是异常的自动转换,如 异常转换中所述@Repositoryspring-doc.cn

Spring 提供了进一步的构造型 Comments: , , , 和 。 是任何 Spring Management 组件的通用构造型。、 和 是 的特化 更具体的使用案例(在 Persistence、Service 和 Presentation 中 层)。因此,您可以使用 , 来注释组件类,但是,通过使用 , , 来注释它们,或者,您的类更适合通过工具或关联进行处理 与方面。例如,这些原型注释是 切入点。、 和 也可以 在 Spring Framework 的未来版本中携带其他语义。因此,如果你是 在使用 或 for your service layer 之间进行选择是 显然是更好的选择。同样,如前所述,已经 支持作为持久层中自动异常转换的标记。@Component@Service@Controller@Component@Repository@Service@Controller@Component@Component@Repository@Service@Controller@Repository@Service@Controller@Component@Service@Service@Repositoryspring-doc.cn

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

Spring 提供的许多 Comments 都可以用作 自己的代码。元注释是可以应用于另一个注释的注释。 例如,前面提到的 Comments 使用 进行元注释,如下例所示:@Service@Componentspring-doc.cn

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

    // ...
}
1 原因的处理方式与 .@Component@Service@Component
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {

    // ...
}
1 原因的处理方式与 .@Component@Service@Component

您还可以组合元注释来创建 “组合注释”。例如 Spring MVC 的 Comments 由 和 组成。@RestController@Controller@ResponseBodyspring-doc.cn

此外,组合注释可以选择从 meta-annotations 允许自定义。当您 希望仅公开 meta-annotation 属性的子集。例如,Spring 的注解将范围名称硬编码为,但仍允许 的自定义 .下面的清单显示了 annotation 的定义:@SessionScopesessionproxyModeSessionScopespring-doc.cn

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

}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
        @get:AliasFor(annotation = Scope::class)
        val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)

然后,无需声明 the 即可使用,如下所示:@SessionScopeproxyModespring-doc.cn

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

您还可以覆盖 的值,如下例所示:proxyModespring-doc.cn

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

有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。spring-doc.cn

1.10.3. 自动检测类并注册 bean 定义

Spring 可以自动检测构造型类并使用 .例如,以下两个类 符合此类自动检测的条件:BeanDefinitionApplicationContextspring-doc.cn

Java
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
Kotlin
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
Java
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
Kotlin
@Repository
class JpaMovieFinder : MovieFinder {
    // implementation elided for clarity
}

要自动检测这些类并注册相应的 bean,您需要在类中添加其中的 是这两个类的公共父包。(或者,您可以指定 包含每个类的父包的逗号或分号或空格分隔的列表。@ComponentScan@ConfigurationbasePackagesspring-doc.cn

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
为简洁起见,前面的示例可以使用 注释(即 )。value@ComponentScan("org.example")

以下替代方法使用 XML:spring-doc.cn

<?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>
使用 隐式启用 的功能。使用 .<context:component-scan><context:annotation-config><context:annotation-config><context:component-scan>

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

在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 但是,请确保在 Descriptors 中导出组件类。如果你希望 Spring 调用类的非公共成员,请将 确保它们已“打开”(即,它们在描述符中使用声明而不是声明)。module-infoopensexportsmodule-infospring-doc.cn

此外,当您使用 component-scan 元素。这意味着这两个组件是自动检测的,并且 连接在一起 — 所有这些都没有以 XML 形式提供任何 bean 配置元数据。AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorspring-doc.cn

您可以通过添加 的值为 .AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorannotation-configfalse

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

默认情况下,使用 、 、 、 或 本身使用 Comments 的自定义 Comments 的类是 唯一检测到的候选组件。但是,您可以修改和扩展此行为 通过应用自定义筛选器。将它们添加为 或 属性 注释(或 AS 或元素的子元素 XML 配置)。每个 filter 元素都需要 and 属性。 下表描述了筛选选项:@Component@Repository@Service@Controller@Configuration@ComponentincludeFiltersexcludeFilters@ComponentScan<context:include-filter /><context:exclude-filter /><context:component-scan>typeexpressionspring-doc.cn

表 5.过滤器类型
过滤器类型 示例表达式 描述

annotation (默认)spring-doc.cn

org.example.SomeAnnotationspring-doc.cn

目标组件中类型级别存在元存在的 Annotation。spring-doc.cn

可分配的spring-doc.cn

org.example.SomeClassspring-doc.cn

目标组件可分配给 (扩展或实现) 的类 (或接口) 。spring-doc.cn

AspectJspring-doc.cn

org.example..*Service+spring-doc.cn

要由目标组件匹配的 AspectJ 类型表达式。spring-doc.cn

正则表达式spring-doc.cn

org\.example\.Default.*spring-doc.cn

要与目标组件的类名称匹配的正则表达式。spring-doc.cn

习惯spring-doc.cn

org.example.MyTypeFilterspring-doc.cn

接口的自定义实现。org.springframework.core.type.TypeFilterspring-doc.cn

以下示例显示了忽略所有注释的配置 并使用 “stub” 存储库:@Repositoryspring-doc.cn

Java
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
        excludeFilters = [Filter(Repository::class)])
class AppConfig {
    // ...
}

下面的清单显示了等效的 XML:spring-doc.cn

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
您还可以通过在 注解或通过作为元素的属性提供。这实际上禁用了类的自动检测 使用 、、、、 或 进行批注或元批注。useDefaultFilters=falseuse-default-filters="false"<component-scan/>@Component@Repository@Service@Controller@RestController@Configuration

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

Spring 组件还可以向容器提供 bean 定义元数据。你可以做 这与用于定义 Comments 类中的 Bean 元数据的 Comments 相同。以下示例显示了如何执行此操作:@Bean@Configurationspring-doc.cn

Java
@Component
public class FactoryMethodComponent {

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

    public void doWork() {
        // Component method implementation omitted
    }
}
Kotlin
@Component
class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    fun doWork() {
        // Component method implementation omitted
    }
}

前面的类是一个 Spring 组件,其方法中包含特定于应用程序的代码。但是,它还提供了一个 bean 定义,该定义具有 factory 方法引用方法 。注释标识 Factory 方法和其他 Bean 定义属性,例如通过 注释。可以指定的其他方法级注释包括 、 和 自定义限定符注释。doWork()publicInstance()@Bean@Qualifier@Scope@Lazyspring-doc.cn

除了其在组件初始化中的作用外,您还可以将注释放置在标有 或 的注入点上。在此上下文中, 它会导致注入延迟分辨率代理。但是,这种代理方法 相当有限。用于复杂的惰互,特别是组合 对于可选依赖项,我们建议改用。@Lazy@Autowired@InjectObjectProvider<MyTargetBean>

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

Java
@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);
    }
}
Kotlin
@Component
class FactoryMethodComponent {

    companion object {
        private var i: Int = 0
    }

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected fun protectedInstance(
            @Qualifier("public") spouse: TestBean,
            @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
        this.spouse = spouse
        this.country = country
    }

    @Bean
    private fun privateInstance() = TestBean("privateInstance", i++)

    @Bean
    @RequestScope
    fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}

该示例将 method 参数自动连接到另一个名为 .Spring Expression Language 元素 通过表示法定义属性的值 。对于 Comments,表达式解析器被预先配置为在 解析表达式文本。StringcountryageprivateInstance#{ <expression> }@Valuespring-doc.cn

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

Java
@Component
public class FactoryMethodComponent {

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

    @Bean
    @Scope("prototype")
    fun prototypeInstance(injectionPoint: InjectionPoint) =
            TestBean("prototypeInstance for ${injectionPoint.member}")
}

常规 Spring 组件中的方法的处理方式与它们的 对应的 Spring 类中。区别在于,类没有使用 CGLIB 进行增强以拦截方法和字段的调用。 CGLIB 代理是调用方法或方法中的字段的方法 IN CLASSES 创建对协作对象的 Bean 元数据引用。 这些方法不是用普通的 Java 语义调用的,而是通过 container 来提供 Spring 的通常生命周期管理和代理 bean,即使通过对方法的编程调用引用其他 bean 也是如此。 相比之下,在普通类中调用方法中的方法或字段具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他 约束适用。@Bean@Configuration@Component@Bean@Configuration@Bean@Bean@Componentspring-doc.cn

您可以将方法声明为 ,允许在没有 创建其包含的 Configuration 类作为实例。这使得特别 sense 在定义后处理器 bean(例如,type 或 )时,因为这样的 bean 在容器的早期就被初始化了 生命周期,并且应避免在此时触发配置的其他部分。@BeanstaticBeanFactoryPostProcessorBeanPostProcessorspring-doc.cn

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

方法的 Java 语言可见性不会对 Spring 容器中生成的 bean 定义。您可以自由地声明您的 你认为适合 non- classes 和 static 的 factory 方法 方法。但是,类中的常规方法需要 才能被覆盖 — 也就是说,它们不能被声明为 或 。@Bean@Configuration@Bean@Configurationprivatefinalspring-doc.cn

@Bean方法也会在给定组件的基类上发现,或者 configuration 类,以及在 Java 8 上接口中声明的默认方法 由 component 或 configuration 类实现。这允许很多 灵活组合复杂的配置安排,甚至多个 从 Spring 4.2 开始,可以通过 Java 8 默认方法进行继承。spring-doc.cn

最后,单个类可以包含多个方法 bean,作为多个工厂方法的安排,根据可用情况使用 运行时的依赖项。这与选择“最贪婪”的算法相同 constructor 或 factory 方法:具有 在构造时选择最大数量的 satisfiable dependencies, 类似于容器在多个构造函数之间进行选择的方式。@Bean@Autowiredspring-doc.cn

1.10.6. 命名自动检测到的分量

当组件在扫描过程中被自动检测时,其 Bean 名称为 由该扫描程序已知的策略生成。默认情况下,任何 Spring 构造型注释(、和)因此将该名称提供给 相应的 bean 定义。BeanNameGenerator@Component@Repository@Service@Controllervaluespring-doc.cn

如果此类注释不包含名称或包含检测到的任何其他组件 (例如由自定义过滤器发现的那些),默认的 Bean 名称生成器返回 未大写的非限定类名。例如,如果以下组件 类,则名称为 和 :valuemyMovieListermovieFinderImplspring-doc.cn

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

如果您不想依赖默认的 bean 命名策略,则可以提供自定义的 bean 命名策略。首先,实现 BeanNameGenerator 接口,并确保包含一个默认的 no-arg 构造函数。然后,提供完整的 限定的类名,如以下示例注释 和 bean 定义显示。spring-doc.cn

如果由于多个自动检测到的组件具有 相同的非限定类名(即,具有相同名称但驻留在 不同的软件包),你可能需要配置一个,默认为 生成的 Bean 名称的完全限定类名。从 Spring Framework 5.2.3 开始,位于 package 中可用于此类目的。BeanNameGeneratorFullyQualifiedAnnotationBeanNameGeneratororg.springframework.context.annotation
Java
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

作为一般规则,请考虑在使用 Comments 指定名称时,每当其他 组件可能会显式引用它。另一方面, 每当容器负责 wiring 时,自动生成的名称就足够了。spring-doc.cn

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

与一般的 Spring 管理组件一样,默认和最常见的 autodetected components 为 。但是,有时您需要不同的范围 这可以通过 Annotation 指定。您可以提供 范围,如下例所示:singleton@Scopespring-doc.cn

Java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
@Scope注解仅在具体的 Bean 类上被内省(对于带注解的 组件)或工厂方法(用于方法)。与 XML Bean 相比 定义,没有 bean 定义继承的概念,而继承 类级别的层次结构与元数据目的无关。@Bean

有关 Spring 上下文中特定于 Web 的范围(例如“request”或“session”)的详细信息, 请参阅 Request、Session、Application 和 WebSocket 范围。与这些范围的预构建注释一样, 您还可以使用 Spring 的元注释编写自己的范围注释 方法:例如,使用 、 也可能声明自定义范围代理模式。@Scope("prototype")spring-doc.cn

要为范围解析提供自定义策略,而不是依赖 基于注释的方法,您可以实现 ScopeMetadataResolver 接口。请务必包含默认的 no-arg 构造函数。然后,您可以提供 完全限定的类名,如以下示例所示 注释和 bean 定义显示:
Java
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

当使用某些非单例作用域时,可能需要为 scoped 对象。原因在 Scoped Bean as Dependencies 中进行了描述。 为此,component-scan 上提供了 scoped-proxy 属性 元素。三个可能的值是:、 和 。例如 以下配置将生成标准 JDK 动态代理:nointerfacestargetClassspring-doc.cn

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

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

Fine-tuning Annotation-based Autowiring with Qualifiers中讨论了该注解。 该部分中的示例演示了 annotation 和 自定义限定符注释,用于在解析 autowire 时提供精细控制 候选人。因为这些示例是基于 XML Bean 定义的,所以限定符 通过使用 XML 中元素的 or 子元素,在候选 Bean 定义上提供元数据。当依赖 Classpath 扫描 自动检测组件,则可以提供类型级 Candidate 类的注释。以下三个示例演示了这一点 技术:@Qualifier@Qualifierqualifiermetabeanspring-doc.cn

Java
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
Java
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
    // ...
}
Java
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
    // ...
}
与大多数基于 Comments 的替代方案一样,请记住,Comments 元数据是 绑定到类定义本身,而 XML 的使用允许多个 bean 的 SAME 类型在其限定符元数据中提供变体,因为 元数据是按实例而不是按类提供的。

1.10.9. 生成候选组件的索引

虽然 Classpath 扫描非常快,但可以提高启动性能 通过在编译时创建静态候选列表来获取大型应用程序。在这个 模式下,作为组件扫描目标的所有模块都必须使用此机制。spring-doc.cn

您的现有 or 指令必须保留 unchanged 请求上下文以扫描某些包中的候选项。当检测到此类索引时,它会自动使用它,而不是扫描 类路径。@ComponentScan<context:component-scan/>ApplicationContext

要生成索引,请向包含 作为组件 Scan 指令目标的组件。以下示例显示了 如何使用 Maven 执行此操作:spring-doc.cn

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

对于 Gradle 4.5 及更早版本,应在配置中声明依赖项,如以下示例所示:compileOnlyspring-doc.cn

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

对于 Gradle 4.6 及更高版本,应在配置中声明依赖项,如以下示例所示:annotationProcessorspring-doc.cn

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

工件会生成一个文件,该文件 包含在 jar 文件中。spring-context-indexerMETA-INF/spring.componentsspring-doc.cn

在 IDE 中使用此模式时,必须为 注册为注释处理器,以确保索引在以下时间是最新的 候选组件已更新。spring-context-indexer
找到文件时,将自动启用索引 在 Classpath 上。如果索引部分可用于某些库(或用例) 但无法为整个应用程序构建,则可以回退到常规的 Classpath 排列(就好像根本没有索引一样),作为 JVM 系统属性或通过 SpringProperties 机制。META-INF/spring.componentsspring.index.ignoretrue

1.11. 使用 JSR 330 标准注解

从 Spring 3.0 开始, Spring 提供对 JSR-330 标准注释的支持 (依赖关系注入)。这些 Comments 的扫描方式与 Spring 相同 附注。要使用它们,您需要在 Classpath 中包含相关的 jar。spring-doc.cn

如果您使用 Maven,则工件在标准 Maven 中可用 存储库 ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。 您可以将以下依赖项添加到文件pom.xml:javax.injectspring-doc.cn

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

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

您可以按如下方式使用 ,而不是 :@Autowired@javax.inject.Injectspring-doc.cn

Java
import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}
Kotlin
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder


    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}

与 一样,您可以在字段级别、方法级别使用 和 constructor-argument 级别。此外,您可以将注入点声明为 ,允许按需访问范围较短的 bean 或延迟访问 其他 bean 通过调用。以下示例提供了 前面的示例:@Autowired@InjectProviderProvider.get()spring-doc.cn

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

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

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

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}
Kotlin
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder


    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}

如果您想为应该注入的依赖项使用限定名称, 您应该使用注释,如下例所示:@Namedspring-doc.cn

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder

    @Inject
    fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
        this.movieFinder = movieFinder
    }

    // ...
}

与 一样,也可以与 或 一起使用。这在这里更适用,因为没有 一个属性。以下一对示例演示如何使用 and :@Autowired@Injectjava.util.Optional@Nullable@Injectrequired@Inject@Nullablespring-doc.cn

public class SimpleMovieLister {

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

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

    @Inject
    var movieFinder: MovieFinder? = null
}

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

您可以使用 或 ,而不是 , 如下例所示:@Component@javax.inject.Namedjavax.annotation.ManagedBeanspring-doc.cn

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

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}

在不指定组件名称的情况下使用是很常见的。 可以以类似的方式使用,如下例所示:@Component@Namedspring-doc.cn

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

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

@Named
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}

当您使用 或 时,您可以在 与使用 Spring 注释时的方式完全相同,如下例所示:@Named@ManagedBeanspring-doc.cn

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
与 相反,JSR-330 和 JSR-250 注释是不可组合的。您应该使用 Spring 的 stereotype 模型来构建 自定义组件注释。@Component@NamedManagedBean

1.11.3. JSR-330 标准注解的限制

当您使用标准注释时,您应该知道一些重要的 功能不可用,如下表所示:spring-doc.cn

表 6.Spring 组件模型元素与 JSR-330 变体
Spring javax.inject.* javax.inject 限制 / 注释

@Autowiredspring-doc.cn

@Injectspring-doc.cn

@Inject没有 'required' 属性。可以与 Java 8 一起使用。Optionalspring-doc.cn

@Componentspring-doc.cn

@Named / @ManagedBeanspring-doc.cn

JSR-330 不提供可组合模型,只提供一种标识命名组件的方法。spring-doc.cn

@Scope(“单例”)spring-doc.cn

@Singletonspring-doc.cn

JSR-330 的默认范围类似于 Spring 的 .但是,为了保持它 与 Spring 的一般默认值一致,在 Spring 中声明的 JSR-330 bean container 默认为 a。为了使用除 之外的作用域 , 你应该使用 Spring 的 Comments。 还提供 @Scope 注释。 不过,这个 API 仅用于创建您自己的 Comments。prototypesingletonsingleton@Scopejavax.injectspring-doc.cn

@Qualifierspring-doc.cn

@Qualifier / @Namedspring-doc.cn

javax.inject.Qualifier只是一个用于构建自定义限定符的元注释。 具体限定符(如带有值的 Spring)可以关联 通过。String@Qualifierjavax.inject.Namedspring-doc.cn

@Valuespring-doc.cn

-spring-doc.cn

无等效项spring-doc.cn

@Requiredspring-doc.cn

-spring-doc.cn

无等效项spring-doc.cn

@Lazyspring-doc.cn

-spring-doc.cn

无等效项spring-doc.cn

对象工厂spring-doc.cn

供应商spring-doc.cn

javax.inject.Provider是 Spring 的 , 仅使用较短的方法名称。它也可以与 Spring 的 or 具有未注释的构造函数和 setter 方法。ObjectFactoryget()@Autowiredspring-doc.cn

1.12. 基于 Java 的容器配置

本节介绍如何在 Java 代码中使用 Comments 来配置 Spring 容器。它包括以下主题:spring-doc.cn

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

Spring 的新 Java 配置支持中的核心工件是 -annotated 类和 -annotated 方法。@Configuration@Beanspring-doc.cn

注解用于指示方法 instantites、configures 和 初始化一个要由 Spring IoC 容器管理的新对象。对于熟悉的人 使用 Spring 的 XML 配置,注解起着与 元素。您可以将 -annotated 方法与任何 Spring 一起使用。然而,它们最常与Beans一起使用。@Bean<beans/>@Bean<bean/>@Bean@Component@Configurationspring-doc.cn

对类进行注释表示其主要目的是作为 bean 定义的来源。此外,类允许 inter-bean 依赖项通过调用同一类中的其他方法来定义。 最简单的类如下所示:@Configuration@Configuration@Bean@Configurationspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun myService(): MyService {
        return MyServiceImpl()
    }
}

前面的类等价于下面的 Spring XML:AppConfig<beans/>spring-doc.cn

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration vs “lite” @Bean模式?

当在未使用 注释的类中声明方法时,它们被称为以 “lite” 模式处理。Bean 方法 在 a 甚至普通的 old class 中声明的都被认为是 “lite”的, 具有不同的 Containing 类和 Method 的 Primary 用途 在那里是一种奖励。例如,服务组件可能会公开管理视图 通过每个适用组件类上的附加方法添加到容器中。 在这种情况下,方法是通用的工厂方法机制。@Bean@Configuration@Component@Bean@Bean@Beanspring-doc.cn

与 full 不同,lite 方法无法声明 bean 间依赖关系。 相反,它们对其包含组件的内部 state 进行操作,并且可以选择对 他们可以声明的参数。因此,此类方法不应调用其他方法。每个这样的方法实际上都只是一个特定 bean 引用,而不需要任何特殊的运行时语义。这里的积极副作用是 在运行时不必应用 CGLIB 子类化,因此 类设计的术语(即,包含类可以是 ETC 等)。@Configuration@Bean@Bean@Beanfinalspring-doc.cn

在常见场景中,方法将在类中声明, 确保始终使用 “full” 模式,因此跨方法引用 重定向到容器的生命周期管理。这可以防止通过常规 Java 调用意外调用相同的方法,这有助于 减少在 “Lite” 模式下运行时难以追踪的细微错误。@Bean@Configuration@Beanspring-doc.cn

以下部分将深入讨论 and 注释。 但是,首先,我们介绍了使用 基于 Java 的配置。@Bean@Configurationspring-doc.cn

1.12.2. 使用 实例化 Spring 容器AnnotationConfigApplicationContext

以下各节记录了 Spring 中引入的 Spring 3.0. 这个多功能的实现不仅能够接受类作为输入,还能够接受普通类和类 使用 JSR-330 元数据进行注释。AnnotationConfigApplicationContextApplicationContext@Configuration@Componentspring-doc.cn

当类作为输入提供时,类本身 注册为 bean 定义和类中所有声明的方法 也注册为 Bean 定义。@Configuration@Configuration@Beanspring-doc.cn

当提供 JSR-330 类时,它们将被注册为 Bean 定义,并假定 DI 元数据(如 或 在必要时使用这些类。@Component@Autowired@Injectspring-doc.cn

结构简单

与在实例化 a 时将 Spring XML 文件用作 input 的方式大致相同,您可以在 实例化一个 .这允许完全 Spring 容器的无 XML 用法,如下例所示:ClassPathXmlApplicationContext@ConfigurationAnnotationConfigApplicationContextspring-doc.cn

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

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

如前所述,不仅限于工作 与类。可以提供任何带 JSR-330 注释的类 作为构造函数的 input,如下例所示:AnnotationConfigApplicationContext@Configuration@Componentspring-doc.cn

Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

前面的示例假定 , , 和 使用 Spring 依赖项注入注释,例如 .MyServiceImplDependency1Dependency2@Autowiredspring-doc.cn

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

您可以使用 no-arg 构造函数实例化 ,然后使用该方法进行配置。这种方法特别有用 以编程方式构建 .以下内容 示例展示了如何做到这一点:AnnotationConfigApplicationContextregister()AnnotationConfigApplicationContextspring-doc.cn

Java
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();
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.register(AppConfig::class.java, OtherConfig::class.java)
    ctx.register(AdditionalConfig::class.java)
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}
启用 Component Scanningscan(String…​)

要启用组件扫描,您可以按如下方式对类进行注释:@Configurationspring-doc.cn

Java
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig  {
    // ...
}
1 此注释启用组件扫描。
Kotlin
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig  {
    // ...
}
1 此注释启用组件扫描。

有经验的 Spring 用户可能熟悉 XML 声明等价物 Spring 的命名空间,如以下示例所示:context:spring-doc.cn

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

在前面的示例中,扫描包以查找任何带 -ancomments 的类,并且这些类被注册为 Spring bean 定义。 公开了该方法以允许相同的组件扫描功能,因为 以下示例显示:com.acme@ComponentAnnotationConfigApplicationContextscan(String…​)spring-doc.cn

Java
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
Kotlin
fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.scan("com.acme")
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
}
请记住,类是元注释的,因此它们是组件扫描的候选者。在前面的示例中, 假设 在 package(或任何 package)中声明 ),则会在调用 期间选取它。在 上,它的所有方法都将被处理并注册为容器中的 Bean 定义。@Configuration@ComponentAppConfigcom.acmescan()refresh()@Bean
支持 Web 应用程序AnnotationConfigWebApplicationContext

的变体 可用 跟。在以下情况下,可以使用此实现 配置 Spring servlet 侦听器、 Spring MVC 等。以下代码段配置了一个典型的 Spring MVC Web 应用程序(注意 context-param 和 init-param) 的WebApplicationContextAnnotationConfigApplicationContextAnnotationConfigWebApplicationContextContextLoaderListenerDispatcherServletweb.xmlcontextClassspring-doc.cn

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

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

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

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

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

1.12.3. 使用 Annotation@Bean

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

可以在 -annotated 或 -annotated 类中使用注释。@Bean@Configuration@Componentspring-doc.cn

声明一个 Bean

要声明 Bean,可以使用注释对方法进行注释。您使用此 方法在 指定为方法的返回值。默认情况下,bean 名称与 方法名称。下面的示例演示方法声明:@BeanApplicationContext@Beanspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService() = TransferServiceImpl()
}

前面的配置与下面的 Spring XML 完全等价:spring-doc.cn

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

这两个声明都使名为 的 bean 在 中可用,绑定到 类型的对象实例 ,因为 以下文本图像显示:transferServiceApplicationContextTransferServiceImplspring-doc.cn

transferService -> com.acme.TransferServiceImpl

您还可以使用接口(或基类)声明方法 return 类型,如下例所示:@Beanspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}

但是,这会将高级类型预测的可见性限制为指定的 接口类型 ()。然后,使用完整类型 () 仅在实例化受影响的 singleton bean 后,容器才知道。 非惰性单例 bean 根据它们的声明顺序进行实例化, 因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间 尝试通过未声明的类型(如 , 只有在 bean 实例化后才会解析)。TransferServiceTransferServiceImpl@Autowired TransferServiceImpltransferServicespring-doc.cn

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

带注释的方法可以具有任意数量的参数,用于描述 构建该 bean 所需的依赖项。例如,如果我们需要一个 ,我们可以使用一个 参数,如下例所示:@BeanTransferServiceAccountRepositoryspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

解析机制与基于构造函数的依赖项几乎相同 注射。有关更多详细信息,请参阅相关部分spring-doc.cn

接收生命周期回调

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

常规的 Spring 生命周期回调完全支持为 井。如果 Bean 实现 、 、 或 、 则 容器调用相应的方法。InitializingBeanDisposableBeanLifecyclespring-doc.cn

标准接口集(例如 BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContext Aware 等)也完全受支持。*Awarespring-doc.cn

注解支持指定任意初始化和析构 回调方法,很像 Spring XML 的 and attributes 在元素上,如下例所示:@Beaninit-methoddestroy-methodbeanspring-doc.cn

Java
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();
    }
}
Kotlin
class BeanOne {

    fun init() {
        // initialization logic
    }
}

class BeanTwo {

    fun cleanup() {
        // destruction logic
    }
}

@Configuration
class AppConfig {

    @Bean(initMethod = "init")
    fun beanOne() = BeanOne()

    @Bean(destroyMethod = "cleanup")
    fun beanTwo() = BeanTwo()
}

默认情况下,使用 Java 配置定义的具有 public 或 method 的 bean 会自动使用销毁回调进行登记。如果你有一个 public 或 方法,并且你不希望在容器 关闭时,您可以添加到 Bean 定义中以禁用 default 模式。closeshutdowncloseshutdown@Bean(destroyMethod="")(inferred)spring-doc.cn

默认情况下,您可能希望对使用 JNDI 获取的资源执行此操作,因为它的 生命周期在应用程序外部进行管理。特别是,确保始终这样做 ,因为已知它在 Java EE 应用程序服务器上存在问题。DataSourcespring-doc.cn

以下示例说明如何防止 :DataSourcespring-doc.cn

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

此外,对于方法,您通常使用编程式 JNDI 查找,或者 使用 Spring 的 or helpers 或直接的 JNDI 用法,但不使用 variant(这将强制 u 将返回类型声明为类型,而不是实际目标 type,使其更难用于其他方法中的交叉引用调用 打算在此处引用提供的资源)。@BeanJndiTemplateJndiLocatorDelegateInitialContextJndiObjectFactoryBeanFactoryBean@Beanspring-doc.cn

在上面的示例(前面的说明)中,在构造期间直接调用该方法同样有效,如下例所示:BeanOneinit()spring-doc.cn

Java
@Configuration
public class AppConfig {

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

    // ...
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne().apply {
        init()
    }

    // ...
}
当您直接在 Java 中工作时,您可以对对象执行任何您喜欢的操作,并执行 并不总是需要依赖容器生命周期。
指定 Bean 范围

Spring 包含 Comments,以便您可以指定 bean 的范围。@Scopespring-doc.cn

使用注释@Scope

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

默认范围是 ,但您可以使用注解 如下例所示:singleton@Scopespring-doc.cn

Java
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
Kotlin
@Configuration
class MyConfiguration {

    @Bean
    @Scope("prototype")
    fun encryptor(): Encryptor {
        // ...
    }
}
@Scopescoped-proxy

Spring 提供了一种通过作用域代理处理作用域依赖项的便捷方法。最简单的创建 使用 XML 配置时,此类代理是 element。 使用 Comments 在 Java 中配置 bean 可提供等效的支持 替换为属性。默认值为 ,它 通常表示不应创建作用域代理,除非使用不同的默认值 已在 component-scan 指令级别进行配置。您可以指定 、 或 .<aop:scoped-proxy/>@ScopeproxyModeScopedProxyMode.DEFAULTScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACESScopedProxyMode.NOspring-doc.cn

如果您将 XML 参考文档(请参阅范围代理)中的范围代理示例移植到我们的 using Java, 它类似于以下内容:@Beanspring-doc.cn

Java
// 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;
}
Kotlin
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()

@Bean
fun userService(): Service {
    return SimpleUserService().apply {
        // a reference to the proxied userPreferences bean
        setUserPreferences(userPreferences())
    }
}
自定义 Bean 命名

默认情况下,配置类使用方法的名称作为 结果 bean。但是,可以使用属性 如下例所示:@Beannamespring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean("myThing")
    fun thing() = Thing()
}
Bean 别名

正如 命名 Bean 中所讨论的,有时需要给出一个 bean 多个名称,也称为 Bean 别名。为此,注释的属性接受 String 数组。以下示例演示如何设置 bean 的多个别名:name@Beanspring-doc.cn

Java
@Configuration
public class AppConfig {

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

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

有时,提供更详细的 bean 文本描述会很有帮助。这可以 当 bean 公开(可能通过 JMX)以进行监视时,特别有用。spring-doc.cn

要向 添加描述 ,可以使用 @Description 注释,如下例所示:@Beanspring-doc.cn

Java
@Configuration
public class AppConfig {

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

    @Bean
    @Description("Provides a basic example of a bean")
    fun thing() = Thing()
}

1.12.4. 使用注解@Configuration

@Configuration是类级注解,指示对象是 bean 定义。 类通过 -annotated 声明 bean 方法。对类的方法的调用也可用于定义 bean 间依赖关系。有关一般介绍,请参阅基本概念:@Bean@Configuration@Configuration@Bean@Bean@Configurationspring-doc.cn

注入 bean 间依赖关系

当 bean 彼此依赖时,表达这种依赖性就很简单 就像让一个 bean 方法调用另一个 bean 方法一样,如下例所示:spring-doc.cn

Java
@Configuration
public class AppConfig {

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

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne(beanTwo())

    @Bean
    fun beanTwo() = BeanTwo()
}

在前面的示例中,接收对 through 构造函数 注射。beanOnebeanTwospring-doc.cn

这种声明 bean 间依赖关系的方法仅在 在类中声明。不能声明 bean 间依赖关系 通过使用普通类。@Bean@Configuration@Component
查找方法注入

如前所述,查找方法注入是一个 您应该很少使用的高级功能。它在 singleton 范围的 bean 依赖于原型范围的 bean。使用 Java 实现此目的 type 配置为实现此模式提供了一种自然的方法。这 以下示例显示了如何使用 Lookup 方法注入:spring-doc.cn

Java
public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {
    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val 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 fun createCommand(): Command
}

通过使用 Java 配置,您可以创建 where abstract 方法被覆盖,以便它查找新的 (prototype) 命令对象。以下示例显示了如何执行此操作:CommandManagercreateCommand()spring-doc.cn

Java
@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();
        }
    }
}
Kotlin
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
    val command = AsyncCommand()
    // inject dependencies here as required
    return command
}

@Bean
fun commandManager(): CommandManager {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return object : CommandManager() {
        override fun createCommand(): Command {
            return asyncCommand()
        }
    }
}
有关基于 Java 的配置如何在内部工作的更多信息

请考虑以下示例,该示例显示了一个被调用两次的带 Comments 的方法:@Beanspring-doc.cn

Java
@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();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun clientService1(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientService2(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}

clientDao()已被调用一次 和 一次。 由于此方法会创建一个新实例并返回它,因此您将 通常期望有两个实例(每个服务一个)。那肯定是 problematic:在 Spring 中,实例化的 bean 默认有一个范围。这是 神奇之处:所有类在启动时都是子类化的 跟。在子类中,子方法首先检查容器是否有任何 在调用 Parent 方法并创建新实例之前缓存 (作用域) bean。clientService1()clientService2()ClientDaoImplsingleton@ConfigurationCGLIBspring-doc.cn

根据 Bean 的范围,行为可能会有所不同。我们正在交谈 关于单例 这里.

从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的类路径中,因为 CGLIB 类已重新打包并直接包含在下 在 spring-core JAR 中。org.springframework.cglibspring-doc.cn

由于 CGLIB 在 startup-time 的 Startup-time 中。特别是,配置类不能是 final。但是,由于 在 4.3 中,允许在 Configuration 类上使用任何构造函数,包括使用或单个非 default 构造函数声明进行 default 注入。@Autowiredspring-doc.cn

如果您希望避免任何 CGLIB 施加的限制,请考虑在非类上声明您的方法(例如,在普通类上)。 方法之间的跨方法调用不会被拦截,因此你有 完全依赖于构造函数或方法级别的依赖注入。@Bean@Configuration@Component@Beanspring-doc.cn

1.12.5. 编写基于 Java 的配置

Spring 基于 Java 的配置功能允许您编写 Comments,这可以减少 配置的复杂程度。spring-doc.cn

使用注释@Import

就像在 Spring XML 文件中使用元素来帮助模块化一样 配置,该注解允许从 另一个 Configuration 类,如下例所示:<import/>@Import@Beanspring-doc.cn

Java
@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();
    }
}
Kotlin
@Configuration
class ConfigA {

    @Bean
    fun a() = A()
}

@Configuration
@Import(ConfigA::class)
class ConfigB {

    @Bean
    fun b() = B()
}

现在,无需同时指定两者和时间 实例化上下文,只需要显式提供,因为 以下示例显示:ConfigA.classConfigB.classConfigBspring-doc.cn

Java
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);
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)

    // now both beans A and B will be available...
    val a = ctx.getBean<A>()
    val b = ctx.getBean<B>()
}

这种方法简化了容器实例化,因为只需要处理一个类 替换为 Like,而不是要求您在构造过程中记住可能大量的类。@Configurationspring-doc.cn

从 Spring Framework 4.2 开始,还支持对常规组件的引用 类,类似于 method。 如果您想通过使用一些 configuration 类作为入口点来显式定义所有组件。@ImportAnnotationConfigApplicationContext.register
注入对导入定义的依赖关系@Bean

前面的示例有效,但过于简单。在大多数实际场景中,bean 具有 跨配置类彼此依赖。使用 XML 时,这不是 问题,因为不涉及编译器,你可以声明并信任 Spring 在容器初始化期间解决它。 使用类时,Java 编译器对 配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。ref="someBean"@Configurationspring-doc.cn

幸运的是,解决这个问题很简单。正如我们已经讨论过的, 一个方法可以有任意数量的描述 Bean 的参数 依赖。考虑以下更真实的场景,其中包含多个类,每个类都依赖于其他类中声明的 bean:@Bean@Configurationspring-doc.cn

Java
@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");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig {

    @Bean
    fun accountRepository(dataSource: DataSource): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}


fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

还有另一种方法可以达到相同的结果。请记住,类是 最终容器中只有另一个 bean:这意味着它们可以利用 and injection 和其他功能,就像任何其他 bean 一样。@Configuration@Autowired@Valuespring-doc.cn

确保您以这种方式注入的依赖项只是最简单的类型。 类在上下文初始化期间很早就被处理,并强制依赖项 以这种方式注入可能会导致意外的提前初始化。只要有可能,就求助于 基于参数的注入,如前面的示例所示。@Configurationspring-doc.cn

此外,要特别小心 和 定义 通过。这些通常应该声明为方法,而不是触发 实例化其包含的配置类。否则,并且可能不会 在配置类本身上工作,因为可以将其创建为早于 AutowiredAnnotationBeanPostProcessor 的 bean 实例。BeanPostProcessorBeanFactoryPostProcessor@Beanstatic @Bean@Autowired@Valuespring-doc.cn

下面的示例展示了如何将一个 bean 自动连接到另一个 bean:spring-doc.cn

Java
@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");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    lateinit var accountRepository: AccountRepository

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig(private val dataSource: DataSource) {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
类中的构造函数注入仅从 Spring 开始受支持 框架 4.3.另请注意,如果 target Bean 只定义了一个构造函数。@Configuration@Autowired
完全合格的导入 bean 以方便导航

在前面的场景中,使用 效果很好,并提供所需的 模块化,但确定自动装配的 bean 定义的确切声明位置是 仍然有点模棱两可。例如,作为开发人员查看 ,如何 您确切知道 bean 的声明位置吗?事实并非如此 explicit 的 intent 函数,这可能就好了。请记住,Spring Tools for Eclipse 提供了以下工具 可以渲染显示所有内容是如何连接的图表,这可能就是您所需要的。也 您的 Java IDE 可以轻松找到 并快速显示返回该类型的方法的位置。@AutowiredServiceConfig@Autowired AccountRepositoryAccountRepository@Beanspring-doc.cn

如果这种歧义是不可接受的,并且您希望进行直接导航 在 IDE 中从一个类到另一个类,考虑自动装配 configuration 类本身。以下示例显示了如何执行此操作:@Configurationspring-doc.cn

Java
@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());
    }
}
Kotlin
@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

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

在上述情况下,where is defined 是完全显式的。 但是,现在与 紧密耦合。那就是 权衡。这种紧密耦合可以通过使用基于接口的 或 抽象基于类的类。请考虑以下示例:AccountRepositoryServiceConfigRepositoryConfig@Configurationspring-doc.cn

Java
@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");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

@Configuration
interface RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository
}

@Configuration
class DefaultRepositoryConfig : RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(...)
    }
}

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

    @Bean
    fun dataSource(): DataSource {
        // return DataSource
    }

}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

Now 相对于具体来说是松散耦合的,内置的 IDE 工具仍然很有用:您可以轻松地 获取实现的类型层次结构。在这个 方式,导航类及其依赖项也没有什么不同 而不是导航基于接口的代码的通常过程。ServiceConfigDefaultRepositoryConfigRepositoryConfig@Configurationspring-doc.cn

如果要影响某些 bean 的启动创建顺序,请考虑 将其中一些声明为 (用于在首次访问时创建,而不是在启动时创建) 或作为某些其他 bean (确保特定的其他 bean 是 在当前 bean 之前创建,超出了后者的直接依赖关系所暗示的范围)。@Lazy@DependsOn
有条件地包含类或方法@Configuration@Bean

有条件地启用或禁用完整类通常很有用 甚至是基于某个任意系统状态的单个方法。一个普通 这方面的示例是,仅在特定的 profile 已在 Spring 中启用(有关详细信息,请参见 Bean Definition Profiles)。@Configuration@Bean@ProfileEnvironmentspring-doc.cn

该 Comments 实际上是通过使用更灵活的 Comments 来实现的 称为 @Conditional。 该注解指示了应该 在注册 A 之前咨询。@Profile@Conditionalorg.springframework.context.annotation.Condition@Beanspring-doc.cn

接口的实现提供了一个返回 或 的方法。例如,下面的清单显示了用于:Conditionmatches(…​)truefalseCondition@Profilespring-doc.cn

Java
@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;
}
Kotlin
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    // Read the @Profile annotation attributes
    val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
    if (attrs != null) {
        for (value in attrs["value"]!!) {
            if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
                return true
            }
        }
        return false
    }
    return true
}

有关更多详细信息,请参阅 @Conditional javadoc。spring-doc.cn

组合 Java 和 XML 配置

Spring 的类支持并不旨在成为 100% 的完全替代品 用于 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是 配置容器。在 XML 方便或必要的情况下,您有一个 选择:要么以“以 XML 为中心”的方式实例化容器,例如使用 和 注释导入 XML,要么以“以 Java 为中心”的方式实例化容器 根据需要。@ConfigurationClassPathXmlApplicationContextAnnotationConfigApplicationContext@ImportResourcespring-doc.cn

以 XML 为中心的类使用@Configuration

最好从 XML 引导 Spring 容器,并以 ad-hoc 方式包含类。例如,在大型现有代码库中 ,则在 根据需要,并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍 在这种 “以 XML 为中心” 的情况下使用类的选项。@Configuration@Configuration@Configurationspring-doc.cn

将类声明为普通 Spring 元素@Configuration<bean/>

请记住,类最终是 容器。在本系列示例中,我们创建了一个名为 和 将其作为定义包含在 中。因为是打开的,所以容器会识别 Comments 并正确处理 Declaration 中声明的方法。@Configuration@ConfigurationAppConfigsystem-test-config.xml<bean/><context:annotation-config/>@Configuration@BeanAppConfigspring-doc.cn

以下示例显示了 Java 中的普通配置类:spring-doc.cn

Java
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

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

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
Kotlin
@Configuration
class AppConfig {

    @Autowired
    private lateinit var dataSource: DataSource

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun transferService() = TransferService(accountRepository())
}

以下示例显示了示例文件的一部分:system-test-config.xmlspring-doc.cn

<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.propertiesspring-doc.cn

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
fun main() {
    val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
    val transferService = ctx.getBean<TransferService>()
    // ...
}
在 file 中,它不会声明一个元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他 bean Ever 引用它,并且不太可能按名称从容器中显式获取它。 同样,bean 仅按类型自动装配,因此不严格要求显式 bean。system-test-config.xmlAppConfig<bean/>idDataSourceid
使用 <context:component-scan/> 选取类@Configuration

因为 is 元注释 , -annotated 类会自动成为组件扫描的候选对象。使用与 describe 在前面的示例中,我们可以重新定义以利用组件扫描。 请注意,在这种情况下,我们不需要显式声明 ,因为启用相同的 功能性。@Configuration@Component@Configurationsystem-test-config.xml<context:annotation-config/><context:component-scan/>spring-doc.cn

以下示例显示了修改后的文件:system-test-config.xmlspring-doc.cn

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

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

在类是配置 容器,则可能仍然需要至少使用一些 XML。在这些 方案中,您只能根据需要使用和定义任意数量的 XML。行为 因此,实现了一种“以 Java 为中心”的容器配置方法,并将 XML 保持为 最低 限度。以下示例(包括一个配置类、一个 XML 文件 ,它定义了一个 bean、一个属性文件和类)展示了如何使用 用于实现使用 XML 的“以 Java 为中心”配置的注释 根据需要:@Configuration@ImportResourcemain@ImportResourcespring-doc.cn

Java
@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);
    }
}
Kotlin
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {

    @Value("\${jdbc.url}")
    private lateinit var url: String

    @Value("\${jdbc.username}")
    private lateinit var username: String

    @Value("\${jdbc.password}")
    private lateinit var password: String

    @Bean
    fun dataSource(): DataSource {
        return 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
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    // ...
}

1.13. 环境抽象

Environment 界面 是集成在容器中的抽象,它对两个键进行建模 应用程序环境的各个方面:配置文件属性spring-doc.cn

配置文件是要注册到 容器。可以将 Bean 分配给配置文件 无论是在 XML 中定义还是使用注释定义。对象的角色 relation to profiles 用于确定当前处于活动状态的用户档案(如果有), 以及默认情况下应处于活动状态的配置文件(如果有)。Environmentspring-doc.cn

属性在几乎所有应用程序中都起着重要作用,可能源自 多种来源:属性文件、JVM 系统属性、系统环境 变量、JNDI、servlet 上下文参数、临时对象、对象等 上。对象与属性相关的角色是提供 具有便捷服务界面的用户,用于配置属性源和解析 属性。PropertiesMapEnvironmentspring-doc.cn

1.13.1. Bean 定义配置文件

Bean 定义配置文件在核心容器中提供了一种机制,该机制允许 在不同环境中注册不同的 bean。“环境”这个词 对不同的用户可能意味着不同的事情,而此功能可以帮助解决许多问题 使用案例,包括:spring-doc.cn

  • 在开发中处理内存中数据源与查找相同的数据源 datasource 的 JNDI 的 QA 或生产环境。spring-doc.cn

  • 仅在将应用程序部署到 性能环境。spring-doc.cn

  • 为客户 A 与客户注册 bean 的自定义实现 B 部署。spring-doc.cn

考虑实际应用程序中的第一个用例,它需要一个 .在测试环境中,配置可能类似于以下内容:DataSourcespring-doc.cn

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

现在考虑如何将此应用程序部署到 QA 或生产环境中 环境中,假设应用程序的数据源已注册 替换为生产应用程序服务器的 JNDI 目录。我们的 bean 现在如下面的清单所示:dataSourcespring-doc.cn

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

问题在于如何根据 当前环境。随着时间的推移,Spring 用户已经设计出了许多方法来 完成此操作,通常依赖于系统环境变量的组合 以及包含解析 添加到正确的配置文件路径,具体取决于环境的值 变量。Bean 定义配置文件是一个核心容器功能,它提供了一个 解决这个问题。<import/>${placeholder}spring-doc.cn

如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中没有。您可以说您想要注册一个 情况 A 中 bean 定义的某个配置文件和 情况 B.我们首先更新配置以反映此需求。spring-doc.cn

@Profile

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

Java
@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();
    }
}
Kotlin
@Configuration
@Profile("development")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
Java
@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");
    }
}
Kotlin
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
如前所述,对于方法,您通常会选择使用 Programmatic JNDI 查找,通过使用 Spring 的 / helpers 或 直接 JNDI 用法,而不是变体,这将强制您将返回类型声明为类型。@BeanJndiTemplateJndiLocatorDelegateInitialContextJndiObjectFactoryBeanFactoryBean

配置文件字符串可以包含简单的配置文件名称(例如,)或 profile 表达式。配置文件表达式允许更复杂的配置文件逻辑 表示(例如,)。支持以下运算符 配置文件表达式:productionproduction & us-eastspring-doc.cn

不能在不使用括号的情况下混合 and 运算符。例如,不是有效的表达式。它必须表示为 。&|production & us-east | eu-centralproduction & (us-east | eu-central)

您可以用作元注释 创建自定义组合注释。以下示例定义了一个自定义注释,您可以将其用作 :@Profile@Production@Profile("production")spring-doc.cn

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果一个类标有 ,则与该类关联的所有方法和注释都将被绕过,除非一个或多个 指定的配置文件处于活动状态。如果 a 或 类被标记为 使用 ,则不会注册或处理该类,除非 配置文件“P1”或“P2”已激活。如果给定配置文件的前缀为 NOT 运算符 (),则仅当配置文件未 积极。例如,给定 ,如果配置文件 “p1”处于活动状态,或者配置文件“p2”未处于活动状态。@Configuration@Profile@Bean@Import@Component@Configuration@Profile({"p1", "p2"})!@Profile({"p1", "!p2"})

@Profile也可以在方法级别声明以仅包含一个特定的 bean 配置类中(例如,对于特定 Bean 的替代变体),作为 以下示例显示:spring-doc.cn

Java
@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");
    }
}
1 该方法仅在配置文件中可用。standaloneDataSourcedevelopment
2 该方法仅在配置文件中可用。jndiDataSourceproduction
Kotlin
@Configuration
class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    fun standaloneDataSource(): DataSource {
        return 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)
    fun jndiDataSource() =
        InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 该方法仅在配置文件中可用。standaloneDataSourcedevelopment
2 该方法仅在配置文件中可用。jndiDataSourceproduction

使用 on 方法时,可能会应用特殊情况:如果 重载方法(类似于构造函数 overloading),则需要在 all 上一致地声明一个 condition 重载方法。如果条件不一致,则仅 重载方法中的第一个声明很重要。因此,可以 不用于选择具有特定参数签名的重载方法 另一个。同一 bean 的所有工厂方法之间的解析遵循 Spring 的 构造函数解析算法。@Profile@Bean@Bean@Profile@Profilespring-doc.cn

如果要定义具有不同性能分析条件的替代 bean, 使用指向同一 bean 名称的不同 Java 方法名称,方法是使用名称 属性,如前面的示例所示。如果参数签名全部为 相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的 首先在有效的 Java 类中表示这种排列的方式 (因为只能有一个特定名称和参数签名的方法)。@Beanspring-doc.cn

XML Bean 定义配置文件

XML 对应项是元素的属性。我们前面的示例 配置可以重写为两个 XML 文件,如下所示:profile<beans>spring-doc.cn

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

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

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

也可以避免在同一个文件中拆分和嵌套元素, 如下例所示:<beans/>spring-doc.cn

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

已限制为仅允许 文件中的最后一个。这应该有助于提供灵活性,而不会产生 XML 文件中的混乱。spring-bean.xsdspring-doc.cn

XML 对应项不支持前面描述的配置文件表达式。有可能, 但是,要使用运算符 .也可以应用逻辑 “and” 嵌套配置文件,如下例所示:!spring-doc.cn

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

在前面的示例中,如果 和 配置文件都处于活动状态,则会公开 Bean。dataSourceproductionus-eastspring-doc.cn

激活配置文件

现在我们已经更新了配置,我们仍然需要指示 Spring 哪个 配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到 a thrown,因为容器找不到 名为 的 Spring Bean 。NoSuchBeanDefinitionExceptiondataSourcespring-doc.cn

可以通过多种方式激活配置文件,但最直接的是 它以编程方式针对 API,该 API 可通过 .以下示例显示了如何执行此操作:EnvironmentApplicationContextspring-doc.cn

Java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
Kotlin
val ctx = AnnotationConfigApplicationContext().apply {
    environment.setActiveProfiles("development")
    register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
    refresh()
}

此外,您还可以通过属性以声明方式激活用户档案,该属性可通过系统环境指定 变量、JVM 系统属性、servlet 上下文参数,甚至作为 条目(请参阅 PropertySource Abstraction)。在集成测试中,active 可以使用模块中的 Comments 来声明配置文件(请参阅使用环境配置文件的上下文配置)。spring.profiles.activeweb.xml@ActiveProfilesspring-testspring-doc.cn

请注意,用户档案不是“非此即彼”的命题。您可以激活多个 配置文件。以编程方式,您可以向接受 varargs 的方法提供多个配置文件名称。以下示例 激活多个配置文件:setActiveProfiles()String…​spring-doc.cn

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

声明性地,可以接受逗号分隔的配置文件名称列表, 如下例所示:spring.profiles.activespring-doc.cn

    -Dspring.profiles.active="profile1,profile2"
默认配置文件

default 配置文件表示默认启用的配置文件。考虑一下 以下示例:spring-doc.cn

Java
@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();
    }
}
Kotlin
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}

如果没有配置文件处于活动状态,则会创建 。你可以看到这个 作为为一个或多个 bean 提供默认定义的一种方式。如果有 profile 时,默认配置文件不适用。dataSourcespring-doc.cn

您可以使用 on 更改默认配置文件的名称 或,以声明方式,通过使用属性。setDefaultProfiles()Environmentspring.profiles.defaultspring-doc.cn

1.13.2. 抽象PropertySource

Spring 的抽象通过可配置的 属性源的层次结构。请考虑以下清单:Environmentspring-doc.cn

Java
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);
Kotlin
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")

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

这些默认属性源可用于 ,以便在独立环境中使用 应用。StandardServletEnvironment 中填充了其他默认属性源,包括 servlet config 和 servlet context 参数。它可以选择启用 JndiPropertySource。 有关详细信息,请参阅 javadoc。StandardEnvironment

具体来说,当您使用 时,如果系统属性或环境变量位于 运行。StandardEnvironmentenv.containsProperty("my-property")my-propertymy-propertyspring-doc.cn

执行的搜索是分层的。默认情况下,系统属性优先于 环境变量。因此,如果在 对 的调用,系统属性值 “wins” 并返回。 请注意,属性值不会合并 而是完全被前面的条目覆盖。my-propertyenv.getProperty("my-property")spring-doc.cn

对于通用 ,完整的层次结构如下,其中 topest-precedence 条目:StandardServletEnvironmentspring-doc.cn

  1. ServletConfig 参数(如果适用 — 例如,在上下文的情况下)DispatcherServletspring-doc.cn

  2. ServletContext 参数(web.xml context-param 条目)spring-doc.cn

  3. JNDI 环境变量 ( entries)java:comp/env/spring-doc.cn

  4. JVM 系统属性(命令行参数)-Dspring-doc.cn

  5. JVM 系统环境(操作系统环境变量)spring-doc.cn

最重要的是,整个机制是可配置的。也许您有一个自定义源 要集成到此搜索中的属性。为此,请实现 并实例化你自己的 Git,并将其添加到 当前。以下示例显示了如何执行此操作:PropertySourcePropertySourcesEnvironmentspring-doc.cn

Java
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
Kotlin
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())

在前面的代码中,已在 搜索。如果它包含属性,则检测并返回该属性,优先使用 任何其他 .MutablePropertySources API 公开了许多方法,这些方法允许对 property 源。MyPropertySourcemy-propertymy-propertyPropertySourcespring-doc.cn

1.13.3. 使用@PropertySource

@PropertySource 注解提供了一种方便的声明性机制,用于将 a 添加到 Spring 的 .PropertySourceEnvironmentspring-doc.cn

给定一个包含键值对的 called 文件 ,则 下面的类以 对 return 的调用 :app.propertiestestbean.name=myTestBean@Configuration@PropertySourcetestBean.getName()myTestBeanspring-doc.cn

Java
@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;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

资源位置中存在的任何占位符都是 针对已针对 environment,如下例所示:${…​}@PropertySourcespring-doc.cn

Java
@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;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

假设 它已经存在于其中一个属性源中 registered (例如,系统属性或环境变量),则占位符为 resolved 的值。如果不是,则使用 作为默认值。如果未指定 default 且无法解析属性,则会引发 an。my.placeholderdefault/pathIllegalArgumentExceptionspring-doc.cn

根据 Java 8 约定,注释是可重复的。 但是,所有这些注解都需要在同一 级别,可以直接在配置类上,也可以作为 相同的自定义注释。混合直接注释和元注释不是 推荐,因为直接注释可以有效地覆盖元注释。@PropertySource@PropertySource

1.13.4. 语句中的占位符解析

从历史上看,元素中占位符的值只能针对 JVM 系统属性或环境变量。现在情况已不再如此。因为 抽象集成在整个容器中,很容易 通过它的 route resolution of placeholders 进行路由解析。这意味着您可以配置 解决过程。您可以更改搜索的优先级 系统属性和环境变量,或者完全删除它们。您还可以添加 自己的属性源添加到组合中。Environmentspring-doc.cn

具体来说,无论在何处定义属性,只要它在 :customerEnvironmentspring-doc.cn

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

1.14. 注册LoadTimeWeaver

Spring 使用 该 来动态转换类 加载到 Java 虚拟机 (JVM) 中。LoadTimeWeaverspring-doc.cn

要启用加载时编织,您可以将 添加到其中一个类中,如下例所示:@EnableLoadTimeWeaving@Configurationspring-doc.cn

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig

或者,对于 XML 配置,您可以使用以下元素:context:load-time-weaverspring-doc.cn

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

一旦为 配置了 ,其中的任何 bean 都可以实现 ,从而接收对加载时间的引用 Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是 对于 JPA 类转换是必需的。 有关更多详细信息,请查阅LocalContainerEntityManagerFactoryBean javadoc。有关 AspectJ 加载时编织的更多信息,请参见 Spring 框架中的使用 AspectJ 进行加载时编织ApplicationContextApplicationContextLoadTimeWeaverAwarespring-doc.cn

1.15. 其他功能ApplicationContext

章节介绍中所述,该包提供了用于管理和操作 bean 的基本功能,包括 编程方式。该包添加了 ApplicationContext 接口,该接口除了扩展了其他 接口,以便在更多应用程序中提供额外的功能 面向框架的样式。许多人完全使用 声明式方式,甚至不是以编程方式创建它,而是依赖于 支持类,例如在 Java EE Web 应用程序的正常启动过程中自动实例化 an 的一部分。org.springframework.beans.factoryorg.springframework.contextBeanFactoryApplicationContextContextLoaderApplicationContextspring-doc.cn

为了以更面向框架的样式增强功能,上下文 package 还提供以下功能:BeanFactoryspring-doc.cn

  • 通过界面访问 i18n 风格的消息。MessageSourcespring-doc.cn

  • 通过界面访问资源,例如 URL 和文件。ResourceLoaderspring-doc.cn

  • 事件发布,即向实现接口 通过使用界面。ApplicationListenerApplicationEventPublisherspring-doc.cn

  • 加载多个 (分层) 上下文,让每个上下文都专注于一个 特定层,例如应用程序的 Web 层,通过接口。HierarchicalBeanFactoryspring-doc.cn

1.15.1. 国际化使用MessageSource

该接口扩展了一个名为 and 的接口。 因此,提供国际化 (“i18n”) 功能。Spring 还提供了接口,该接口可以分层解析消息。 这些接口共同为 Spring effects 消息提供了基础 分辨率。在这些接口上定义的方法包括:ApplicationContextMessageSourceHierarchicalMessageSourcespring-doc.cn

  • String getMessage(String code, Object[] args, String default, Locale loc): 基本 用于从 .未找到消息时 对于指定的区域设置,将使用 default message。传入的任何参数都将变为 替换值,使用标准 图书馆。MessageSourceMessageFormatspring-doc.cn

  • String getMessage(String code, Object[] args, Locale loc):基本相同 前一种方法,但有一个区别:不能指定默认消息。如果 找不到消息,将引发 A。NoSuchMessageExceptionspring-doc.cn

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):所有属性 在上述方法中也包装在名为 的类中,您可以将其与此方法一起使用。MessageSourceResolvablespring-doc.cn

加载 an 时,它会自动搜索在上下文中定义的 bean。Bean 必须具有名称 。如果这样的 bean ,则所有对上述方法的调用都会委托给消息源。如果没有 message source 时,尝试查找包含 bean 的 bean 的 intent如果是这样,它将使用该 bean 作为 .如果找不到任何消息源,则会实例化一个空函数,以便能够接受对 方法。ApplicationContextMessageSourcemessageSourceApplicationContextMessageSourceApplicationContextDelegatingMessageSourcespring-doc.cn

Spring 提供了三种实现 、 和 。他们都为了实现嵌套 消息。这很少使用,但提供了编程方式 将消息添加到源。以下示例显示:MessageSourceResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSourceHierarchicalMessageSourceStaticMessageSourceResourceBundleMessageSourcespring-doc.cn

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

该示例假定您有三个名为 的资源包,并在 Classpath 中定义。任何解决消息的请求都是 以 JDK 标准方式通过对象解析消息进行处理。对于 本示例的目的,假设上述两个资源包文件的内容 如下:formatexceptionswindowsResourceBundlespring-doc.cn

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

下一个示例显示了一个运行该功能的程序。 请记住,所有 implementations 也是 implementations,因此可以强制转换为 interface。MessageSourceApplicationContextMessageSourceMessageSourcespring-doc.cn

Java
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);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
    println(message)
}

上述程序的结果输出如下:spring-doc.cn

Alligators rock!

总而言之,它在一个名为 的文件中定义,该文件 存在于 Classpath 的根目录中。bean 定义引用 通过其属性的资源包数。这三个文件是 在列表中传递给属性的 exists 作为文件位于 Classpath 和 分别称为 、 和 。MessageSourcebeans.xmlmessageSourcebasenamesbasenamesformat.propertiesexceptions.propertieswindows.propertiesspring-doc.cn

下一个示例显示了传递给消息查找的参数。这些参数是 转换为 Objects 并插入到 lookup 消息的占位符中。Stringspring-doc.cn

<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
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);
    }
}
Kotlin
    class Example {

    lateinit var messages: MessageSource

    fun execute() {
        val message = messages.getMessage("argument.required",
                arrayOf("userDao"), "Required", Locale.ENGLISH)
        println(message)
    }
}

调用该方法的结果输出如下:execute()spring-doc.cn

The userDao argument is required.

关于国际化(“i18n”),Spring 的各种实现遵循与标准 JDK 相同的语言环境解析和回退规则。简而言之,继续定义示例 以前,如果要针对英国 () 区域设置解析邮件,请使用 将分别创建名为 、 和 、 的文件。MessageSourceResourceBundlemessageSourceen-GBformat_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.propertiesspring-doc.cn

通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 resolved 手动指定:spring-doc.cn

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
Java
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);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("argument.required",
            arrayOf("userDao"), "Required", Locale.UK)
    println(message)
}

运行上述程序的结果输出如下:spring-doc.cn

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

您还可以使用该接口获取对已定义的任何内容的引用。在实现该接口的 an 中定义的任何 bean 都会注入 应用程序和配置 bean 时的应用程序上下文。MessageSourceAwareMessageSourceApplicationContextMessageSourceAwareMessageSourcespring-doc.cn

因为 Spring 的基于 Java 的,所以它不会合并 具有相同基本名称的捆绑包,但将仅使用找到的第一个捆绑包。 具有相同基本名称的后续消息包将被忽略。MessageSourceResourceBundle
作为 的替代方法,Spring 提供了一个类。此变体支持相同的捆绑包 文件格式,但比基于 JDK 的标准实现更灵活。特别是,它允许读取 文件(不仅来自 Classpath),并支持热 重新加载 bundle 属性文件(同时在两者之间有效地缓存它们)。 有关详细信息,请参见ReloadableResourceBundleMessageSource javadoc。ResourceBundleMessageSourceReloadableResourceBundleMessageSourceResourceBundleMessageSource

1.15.2. 标准事件和自定义事件

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

从 Spring 4.2 开始,活动基础设施得到了显著改进,并提供 基于注释的模型以及 能够发布任何任意事件(即,不一定 extend from (扩展自 )。当这样的对象被发布时,我们将其包装在 活动。ApplicationEvent

下表描述了 Spring 提供的标准事件:spring-doc.cn

表 7.内置事件
事件 解释

ContextRefreshedEventspring-doc.cn

在初始化或刷新时发布(例如,通过 使用界面上的方法)。 这里,“initialized” 表示加载所有 bean,检测到后处理器 bean 和 activated,则单例是预先实例化的,并且对象是 随时可用。只要上下文尚未关闭,就可以触发刷新 多次,前提是 chosen 实际上支持这样的 “hot” 刷新。例如,支持热刷新,但不支持。ApplicationContextrefresh()ConfigurableApplicationContextApplicationContextApplicationContextXmlWebApplicationContextGenericApplicationContextspring-doc.cn

ContextStartedEventspring-doc.cn

使用接口上的方法启动时发布。这里,“started” 表示所有 bean 都接收到显式的 start 信号。通常,此信号用于重新启动 bean ,但它也可用于启动尚未 配置为自动启动(例如,尚未在 初始化)。ApplicationContextstart()ConfigurableApplicationContextLifecyclespring-doc.cn

ContextStoppedEventspring-doc.cn

使用接口上的方法停止时发布。这里,“stopped”意味着所有 bean 都接收到显式的停止信号。可以通过调用重新启动已停止的上下文。ApplicationContextstop()ConfigurableApplicationContextLifecyclestart()spring-doc.cn

ContextClosedEventspring-doc.cn

使用方法关闭 时发布 在接口上或通过 JVM 关闭钩子。这里 “closed” 表示所有单例 bean 都将被销毁。关闭上下文后, 它已达到其生命周期的终点,无法刷新或重新启动。ApplicationContextclose()ConfigurableApplicationContextspring-doc.cn

RequestHandledEventspring-doc.cn

一个特定于 Web 的事件,告诉所有 bean HTTP 请求已得到处理。这 事件在请求完成后发布。此活动仅适用于 使用 Spring 的 .DispatcherServletspring-doc.cn

ServletRequestHandledEventspring-doc.cn

的子类添加特定于 Servlet 的上下文信息。RequestHandledEventspring-doc.cn

您还可以创建和发布自己的自定义事件。以下示例显示了 简单的类来扩展 Spring 的基类:ApplicationEventspring-doc.cn

Java
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...
}
Kotlin
class BlockedListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)

要发布自定义 ,请在 .通常,这是通过创建一个实现的类并将其注册为 Spring bean 来完成的。以下内容 example 显示了这样的类:ApplicationEventpublishEvent()ApplicationEventPublisherApplicationEventPublisherAwarespring-doc.cn

Java
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...
    }
}
Kotlin
class EmailService : ApplicationEventPublisherAware {

    private lateinit var blockedList: List<String>
    private lateinit var publisher: ApplicationEventPublisher

    fun setBlockedList(blockedList: List<String>) {
        this.blockedList = blockedList
    }

    override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
        this.publisher = publisher
    }

    fun sendEmail(address: String, content: String) {
        if (blockedList!!.contains(address)) {
            publisher!!.publishEvent(BlockedListEvent(this, address, content))
            return
        }
        // send email...
    }
}

在配置时, Spring 容器检测到 implements 并自动调用 .实际上,传入的参数是 Spring 容器本身。您正在通过其界面与应用程序上下文进行交互。EmailServiceApplicationEventPublisherAwaresetApplicationEventPublisher()ApplicationEventPublisherspring-doc.cn

要接收 custom ,您可以创建一个实现的类并将其注册为 Spring bean。以下示例 显示了这样的类:ApplicationEventApplicationListenerspring-doc.cn

Java
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...
    }
}
Kotlin
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {

    lateinit var notificationAddres: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意,它通常使用 your 自定义事件(在前面的示例中)。这意味着该方法可以保持类型安全,无需进行向下转换。 您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,event 侦听器同步接收事件。这意味着 块,直到所有侦听器都处理完事件。这样做的一个优势 同步和单线程方法是,当侦听器接收到事件时,它会 在发布者的事务上下文中运行(如果事务上下文为 可用。如果需要其他事件发布策略,请参阅 javadoc 对于 Spring 的 ApplicationEventMulticaster 接口 和 SimpleApplicationEventMulticaster implementation 来获取配置选项。ApplicationListenerBlockedListEventonApplicationEvent()publishEvent()spring-doc.cn

以下示例显示了用于注册和配置每个 上面的类:spring-doc.cn

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

把它们放在一起,当 bean 的方法是 调用,如果存在任何应阻止的电子邮件,则发布 Custom Event 类型。bean 注册为 an 并接收 ,此时它可以 通知相关方。sendEmail()emailServiceBlockedListEventblockedListNotifierApplicationListenerBlockedListEventspring-doc.cn

Spring 的事件机制是为 Spring bean 之间的简单通信而设计的 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring 集成项目提供了 完全支持构建轻量级、面向模式、事件驱动的 构建在众所周知的 Spring 编程模型之上的架构。
基于注释的事件侦听器

您可以使用注释在托管 Bean 的任何方法上注册事件侦听器。可以按如下方式重写:@EventListenerBlockedListNotifierspring-doc.cn

Java
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...
    }
}
Kotlin
class BlockedListNotifier {

    lateinit var notificationAddress: String

    @EventListener
    fun processBlockedListEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

方法签名再次声明它监听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的侦听器接口。 事件类型也可以通过泛型缩小范围,只要实际的事件类型 在其 implementation hierarchy 中解析泛型参数。spring-doc.cn

如果你的方法应该监听多个事件,或者你想用 no 参数,也可以在 Annotation 本身上指定事件类型。这 以下示例显示了如何执行此操作:spring-doc.cn

Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
Kotlin
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}

还可以使用属性 定义 SPEL 表达式的注释中,该表达式应匹配 以实际调用特定事件的方法。conditionspring-doc.cn

以下示例显示了如何重写我们的通知器,以便仅在事件的属性等于 :contentmy-eventspring-doc.cn

Java
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}
Kotlin
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}

每个表达式都根据专用上下文进行计算。下表列出了 项,以便您可以将它们用于条件事件处理:SpELspring-doc.cn

表 8.事件 SPEL 可用元数据
名字 位置 描述

事件spring-doc.cn

root 对象spring-doc.cn

实际的 .ApplicationEventspring-doc.cn

#root.eventeventspring-doc.cn

Arguments 数组spring-doc.cn

root 对象spring-doc.cn

用于调用方法的参数(作为对象数组)。spring-doc.cn

#root.args或; 访问第一个参数,依此类推。argsargs[0]spring-doc.cn

参数名称spring-doc.cn

评估上下文spring-doc.cn

任何方法参数的名称。如果由于某种原因,名称不可用 (例如,因为编译后的字节码中没有调试信息),单个 参数也可以使用语法 where 表示 argument index (从 0 开始)。#a<#arg><#arg>spring-doc.cn

#blEventor(您也可以使用 OR 参数表示法作为别名)#a0#p0#p<#arg>spring-doc.cn

请注意,这允许您访问基础事件,即使您的方法 signature 实际上是指已发布的任意对象。#root.eventspring-doc.cn

如果您需要发布事件作为处理其他事件的结果,则可以更改 method signature 返回应发布的事件,如下例所示:spring-doc.cn

Java
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
Kotlin
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。

该方法为它处理的每个 FOR EACH 发布一个 new。如果需要发布多个事件,可以返回 a 或事件数组。handleBlockedListEvent()ListUpdateEventBlockedListEventCollectionspring-doc.cn

异步侦听器

如果您希望特定侦听器异步处理事件,则可以重用常规@Async支持。 以下示例显示了如何执行此操作:spring-doc.cn

Java
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
Kotlin
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
    // BlockedListEvent is processed in a separate thread
}

使用异步事件时,请注意以下限制:spring-doc.cn

对侦听器进行排序

如果需要在调用另一个侦听器之前调用另一个侦听器,则可以将 Comments 添加到方法声明中,如下例所示:@Orderspring-doc.cn

Java
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
Kotlin
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
泛型事件

您还可以使用泛型来进一步定义事件的结构。考虑使用 where 是创建的实际实体的类型。例如,您 可以创建以下侦听器定义以仅接收 :EntityCreatedEvent<T>TEntityCreatedEventPersonspring-doc.cn

Java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Kotlin
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}

由于类型擦除,仅当触发的事件解析泛型 事件侦听器过滤的参数(即类似 )。class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }spring-doc.cn

在某些情况下,如果所有事件都遵循相同的 结构(如上例中的事件所示)。在这种情况下, 您可以实现以指导框架超越运行时 环境提供。以下事件演示如何执行此操作:ResolvableTypeProviderspring-doc.cn

Java
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()));
    }
}
Kotlin
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {

    override fun getResolvableType(): ResolvableType? {
        return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
    }
}
这不仅适用于您作为 一个事件。ApplicationEvent

1.15.3. 方便地访问低级资源

为了最佳地使用和理解应用程序上下文,您应该熟悉 自己使用 Spring 的抽象,如 参考资料.Resourcespring-doc.cn

应用程序上下文是 ,可用于加载对象。 A 实质上是 JDK 类的功能更丰富的版本。 实际上,包装的实现是 , 其中 适当。A 可以从 透明方式,包括从 Classpath、文件系统位置、任何位置 decpreable 替换为标准 URL 和其他一些变体。如果资源位置 string 是一个没有任何特殊前缀的简单路径,这些资源的来源是 特定于实际的应用程序上下文类型。ResourceLoaderResourceResourcejava.net.URLResourcejava.net.URLResourcespring-doc.cn

您可以配置部署到应用程序上下文中的 Bean 来实现特殊的 回调接口 ,在 初始化时间,应用程序上下文本身作为 . 您还可以公开 type 的属性 ,以用于访问静态资源。 它们像任何其他属性一样被注入其中。您可以将这些属性指定为简单路径,并依赖于这些文本的自动转换 strings 添加到实际对象中。ResourceLoaderAwareResourceLoaderResourceResourceStringResourcespring-doc.cn

提供给构造函数的一个或多个位置路径实际上是 资源字符串,并且以简单形式,根据特定的 context 实现。例如,将简单的 location path 作为 Classpath 位置。您还可以使用位置路径(资源字符串) 替换为特殊前缀来强制从 Classpath 或 URL 加载定义, 无论实际的上下文类型如何。ApplicationContextClassPathXmlApplicationContextspring-doc.cn

1.15.4. Web 应用程序的便捷 ApplicationContext 实例化

例如,您可以使用 .当然,您也可以创建实例 使用其中一个实现以编程方式。ApplicationContextContextLoaderApplicationContextApplicationContextspring-doc.cn

您可以使用 注册 ,因为 以下示例显示:ApplicationContextContextLoaderListenerspring-doc.cn

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

侦听器检查参数。如果参数不 存在,则侦听器将其用作默认值。当 参数确实存在,则侦听器会使用 predefined 分隔符(逗号、分号和空格),并将值用作其中 搜索应用程序上下文。还支持 Ant 样式的路径模式。 示例包括 (对于名称以 结尾且位于目录中的所有文件) 和 (对于任何子目录中的所有此类文件)。contextConfigLocation/WEB-INF/applicationContext.xmlString/WEB-INF/*Context.xmlContext.xmlWEB-INF/WEB-INF/**/*Context.xmlWEB-INFspring-doc.cn

1.15.5. 将 Spring 部署为 Java EE RAR 文件ApplicationContext

可以将 Spring 部署为 RAR 文件,将 context 及其所有必需的 bean 类和库 JAR 在 Java EE RAR 部署中 单位。这相当于引导一个独立的(仅托管的 在 Java EE 环境中)能够访问 Java EE 服务器工具。RAR 部署 是部署无头 WAR 文件方案的更自然的替代方案 — 实际上, 一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中引导 Spring。ApplicationContextApplicationContextApplicationContextspring-doc.cn

RAR 部署非常适合不需要 HTTP 入口点但 而是由消息终端节点和计划作业组成。在这种情况下,bean 可以 使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBC 实例和 JMS 实例,并且还可以注册 平台的 JMX 服务器 — 全部通过 Spring 的标准事务管理和 JNDI 和 JMX 支持工具。应用程序组件还可以与应用程序交互 server 的 JCA 通过 Spring 的抽象。DataSourceConnectionFactoryWorkManagerTaskExecutorspring-doc.cn

有关 RAR 部署中涉及的配置详细信息,请参见SpringContextResourceAdapter类的 javadoc。spring-doc.cn

要将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:spring-doc.cn

  1. 包 所有应用程序类都合并到一个 RAR 文件(这是一个标准、JAR 文件,具有不同的 文件扩展名)。spring-doc.cn

  2. 将所有必需的库 JAR 添加到 RAR 存档的根目录中。spring-doc.cn

  3. 添加部署描述符(如 SpringContextResourceAdapter 的 javadoc 中所示) 和相应的 Spring XML bean 定义文件(通常)。META-INF/ra.xmlMETA-INF/applicationContext.xmlspring-doc.cn

  4. 将生成的 RAR 文件拖放到 Application Server 的部署目录。spring-doc.cn

此类 RAR 部署单元通常是独立的。它们不暴露组件 对外界,甚至对同一应用程序的其他模块也不例外。与 基于 RAR 的 RAR 通常通过与之共享的 JMS 目标进行 其他模块。例如,基于 RAR 的 RAR 还可以安排一些作业 或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步 从外部访问,它可以(例如)导出 RMI 端点,这些端点可以使用 通过同一台计算机上的其他应用程序模块。ApplicationContextApplicationContext

1.16. 使用BeanFactory

API 为 Spring 的 IoC 功能提供了基础。 它的特定 Contract 主要用于与 Spring 和 相关的第三方框架及其实现 是更高级别容器中的关键委托。BeanFactoryDefaultListableBeanFactoryGenericApplicationContextspring-doc.cn

BeanFactory和相关接口(如 、 、 )是其他框架组件的重要集成点。 通过不需要任何注释甚至反射,它们允许非常高效 容器与其组件之间的交互。应用程序级 bean 可以 使用相同的回调接口,但通常更喜欢声明性依赖项 注入,而是通过 Comments 或编程配置。BeanFactoryAwareInitializingBeanDisposableBeanspring-doc.cn

请注意,核心 API 级别及其实现不会对配置格式或任何 要使用的组件注释。所有这些风格都来自扩展 (例如 和 )和 对共享库进行操作,将其作为核心元数据表示形式。 这就是 Spring 的容器如此灵活和可扩展的本质。BeanFactoryDefaultListableBeanFactoryXmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessorBeanDefinitionspring-doc.cn

1.16.1. 或 ?BeanFactoryApplicationContext

本节介绍 和 容器级别之间的差异以及对引导程序的影响。BeanFactoryApplicationContextspring-doc.cn

除非你有充分的理由不这样做,否则你应该使用 an 及其子类作为自定义引导的常见实现。这些是主要条目 指向 Spring 的核心容器,用于所有常见目的:加载配置 文件, 触发类路径扫描, 以编程方式注册 Bean 定义 和带 Comments 的类,以及(从 5.0 开始)注册函数式 bean 定义。ApplicationContextGenericApplicationContextAnnotationConfigApplicationContextspring-doc.cn

因为 an 包括 的所有功能 ,所以它是 一般推荐 平原 ,但 full 需要控制 Bean 处理。在 an(例如实现)中,检测到多种 bean 按约定(即按 Bean 名称或 Bean 类型 — 特别是后处理器), 而 Plain 对任何特殊的豆子都是不可知的。ApplicationContextBeanFactoryBeanFactoryApplicationContextGenericApplicationContextDefaultListableBeanFactoryspring-doc.cn

对于许多扩展容器功能,例如注释处理和 AOP 代理, BeanPostProcessor 扩展点是必不可少的。 如果仅使用 plain ,则此类后处理器不会 默认情况下被检测并激活。这种情况可能会令人困惑,因为 您的 bean 配置实际上没有任何问题。相反,在这种情况下, 容器需要通过其他设置完全引导。DefaultListableBeanFactoryspring-doc.cn

下表列出了 和 interfaces 和实现提供的功能。BeanFactoryApplicationContextspring-doc.cn

表 9.特征矩阵
特征 BeanFactory ApplicationContext

Bean 实例化/连接spring-doc.cn

是的spring-doc.cn

是的spring-doc.cn

集成的生命周期管理spring-doc.cn

spring-doc.cn

是的spring-doc.cn

自动注册BeanPostProcessorspring-doc.cn

spring-doc.cn

是的spring-doc.cn

自动注册BeanFactoryPostProcessorspring-doc.cn

spring-doc.cn

是的spring-doc.cn

便捷访问(用于国际化)MessageSourcespring-doc.cn

spring-doc.cn

是的spring-doc.cn

内置发布机制ApplicationEventspring-doc.cn

spring-doc.cn

是的spring-doc.cn

要使用 , 您需要以编程方式调用 ,如下例所示:DefaultListableBeanFactoryaddBeanPostProcessorspring-doc.cn

Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
Kotlin
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())

// now start using the factory

要将 a 应用于普通 , 您需要调用其方法,如下例所示:BeanFactoryPostProcessorDefaultListableBeanFactorypostProcessBeanFactoryspring-doc.cn

Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
Kotlin
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))

// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))

// now actually do the replacement
cfg.postProcessBeanFactory(factory)

在这两种情况下,显式注册步骤都很不方便,即 为什么在 Spring 支持的应用程序中,各种变体比 plain 更受欢迎,尤其是当 依赖 和 实例进行扩展 典型企业设置中的容器功能。ApplicationContextDefaultListableBeanFactoryBeanFactoryPostProcessorBeanPostProcessorspring-doc.cn

An 具有所有常见的 Annotation 后处理器 已注册,并可能在 覆盖配置注释,例如 . 在 Spring 基于 Comments 的配置模型的抽象层, Bean 后处理器的概念变成了一个纯粹的内部容器细节。AnnotationConfigApplicationContext@EnableTransactionManagementspring-doc.cn

2. 资源

本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:spring-doc.cn

2.1. 简介

Java 的标准类和各种 URL 前缀的标准处理程序, 不幸的是,对于所有对低级资源的访问来说,这还不够。为 示例中,没有可用于访问 需要从 Classpath 获取的资源或相对于 .虽然可以为专用前缀注册新的处理程序(类似于前缀的现有处理程序,例如 ),这通常是 相当复杂,并且界面仍然缺乏一些理想的功能, 例如,用于检查所指向的资源是否存在的方法。java.net.URLURLServletContextURLhttp:URLspring-doc.cn

2.2. 资源接口

Spring 的接口旨在成为一个功能更强大的抽象接口 访问低级资源。下面的清单显示了接口 定义:ResourceResourcespring-doc.cn

Java
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
Kotlin
interface Resource : InputStreamSource {

    fun exists(): Boolean

    val isOpen: Boolean

    val url: URL

    val file: File

    @Throws(IOException::class)
    fun createRelative(relativePath: String): Resource

    val filename: String

    val description: String
}

如接口的定义所示,它扩展了接口。下面的清单显示了接口的定义:ResourceInputStreamSourceInputStreamSourcespring-doc.cn

Java
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}
Kotlin
interface InputStreamSource {

    val inputStream: InputStream
}

界面中一些最重要的方法是:Resourcespring-doc.cn

  • getInputStream():查找并打开资源,返回 for 从资源中读取。预计每次调用都会返回一个新的 .调用方负责关闭流。InputStreamInputStreamspring-doc.cn

  • exists():返回一个 物理形式。booleanspring-doc.cn

  • isOpen():返回一个 tag,指示此资源是否表示句柄 与开放的流。如果 ,则不能多次读取 ,并且 必须只读取一次,然后关闭以避免资源泄漏。的返回 所有常用的资源实现,除了 .booleantrueInputStreamfalseInputStreamResourcespring-doc.cn

  • getDescription():返回此资源的说明,用于错误 output 来执行。这通常是完全限定的文件名或 资源的实际 URL。spring-doc.cn

其他方法允许您获取表示 资源(如果底层实现兼容并支持 功能)。URLFilespring-doc.cn

Spring 本身广泛使用抽象,作为 需要资源时有许多方法签名。某些 Spring API 中的其他方法 (例如各种实现的构造函数)采用一个 THAT,它以朴素或简单的形式用于创建适当的 该上下文实现,或者通过路径上的特殊前缀,让 caller 指定必须创建和使用特定实现。ResourceApplicationContextStringResourceStringResourcespring-doc.cn

虽然该接口与 Spring 和 Spring 一起使用很多,但它实际上是 在您自己的代码中单独用作通用工具类非常有用,用于访问 资源,即使您的代码不知道或不关心 Spring 的任何其他部分。 虽然这会将您的代码耦合到 Spring,但它实际上只将其耦合到这一小群 实用程序类,这些类可以作为 被认为等同于您用于此目的的任何其他库。ResourceURLspring-doc.cn

抽象不会取代功能。 它会尽可能地包装它。例如,a 包装一个 URL 并使用 wrapped 来执行其工作。ResourceUrlResourceURL

2.3. 内置资源实现

Spring 包括以下实现:Resourcespring-doc.cn

2.3.1.UrlResource

UrlResource包装 a 并可用于访问任何 通常可通过 URL 访问,例如文件、HTTP 目标、FTP 目标等。都 URL 具有标准化的表示形式,因此适当的标准化 前缀用于指示一种 URL 类型与另一种 URL 类型。这包括 访问文件系统路径、通过 HTTP 协议访问资源、通过 FTP 访问资源等。java.net.URLStringfile:http:ftp:spring-doc.cn

A 由 Java 代码显式使用构造函数创建 但通常在调用采用用于表示路径的参数的 API 方法时隐式创建。对于后一种情况,JavaBeans 最终决定创建哪种类型。如果路径 string 包含众所周知的(对它来说,即)前缀(例如 ),它 为该前缀创建适当的专用。但是,如果它没有 识别前缀,则假定该字符串是标准 URL 字符串,并且 创建一个 .UrlResourceUrlResourceStringPropertyEditorResourceclasspath:ResourceUrlResourcespring-doc.cn

2.3.2.ClassPathResource

此类表示应从 Classpath 获取的资源。它使用 线程上下文类加载器、给定类加载器或 loading resources.spring-doc.cn

此实现支持解析,就像类路径 资源驻留在文件系统中,但不适用于驻留在 jar 中,并且尚未扩展(通过 servlet 引擎或任何环境) 添加到文件系统中。为了解决这个问题,各种 implementations 始终支持 分辨率设置为 .Resourcejava.io.FileResourcejava.net.URLspring-doc.cn

A 是由 Java 代码通过显式使用构造函数创建的,但通常是在调用采用用于表示路径的参数的 API 方法时隐式创建的。对于后一种情况,JavaBeans 识别字符串路径上的特殊前缀 , 和 在这种情况下创建一个。ClassPathResourceClassPathResourceStringPropertyEditorclasspath:ClassPathResourcespring-doc.cn

2.3.3.FileSystemResource

这是 和 handles 的实现。 它支持分辨率为 a 和 .Resourcejava.io.Filejava.nio.file.PathFileURLspring-doc.cn

2.3.4.ServletContextResource

这是解释 相关 Web 应用程序根目录中的相对路径。ResourceServletContextspring-doc.cn

它始终支持流访问和 URL 访问,但仅允许访问 当 Web 应用程序存档扩展并且资源物理位于 文件系统。无论它是否被扩展、在文件系统上或被访问 直接从 JAR 或其他位置(如数据库)实际上是 依赖于 Servlet 容器。java.io.Filespring-doc.cn

2.3.5.InputStreamResource

An 是给定 .只有在没有 具体实施适用。特别是,在可能的情况下,prefers 或任何基于文件的实现。InputStreamResourceResourceInputStreamResourceByteArrayResourceResourcespring-doc.cn

与其他实现相反,这是对已打开的 资源。因此,它从 返回 。如果需要,请勿使用 将资源描述符保留在某个位置,或者如果您需要读取多个流 次。ResourcetrueisOpen()spring-doc.cn

2.3.6.ByteArrayResource

这是给定字节数组的实现。它为给定的字节数组创建一个。ResourceByteArrayInputStreamspring-doc.cn

它对于从任何给定的字节数组加载内容非常有用,而不必求助于 一次性使用。InputStreamResourcespring-doc.cn

2.4. 使用ResourceLoader

该接口旨在由可以返回 (即 load)实例。下面的清单显示了接口定义:ResourceLoaderResourceResourceLoaderspring-doc.cn

Java
public interface ResourceLoader {

    Resource getResource(String location);
}
Kotlin
interface ResourceLoader {

    fun getResource(location: String): Resource
}

所有应用程序上下文都实现该接口。因此,所有 应用程序上下文可用于获取实例。ResourceLoaderResourcespring-doc.cn

当您调用特定的应用程序上下文时,位置路径 specified 没有特定的前缀,则返回一个 适合该特定应用程序上下文。例如,假设以下内容 针对实例运行的代码片段:getResource()ResourceClassPathXmlApplicationContextspring-doc.cn

Java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("some/resource/path/myTemplate.txt")

对于 ,该代码返回一个 .如果运行相同的方法 对于实例,它将返回一个 .对于 a ,它将返回一个 .它同样会为每个上下文返回适当的对象。ClassPathXmlApplicationContextClassPathResourceFileSystemXmlApplicationContextFileSystemResourceWebApplicationContextServletContextResourcespring-doc.cn

因此,您可以以适合特定应用程序的方式加载资源 上下文。spring-doc.cn

另一方面,您也可以强制使用 application context 类型,通过指定特殊前缀,如下所示 示例显示:ClassPathResourceclasspath:spring-doc.cn

Java
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")

同样,您可以通过指定任何标准前缀来强制使用 a。以下一对示例使用 and 前缀:UrlResourcejava.net.URLfilehttpspring-doc.cn

Java
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
Java
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")

下表总结了将对象转换为对象的策略:StringResourcespring-doc.cn

表 10.资源字符串
前缀 解释

类路径:spring-doc.cn

classpath:com/myapp/config.xmlspring-doc.cn

从 Classpath 加载。spring-doc.cn

文件:spring-doc.cn

file:///data/config.xmlspring-doc.cn

作为 从文件系统加载。另请参阅 FileSystemResource 注意事项URLspring-doc.cn

http:spring-doc.cn

https://myserver/logo.pngspring-doc.cn

加载为 .URLspring-doc.cn

(无)spring-doc.cn

/data/config.xmlspring-doc.cn

取决于底层 .ApplicationContextspring-doc.cn

2.5. 界面ResourceLoaderAware

该接口是一个特殊的回调接口,用于标识 组件。以下内容 清单显示了接口的定义:ResourceLoaderAwareResourceLoaderResourceLoaderAwarespring-doc.cn

Java
public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}
Kotlin
interface ResourceLoaderAware {

    fun setResourceLoader(resourceLoader: ResourceLoader)
}

当类实现并部署到应用程序上下文中时 (作为 Spring 管理的 bean)中,它被应用程序识别为 上下文。然后,应用程序上下文调用 , 将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现了 界面)。ResourceLoaderAwareResourceLoaderAwaresetResourceLoader(ResourceLoader)ResourceLoaderspring-doc.cn

由于 an 是 ,因此 bean 还可以实现接口并使用提供的应用程序上下文直接 load 资源。但是,一般来说,如果这就是您所需要的,最好使用专用界面。该代码将仅与资源加载耦合 接口(可以被认为是一个 Util 接口),而不是整个 Spring 接口。ApplicationContextResourceLoaderApplicationContextAwareResourceLoaderApplicationContextspring-doc.cn

在应用程序组件中,您还可以依赖 as 的自动装配 实现接口的替代方法。“传统”模式和自动装配模式(如自动装配协作者中所述) 能够为 constructor 参数或 setter 方法参数。为了获得更大的灵活性(包括 autowire fields 和多个参数方法),请考虑使用基于注释的 自动装配功能。在这种情况下,它被自动连接到一个字段 constructor 参数或期望类型为 long 的方法参数 因为有问题的 field、constructor 或 method 带有 Annotation。 有关更多信息,请参阅使用 @AutowiredResourceLoaderResourceLoaderAwareconstructorbyTypeResourceLoaderResourceLoaderResourceLoader@Autowiredspring-doc.cn

2.6. 资源作为依赖项

如果 Bean 本身将通过某种排序来确定和提供资源路径 动态进程中,bean 使用该接口来加载资源可能是有意义的。例如,考虑加载一些 sort,其中所需的特定资源取决于用户的角色。如果 资源是静态的,因此完全消除接口的使用是有意义的,让 bean 公开它需要的属性, 并期望他们被注入其中。ResourceLoaderResourceLoaderResourcespring-doc.cn

然后注入这些属性变得微不足道的是,所有应用程序上下文 注册并使用一个特殊的 JavaBeans ,它可以转换路径 到对象。因此,如果具有 type 为 的 template 属性,则可以 为该资源配置一个简单的字符串,如下例所示:PropertyEditorStringResourcemyBeanResourcespring-doc.cn

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

请注意,资源路径没有前缀。因此,因为应用程序上下文本身是 将用作 ,资源本身通过 、 、 或 、 加载 、 取决于上下文的确切类型。ResourceLoaderClassPathResourceFileSystemResourceServletContextResourcespring-doc.cn

如果需要强制使用特定类型,可以使用前缀。 以下两个示例显示了如何强制 a 和 a (后者用于访问文件系统文件):ResourceClassPathResourceUrlResourcespring-doc.cn

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

2.7. 应用程序上下文和资源路径

本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 ,以及如何使用 XML、如何使用通配符和其他详细信息。spring-doc.cn

2.7.1. 构造应用程序上下文

应用程序上下文构造函数(针对特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。spring-doc.cn

当此类位置路径没有前缀时,从 该路径 和 用于加载 bean 定义的路径取决于 并且适合于 特定的应用程序上下文。例如,请考虑以下示例,该示例创建一个 :ResourceClassPathXmlApplicationContextspring-doc.cn

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")

Bean 定义是从 Classpath 加载的,因为 使用。但是,请考虑以下示例,该示例创建一个 :ClassPathResourceFileSystemXmlApplicationContextspring-doc.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")

现在,bean 定义是从文件系统位置加载的(在本例中,相对于 当前工作目录)。spring-doc.cn

请注意,在 location path 会覆盖 created 的默认类型,以加载 定义。请考虑以下示例:Resourcespring-doc.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")

Using 从 Classpath 中加载 bean 定义。但是,它仍然是 .如果它随后用作 ,则任何 无前缀的路径仍被视为文件系统路径。FileSystemXmlApplicationContextFileSystemXmlApplicationContextResourceLoaderspring-doc.cn

构造实例 — 快捷方式ClassPathXmlApplicationContext

这公开了许多构造函数以启用 方便的实例化。基本思想是,您可以只提供一个字符串数组 ,仅包含 XML 文件本身的文件名(没有前导路径 信息),并且还提供 .then 从提供的类中派生路径信息。ClassPathXmlApplicationContextClassClassPathXmlApplicationContextspring-doc.cn

请考虑以下目录布局:spring-doc.cn

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

下面的示例展示了由 名为 and 的文件(在 Classpath 上)可以实例化:ClassPathXmlApplicationContextservices.xmldaos.xmlspring-doc.cn

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);
Kotlin
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java)

有关各种构造函数的详细信息,请参见ClassPathXmlApplicationContext javadoc。spring-doc.cn

2.7.2. 应用程序上下文构造函数资源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径(如 前面所示),每个 Target(或可能)都有 包含特殊的 “classpath*:” 前缀或内部 Ant 样式的正则表达式 (通过使用 Spring 的 Util 进行匹配)。后者两者都是有效的 通配符。ResourcePathMatcherspring-doc.cn

此机制的一个用途是当您需要执行组件样式的应用程序组装时。都 组件可以将上下文定义片段“发布”到已知的位置路径,并且 当使用前缀为 的相同路径创建最终应用程序上下文时,会自动选取所有组件片段。classpath*:spring-doc.cn

请注意,此通配符特定于应用程序上下文中资源路径的使用 构造函数(或者直接使用实用程序类层次结构时),并且是 在构建时解决。它与类型本身无关。 您不能使用前缀来构造实际的 ,因为 一个资源一次只指向一个资源。PathMatcherResourceclasspath*:Resourcespring-doc.cn

Ant-style 模式

路径位置可以包含 Ant 样式模式,如下例所示:spring-doc.cn

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

当路径位置包含 Ant 样式模式时,解析程序会遵循更复杂的过程来尝试解析 通配符。它会为直到最后一个非通配符分段的路径生成一个 从中获取 URL。如果此 URL 不是 URL 或特定于容器的变体 (例如在 WebLogic、WebSphere 等中),则 a 是 从中获取,并用于通过遍历文件系统来解析通配符。在 如果是 jar URL,解析器要么从它那里获得 a 要么 手动解析 jar URL,然后遍历 jar 文件的内容以解析 通配符。Resourcejar:zip:wsjarjava.io.Filejava.net.JarURLConnectionspring-doc.cn

对可移植性的影响

如果指定的路径已经是文件 URL(隐式,因为基是文件系统 URL,或者显式地),则通配符保证为 以完全便携的方式工作。ResourceLoaderspring-doc.cn

如果指定的路径是 Classpath 位置,则解析程序必须获取最后一个 非通配符路径段 URL。由于这个 只是路径的一个节点(而不是末尾的文件),实际上它未定义(在 javadoc 中)在这种情况下返回的 URL 类型。在实践中, 它始终表示目录(其中 Classpath 资源 解析为文件系统位置)或某种类型的 jar URL(其中 Classpath 资源 解析为 jar 位置)。尽管如此,此操作仍然存在可移植性问题。Classloader.getResource()ClassLoaderjava.io.Filespring-doc.cn

如果获取了最后一个非通配符段的 jar URL,则解析程序必须能够 从中获取 a 或手动解析 jar URL,以便能够 遍历 jar 的内容并解析通配符。这在大多数环境中都有效 但在其他 API 中失败,我们强烈建议 resources 的通配符解析 来自 jars 在您依赖它之前,请在您的特定环境中对其进行全面测试。java.net.JarURLConnectionspring-doc.cn

前缀classpath*:

在构建基于 XML 的应用程序上下文时,位置字符串可以使用 特殊前缀,如下例所示:classpath*:spring-doc.cn

Java
ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")

此特殊前缀指定与给定名称匹配的所有 Classpath 资源 必须获取(在内部,这基本上是通过调用 发生 ),然后合并以形成最终应用程序 context 定义。ClassLoader.getResources(…​)spring-doc.cn

通配符类路径依赖于底层 classloader 中。由于现在大多数应用程序服务器都提供自己的类加载器 实现时,行为可能会有所不同,尤其是在处理 JAR 文件时。一个 检查是否有效的简单测试是使用 Classloader 从 在 Classpath 上的 jar 中: .尝试此测试 具有相同名称但放置在两个不同位置的文件。如果 不适当的结果,请查看 Application Server 文档以获取 可能影响 ClassLoader 行为的设置。getResources()classpath*getClass().getClassLoader().getResources("<someFileInsideTheJar>")

您还可以将前缀与 位置路径的其余部分(例如 )。在这个 的情况下,解析策略相当简单:调用 用于获取 类加载器层次结构,然后从每个资源中使用相同的分辨率 前面描述的策略用于通配符子路径。classpath*:PathMatcherclasspath*:META-INF/*-beans.xmlClassLoader.getResources()PathMatcherspring-doc.cn

与通配符相关的其他说明

请注意,当与 Ant 样式模式结合使用时,仅 可靠地使用至少一个根目录,除非实际的 目标文件驻留在文件系统中。这意味着诸如 jar 文件的根目录之类的模式可能不会从 jar 文件的根目录中检索文件,而只会从 从扩展目录的根目录。classpath*:classpath*:*.xmlspring-doc.cn

Spring 检索 Classpath 条目的能力源自 JDK 的方法,该方法仅返回 空字符串(指示要搜索的潜在根)。Spring 评估运行时配置和 jar 文件中的清单 ,但不能保证这会导致可移植行为。ClassLoader.getResources()URLClassLoaderjava.class.pathspring-doc.cn

扫描 classpath 包需要存在相应的目录 条目。使用 Ant 构建 JAR 时,不要仅激活文件 switch 的 jar 任务。此外,Classpath 目录可能不会根据安全性公开 某些环境中的策略 — 例如,JDK 1.7.0_45 上的独立应用程序 和更高级别(这需要在您的清单中设置 'Trusted-Library')。请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。spring-doc.cn

在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 在这里,强烈建议将资源放入专用目录, 避免了上述搜索 jar 文件根级别的可移植性问题。spring-doc.cn

不保证包含资源的 ant 样式模式能够找到匹配项 resources(如果要搜索的根包在多个类路径位置中可用)。 请考虑以下资源位置示例:classpath:spring-doc.cn

com/mycompany/package1/service-context.xml

现在考虑一个 Ant 样式的路径,有人可能会使用它来尝试查找该文件:spring-doc.cn

classpath:com/mycompany/**/service-context.xml

此类资源可能只位于一个位置,但是当路径(如前面的示例) 用于尝试解析它,则解析程序会处理 .如果此基础包节点存在于多个 ClassLoader 位置,则实际的最终资源可能不存在。因此,在这种情况下 您应该更喜欢使用相同的 Ant 样式模式,该模式 搜索包含根包的所有类路径位置。getResource("com/mycompany");classpath*:spring-doc.cn

2.7.3. 注意事项FileSystemResource

A 未附加到 (该 is,当 a 不是实际的 ) 处理 绝对路径和相对路径。相对路径是相对于 当前工作目录,而绝对路径是相对于 文件系统。FileSystemResourceFileSystemApplicationContextFileSystemApplicationContextResourceLoaderspring-doc.cn

但是,出于向后兼容性(历史)原因,当 为 .强制所有附加的实例 将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。 在实践中,这意味着以下示例是等效的:FileSystemApplicationContextResourceLoaderFileSystemApplicationContextFileSystemResourcespring-doc.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")

以下示例也是等效的(即使它们不同是有意义的,但作为一个 case 是相对的,另一个是绝对的):spring-doc.cn

Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
Kotlin
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
Kotlin
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")

在实践中,如果你需要真正的绝对文件系统路径,你应该避免使用 带有 OR 和 通过使用 URL 前缀强制使用 a。以下示例 演示如何执行此操作:FileSystemResourceFileSystemXmlApplicationContextUrlResourcefile:spring-doc.cn

Java
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
Kotlin
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
Java
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
Kotlin
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")

3. 验证、数据绑定和类型转换

将验证视为业务逻辑有利有弊,Spring 提供了 不排除其中任何一个的验证 (和数据绑定) 设计。 具体来说,验证不应该与 Web 层相关联,并且应该易于本地化。 并且应该可以插入任何可用的验证器。考虑到这些担忧, Spring 提供了一个既基本又非常有用的 Contract 在应用程序的每一层中。Validatorspring-doc.cn

数据绑定对于将用户输入动态绑定到域非常有用 应用程序模型(或用于处理用户输入的任何对象)的Spring 提供了恰当的命名来做到这一点。和 组成了包,它主要用于但不用于 仅限于 Web 层。DataBinderValidatorDataBindervalidationspring-doc.cn

这是 Spring Framework 中的一个基本概念,被广泛使用 的地方。但是,您可能不需要直接使用 。因为这是参考文档,所以我们觉得有一些解释 可能是有序的。我们将在本章中解释,因为,如果你是 打算使用它,你很可能在尝试将数据绑定到对象时这样做。BeanWrapperBeanWrapperBeanWrapperspring-doc.cn

Spring 的和较低级别的都使用 implementations 来解析和格式化属性值。and 类型是 JavaBeans 规范的一部分,也是 在本章中解释。Spring 3 引入了一个包,它提供了一个 通用类型转换工具,以及用于 格式化 UI 字段值。您可以将这些包用作实现的更简单替代方案。本章还将讨论它们。DataBinderBeanWrapperPropertyEditorSupportPropertyEditorPropertyEditorSupportcore.convertPropertyEditorSupportspring-doc.cn

Spring 通过设置基础设施和适配器支持 Java Bean 验证 Spring 自己的合同。应用程序可以全局启用 Bean 验证一次, 如 Java Bean 验证中所述,并将其专门用于所有验证 需要。在 Web 层中,应用程序可以进一步注册控制器本地 Spring 实例,如 配置 DataBinder 中所述,它可以 对于插入自定义验证逻辑很有用。ValidatorValidatorDataBinderspring-doc.cn

3.1. 使用 Spring 的 Validator 接口进行验证

Spring 具有一个可用于验证对象的接口。该接口通过使用对象来工作,因此,在验证时, 验证程序可以向对象报告验证失败。ValidatorValidatorErrorsErrorsspring-doc.cn

请考虑以下小型数据对象示例:spring-doc.cn

Java
public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}
Kotlin
class Person(val name: String, val age: Int)

下一个示例通过实现 接口的两种方式如下:Personorg.springframework.validation.Validatorspring-doc.cn

  • supports(Class):这能否验证提供的实例 ?ValidatorClassspring-doc.cn

  • validate(Object, org.springframework.validation.Errors):验证给定的对象 并且,如果出现验证错误,则将它们注册到给定的对象。Errorsspring-doc.cn

实现 a 相当简单,尤其是当您知道 Spring Framework 也提供的帮助程序类时。以下内容 实例的示例实现:ValidatorValidationUtilsValidatorPersonspring-doc.cn

Java
public class PersonValidator implements Validator {

    /**
     * This Validator validates only Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
Kotlin
class PersonValidator : Validator {

    /*
     * This Validator validates only Person instances
     */
    override fun supports(clazz: Class<>): Boolean {
        return Person::class.java == clazz
    }

    override fun validate(obj: Any, e: Errors) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
        val p = obj as Person
        if (p.age < 0) {
            e.rejectValue("age", "negativevalue")
        } else if (p.age > 110) {
            e.rejectValue("age", "too.darn.old")
        }
    }
}

类上的方法用于 如果是 Reject 属性,则拒绝该属性或空字符串。请查看 ValidationUtils javadoc 以查看除了前面显示的示例之外,它还提供了哪些功能。staticrejectIfEmpty(..)ValidationUtilsnamenullspring-doc.cn

虽然当然可以实现单个类来验证每个 的嵌套对象中,最好将验证 logic 中每个嵌套的 Object 类在其自己的 implementation中。一个简单的 “丰富”对象的示例是由两个属性(第一个和第二个名称)和一个复杂对象组成的 A。 对象 可以独立于对象使用,因此已实现 Distinct。如果您希望重用包含的 logic 在类中,无需使用复制和粘贴,就可以 dependency-inject 或实例化 an 如下例所示:ValidatorValidatorCustomerStringAddressAddressCustomerAddressValidatorCustomerValidatorAddressValidatorAddressValidatorCustomerValidatorspring-doc.cn

Java
public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    /**
     * This Validator validates Customer instances, and any subclasses of Customer too
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}
Kotlin
class CustomerValidator(private val addressValidator: Validator) : Validator {

    init {
        if (addressValidator == null) {
            throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
        }
        if (!addressValidator.supports(Address::class.java)) {
            throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
        }
    }

    /*
    * This Validator validates Customer instances, and any subclasses of Customer too
    */
    override fun supports(clazz: Class<>): Boolean {
        return Customer::class.java.isAssignableFrom(clazz)
    }

    override fun validate(target: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
        val customer = target as Customer
        try {
            errors.pushNestedPath("address")
            ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
        } finally {
            errors.popNestedPath()
        }
    }
}

验证错误将报告给传递给验证器的对象。在这种情况下 中,你可以使用 tag 来检查错误消息,但是 您也可以自己检查对象。有关 它提供的方法可以在 Javadoc 中找到。Errors<spring:bind/>Errorsspring-doc.cn

3.2. 将代码解析为错误消息

我们介绍了数据绑定和验证。本节介绍如何输出对应的消息 验证错误。在上一节所示的示例中, 我们拒绝了 and 字段。如果我们想使用 a 输出错误消息,可以使用我们在拒绝字段时提供的错误代码来实现 (在本例中为 'name' 和 'age')。当您调用 (直接或间接, 通过使用 例如,类)或其他方法之一 从接口中,底层实现不仅会注册 传入,但还会注册许多其他错误代码。这决定了接口注册的错误代码。默认情况下,使用 the ,它(例如)不仅注册消息 使用您提供的代码,但还会注册包含您传递的字段名称的消息 添加到 reject 方法中。因此,如果您使用 拒绝字段 , 除了代码之外, Spring 还注册了 and(第一个包含字段名称,第二个包含类型 的字段)。这样做是为了方便开发人员在定位错误消息时提供帮助。nameageMessageSourceValidationUtilsrejectValuerejectErrorsMessageCodesResolverErrorsDefaultMessageCodesResolverrejectValue("age", "too.darn.old")too.darn.oldtoo.darn.old.agetoo.darn.old.age.intspring-doc.cn

有关 和 default 策略的更多信息,请参见 在 MessageCodesResolverDefaultMessageCodesResolver 的 javadoc 中, 分别。MessageCodesResolverspring-doc.cn

3.3. Bean 操作和BeanWrapper

该软件包遵循 JavaBeans 标准。 JavaBean 是一个具有默认无参数构造函数的类,它遵循 命名约定,其中(例如)名为 would 的属性 具有 setter 方法和 getter 方法。为 有关 JavaBeans 和规范的更多信息,请参阅 JavaBeansorg.springframework.beansbingoMadnesssetBingoMadness(..)getBingoMadness()spring-doc.cn

bean 包中一个非常重要的类是接口及其 相应的实现 ()。正如 javadoc 中引用的那样,提供了设置和获取属性值(单独或在 bulk)、获取属性描述符和查询属性以确定它们是否为 readable 或 writable。此外,它还提供对嵌套属性 将 子属性 的 属性设置为 无限深度 。还支持添加标准 JavaBeans 和 的功能,而无需在目标类中支持代码。 最后但并非最不重要的一点是,它支持设置索引属性。 通常不由应用程序代码直接使用,但由 和 使用 。BeanWrapperBeanWrapperImplBeanWrapperBeanWrapperBeanWrapperPropertyChangeListenersVetoableChangeListenersBeanWrapperBeanWrapperDataBinderBeanFactoryspring-doc.cn

它的工作方式部分由它的名称表示:它将一个 bean 包装到 对该 Bean 执行操作,例如设置和检索属性。BeanWrapperspring-doc.cn

3.3.1. 设置和获取 Basic 和 Nested 属性

设置和获取属性是通过 的 和 重载方法变体完成的。请参阅他们的 Javadoc 以获取 详。下表显示了这些约定的一些示例:setPropertyValuegetPropertyValueBeanWrapperspring-doc.cn

表 11.属性示例
表达 解释

namespring-doc.cn

指示与 or 和 方法对应的属性。namegetName()isName()setName(..)spring-doc.cn

account.namespring-doc.cn

指示对应于 (例如)OR 方法。nameaccountgetAccount().setName()getAccount().getName()spring-doc.cn

account[2]spring-doc.cn

指示 indexed property 的第三个元素 。索引属性 可以是 , , 或其他自然有序的集合。accountarraylistspring-doc.cn

account[COMPANYNAME]spring-doc.cn

指示由属性的键编制索引的映射条目的值。COMPANYNAMEaccountMapspring-doc.cn

(如果您不打算使用 的 直接 。如果您只使用 和 以及它们的默认实现,您应该跳到 PropertyEditor 部分BeanWrapperDataBinderBeanFactoryspring-doc.cn

以下两个示例类使用 to get 和 set 性能:BeanWrapperspring-doc.cn

Java
public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
Kotlin
class Company {
    var name: String? = null
    var managingDirector: Employee? = null
}
Java
public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}
Kotlin
class Employee {
    var name: String? = null
    var salary: Float? = null
}

以下代码片段显示了如何检索和操作某些 instantiated 和 :CompaniesEmployeesspring-doc.cn

Java
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
Kotlin
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)

// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)

// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?

3.3.2. 内置实现PropertyEditor

Spring 使用 a 的概念来实现 an 和 a 之间的转换。它可以很方便 以不同于对象本身的方式表示属性。例如,a 可以用人类可读的方式表示(如 :),而 我们仍然可以将人类可读的形式转换回原始日期(或者,甚至 更好的是,将以人类可读形式输入的任何日期转换回对象)。这 可以通过注册 类型的自定义编辑器来实现行为。在 or 上注册自定义编辑器, 或者,在特定的 IoC 容器中(如上一章所述),会给出 了解如何将属性转换为所需的类型。有关 的更多信息,请参阅 Oracle 的 java.beans 软件包的 javadocPropertyEditorObjectStringDateString'2007-14-09'Datejava.beans.PropertyEditorBeanWrapperPropertyEditorspring-doc.cn

在 Spring 中使用属性编辑的几个示例:spring-doc.cn

  • 在 bean 上设置属性是通过使用实现来完成的。 当你将声明的某个 bean 的值用作 在 XML 文件中,Spring (如果相应属性的 setter 具有参数)用于尝试将参数解析为对象。PropertyEditorStringClassClassEditorClassspring-doc.cn

  • 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种来完成的 的实现,您可以在 .PropertyEditorCommandControllerspring-doc.cn

Spring 具有许多内置实现,使生活变得轻松。 它们都位于包中。默认情况下,大多数(但不是全部,如下表所示)由 注册。如果属性编辑器可以以某种方式进行配置,则可以 仍然注册你自己的变体来覆盖默认的变体。下表描述 Spring 提供的各种实现:PropertyEditororg.springframework.beans.propertyeditorsBeanWrapperImplPropertyEditorspring-doc.cn

表 12.内置实现PropertyEditor
解释

ByteArrayPropertyEditorspring-doc.cn

字节数组的编辑器。将字符串转换为相应的字节 交涉。默认情况下由 注册。BeanWrapperImplspring-doc.cn

ClassEditorspring-doc.cn

将表示类的 String 解析为实际的类,反之亦然。当 class 的 git 中,则会引发 an。默认情况下,由 注册。IllegalArgumentExceptionBeanWrapperImplspring-doc.cn

CustomBooleanEditorspring-doc.cn

用于属性的可自定义属性编辑器。默认情况下,由 registered by 但可以通过将其自定义实例注册为 自定义编辑器。BooleanBeanWrapperImplspring-doc.cn

CustomCollectionEditorspring-doc.cn

集合的 Property 编辑器,将任何源转换为给定的目标类型。CollectionCollectionspring-doc.cn

CustomDateEditorspring-doc.cn

的可自定义属性编辑器 ,支持自定义 .不 registered (默认)。必须根据需要使用适当的格式进行用户注册。java.util.DateDateFormatspring-doc.cn

CustomNumberEditorspring-doc.cn

任何子类的可自定义属性编辑器,例如 、 、 或 。默认情况下,由 registered by 但可以被 将其自定义实例注册为 Custom Editor。NumberIntegerLongFloatDoubleBeanWrapperImplspring-doc.cn

FileEditorspring-doc.cn

将字符串解析为对象。默认情况下,由 注册。java.io.FileBeanWrapperImplspring-doc.cn

InputStreamEditorspring-doc.cn

单向属性编辑器,可以接受一个字符串并生成(通过 intermediate 和 ) an,以便可以直接将属性设置为字符串。请注意,默认用法不会关闭 的为你。默认情况下,由 注册。ResourceEditorResourceInputStreamInputStreamInputStreamBeanWrapperImplspring-doc.cn

LocaleEditorspring-doc.cn

可以将字符串解析为对象,反之亦然(字符串格式为 ,与 的方法相同)。默认情况下,由 注册。Locale[country][variant]toString()LocaleBeanWrapperImplspring-doc.cn

PatternEditorspring-doc.cn

可以将字符串解析为对象,反之亦然。java.util.regex.Patternspring-doc.cn

PropertiesEditorspring-doc.cn

可以将字符串(使用类的 javadoc 中定义的格式进行格式化)转换为对象。默认情况下,已注册 由。java.util.PropertiesPropertiesBeanWrapperImplspring-doc.cn

StringTrimmerEditorspring-doc.cn

修剪字符串的 Property editor。(可选)允许转换空字符串 转换为值。默认情况下未注册 — 必须由用户注册。nullspring-doc.cn

URLEditorspring-doc.cn

可以将 URL 的字符串表示形式解析为实际对象。 默认情况下,由 注册。URLBeanWrapperImplspring-doc.cn

Spring 使用 来设置 属性的搜索路径 可能需要的编辑器。搜索路径还包括 ,其中 包括类型(如 、 和大多数 原始类型。另请注意,标准的 JavaBeans 基础结构 自动发现类(无需注册它们 显式地)如果它们与它们处理的类位于同一 package 中,并且具有相同的 name 作为该类,并附加例如,可以有以下内容 class 和 package 结构,这足以使 class 为 识别并用作 for -typed 属性。java.beans.PropertyEditorManagersun.bean.editorsPropertyEditorFontColorPropertyEditorEditorSomethingEditorPropertyEditorSomethingspring-doc.cn

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

请注意,您也可以在此处使用标准的 JavaBeans 机制 (在这里有一定程度的描述)。以下示例使用该机制 使用 关联类:BeanInfoBeanInfoPropertyEditorspring-doc.cn

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

引用类的以下 Java 源代码 将 a 与类的属性相关联:SomethingBeanInfoCustomNumberEditorageSomethingspring-doc.cn

Java
public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
Kotlin
class SomethingBeanInfo : SimpleBeanInfo() {

    override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
        try {
            val numberPE = CustomNumberEditor(Int::class.java, true)
            val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
                override fun createPropertyEditor(bean: Any): PropertyEditor {
                    return numberPE
                }
            }
            return arrayOf(ageDescriptor)
        } catch (ex: IntrospectionException) {
            throw Error(ex.toString())
        }

    }
}
注册其他自定义实现PropertyEditor

当将 Bean 属性设置为字符串值时,Spring IoC 容器最终会使用 标准 JavaBeans 实现将这些字符串转换为 财产。Spring 预先注册了许多自定义实现(例如,将 将表示为 String 的类名转换为 Object)。此外 Java 的标准 JavaBeans 查找机制允许对类进行适当命名,并将其与类放在同一个包中 为此,它提供支持,以便可以自动找到它。PropertyEditorPropertyEditorClassPropertyEditorPropertyEditorspring-doc.cn

如果需要注册其他自定义,有几种机制是 可用。最手动的方法,通常不方便或 推荐,就是使用接口的方法,假设你有个参考。 另一种(稍微方便一点)机制是使用特殊的咖啡豆工厂 名为 .虽然您可以使用 Bean Factory 后处理器 对于实现,它有一个 nested 属性设置,因此我们强烈建议您将它与 一起使用,在那里您可以以与任何其他 bean 类似的方式部署它,并且 可以自动检测和应用。PropertyEditorsregisterCustomEditor()ConfigurableBeanFactoryBeanFactoryCustomEditorConfigurerBeanFactoryCustomEditorConfigurerApplicationContextspring-doc.cn

请注意,所有 bean 工厂和应用程序上下文都会自动使用一些 内置属性编辑器,通过使用 A to 处理属性转换。寄存器的标准属性编辑器在上一节中列出。 此外,还可以覆盖或添加其他编辑器来处理 资源查找。BeanWrapperBeanWrapperApplicationContextsspring-doc.cn

标准 JavaBeans 实例用于转换属性值 表示为属性的实际复杂类型的字符串。您可以使用 ,一个 bean factory 后处理器,方便地添加 支持将其他实例复制到 .PropertyEditorCustomEditorConfigurerPropertyEditorApplicationContextspring-doc.cn

请考虑以下示例,该示例定义了一个名为 和 另一个名为 的类,需要将其设置为属性:ExoticTypeDependsOnExoticTypeExoticTypespring-doc.cn

Java
package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}
Kotlin
package example

class ExoticType(val name: String)

class DependsOnExoticType {

    var type: ExoticType? = null
}

当事情设置正确时,我们希望能够将 type 属性分配为 string,将其转换为实际实例。以下 Bean 定义显示了如何设置此关系:PropertyEditorExoticTypespring-doc.cn

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

实现可能类似于以下内容:PropertyEditorspring-doc.cn

Java
// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}
Kotlin
// converts string representation to ExoticType object
package example

import java.beans.PropertyEditorSupport

class ExoticTypeEditor : PropertyEditorSupport() {

    override fun setAsText(text: String) {
        value = ExoticType(text.toUpperCase())
    }
}

最后,以下示例显示了如何使用 向 注册 new,然后 将能够根据需要使用它:CustomEditorConfigurerPropertyEditorApplicationContextspring-doc.cn

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
PropertyEditorRegistrar

向 Spring 容器注册属性编辑器的另一种机制是 创建并使用 .此接口在以下情况下特别有用 您需要在几种不同情况下使用同一组属性编辑器。 您可以编写相应的注册商并在每种情况下重复使用它。 实例与一个名为 的接口一起工作,该接口由 Spring(和 )实现。 实例特别方便 当与 (在此处描述) 结合使用时,它会公开一个属性 叫。 已添加的实例 以这种方式可以轻松共享给 和 Spring MVC 控制器。此外,它还避免了在自定义时进行同步的需要 editors:A 应为每次 bean 创建尝试创建新的实例。PropertyEditorRegistrarPropertyEditorRegistrarPropertyEditorRegistryBeanWrapperDataBinderPropertyEditorRegistrarCustomEditorConfigurersetPropertyEditorRegistrars(..)PropertyEditorRegistrarCustomEditorConfigurerDataBinderPropertyEditorRegistrarPropertyEditorspring-doc.cn

以下示例显示如何创建自己的实现:PropertyEditorRegistrarspring-doc.cn

Java
package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...
    }
}
Kotlin
package com.foo.editors.spring

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry

class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {

    override fun registerCustomEditors(registry: PropertyEditorRegistry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())

        // you could register as many custom property editors as are required here...
    }
}

另请参阅 for an example implementation.请注意,在该方法的实现中,它是如何创建每个属性编辑器的新实例。org.springframework.beans.support.ResourceEditorRegistrarPropertyEditorRegistrarregisterCustomEditors(..)spring-doc.cn

下一个示例展示了如何配置 a 并将 our 的实例注入其中:CustomEditorConfigurerCustomPropertyEditorRegistrarspring-doc.cn

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

最后(对于你们这些人来说,这与本章的重点有点不同 使用 Spring 的 MVC Web 框架),在 与数据绑定(如 )结合使用可以非常 方便。以下示例在 方法的实现:PropertyEditorRegistrarsControllersSimpleFormControllerPropertyEditorRegistrarinitBinder(..)spring-doc.cn

Java
public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}
Kotlin
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {

    protected fun initBinder(request: HttpServletRequest,
                            binder: ServletRequestDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }

    // other methods to do with registering a User
}

这种注册样式可以产生简洁的代码(实现 of 只有一行长),并允许将公共注册码封装在一个类中,然后根据需要在任意数量的类之间共享。PropertyEditorinitBinder(..)PropertyEditorControllersspring-doc.cn

3.4. Spring Type Conversion

Spring 3 引入了一个包,它提供了一个通用的类型转换 系统。系统定义了一个 SPI 来实现类型转换逻辑和一个 API 在运行时执行类型转换。在 Spring 容器中,您可以使用此系统 作为转换外部化 bean 属性值的实现的替代方法 strings 设置为所需的属性类型。您还可以在 需要类型转换的应用程序。core.convertPropertyEditorspring-doc.cn

3.4.1. 转换器 SPI

实现类型转换逻辑的 SPI 简单且强类型,如下所示 接口定义显示:spring-doc.cn

Java
package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}
Kotlin
package org.springframework.core.convert.converter

interface Converter<S, T> {

    fun convert(source: S): T
}

要创建自己的转换器,请实现接口并参数化为要转换的源类型和要转换的类型。您还可以透明地应用这样的 converter (如果需要 转换为 的数组或集合,前提是委托数组或集合 converter 也已注册(默认情况下已注册)。ConverterSTSTDefaultConversionServicespring-doc.cn

对于对 的每次调用,保证 source 参数不为 null。如果转换失败,您可以抛出任何未经检查的异常。具体来说,它应该抛出一个 an 来报告一个无效的源值。 请注意确保您的实现是线程安全的。convert(S)ConverterIllegalArgumentExceptionConverterspring-doc.cn

包中提供了几种转换器实现,如 一种便利。其中包括从字符串到数字和其他常见类型的转换器。 下面的清单显示了该类,这是一个典型的实现:core.convert.supportStringToIntegerConverterspring-doc.cn

Java
package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}
Kotlin
package org.springframework.core.convert.support

import org.springframework.core.convert.converter.Converter

internal class StringToInteger : Converter<String, Int> {

    override fun convert(source: String): Int? {
        return Integer.valueOf(source)
    }
}

3.4.2. 使用ConverterFactory

当您需要集中整个类层次结构的转换逻辑时 (例如,在 Convert from to Objects 时),您可以实施 ,如下例所示:StringEnumConverterFactoryspring-doc.cn

Java
package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
Kotlin
package org.springframework.core.convert.converter

interface ConverterFactory<S, R> {

    fun <T : R> getConverter(targetType: Class<T>): Converter<S, T>
}

将 S 参数化为要转换的起始类型,将 R 参数化为定义基本类型 您可以转换为的类的范围。然后实施 , 其中 T 是 R 的子类。getConverter(Class<T>)spring-doc.cn

以 为例:StringToEnumConverterFactoryspring-doc.cn

Java
package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnumConverter(targetType);
    }

    private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}

3.4.3. 使用GenericConverter

当您需要复杂的实现时,请考虑使用接口。使用更灵活但类型不太强的签名 than ,a 支持在多个源和 目标类型。此外,还提供了 source 和 target 字段 context 中,您可以在实施转化逻辑时使用。这样的上下文允许 类型转换由字段注释或在 字段签名。以下清单显示了 的接口定义:ConverterGenericConverterConverterGenericConverterGenericConverterGenericConverterspring-doc.cn

Java
package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
Kotlin
package org.springframework.core.convert.converter

interface GenericConverter {

    fun getConvertibleTypes(): Set<ConvertiblePair>?

    fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any?
}

要实现 , have 返回支持的 source→target 类型对。然后 implement 来包含您的转换逻辑。源提供 访问保存要转换的值的 source 字段。目标提供对要设置转换值的目标字段的访问。GenericConvertergetConvertibleTypes()convert(Object, TypeDescriptor, TypeDescriptor)TypeDescriptorTypeDescriptorspring-doc.cn

一个很好的例子是在 Java 数组之间转换的转换器 和一个集合。这样的 an 内省了声明 目标集合类型,用于解析集合的元素类型。这样,每个 元素转换为 collection 元素类型,然后再将 collection 在 target 字段上设置。GenericConverterArrayToCollectionConverterspring-doc.cn

因为是一个更复杂的 SPI 接口,所以你应该使用 它只在你需要的时候。偏爱或基本型 转换需求。GenericConverterConverterConverterFactory
ConditionalGenericConverter

有时,您希望 a 仅在特定条件成立时运行 。为 例如,您可能希望仅在存在特定注释时运行 在 target 字段上,或者您可能希望仅在特定方法时运行 (例如方法)在 Target 类上定义。 是 和 接口的联合,允许您定义此类自定义匹配条件:ConverterConverterConverterstatic valueOfConditionalGenericConverterGenericConverterConditionalConverterspring-doc.cn

Java
public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
Kotlin
interface ConditionalConverter {

    fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
}

interface ConditionalGenericConverter : GenericConverter, ConditionalConverter

a 的一个很好的例子是 that convert 在持久实体标识符和实体引用之间。仅当目标实体类型声明静态查找器方法(例如,)时,此类 才可能匹配。您可以在 的实现中执行此类 finder 方法检查。ConditionalGenericConverterIdToEntityConverterIdToEntityConverterfindAccount(Long)matches(TypeDescriptor, TypeDescriptor)spring-doc.cn

3.4.4. APIConversionService

ConversionService定义一个统一的 API,用于在 运行。转换器通常在以下 Facade 接口后面运行:spring-doc.cn

Java
package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}
Kotlin
package org.springframework.core.convert

interface ConversionService {

    fun canConvert(sourceType: Class<*>, targetType: Class<*>): Boolean

    fun <T> convert(source: Any, targetType: Class<T>): T

    fun canConvert(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean

    fun convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any

}

大多数实现还实现了 ,该 提供用于注册转换器的 SPI。在内部,实现委托其注册的转换器来执行类型转换逻辑。ConversionServiceConverterRegistryConversionServicespring-doc.cn

包中提供了健壮的实现。 通用实现是否适合 在大多数环境中使用。 提供便捷的工厂 创建通用配置。ConversionServicecore.convert.supportGenericConversionServiceConversionServiceFactoryConversionServicespring-doc.cn

3.4.5. 配置ConversionService

A 是无状态对象,旨在在 application 中实例化 startup 的 URL,然后在多个线程之间共享。在 Spring 应用程序中,您通常 为每个 Spring 容器(或 )配置一个实例。 Spring 会拾取它,并在 type 转换需要由框架执行。您还可以将其注入到任何 bean 中并直接调用它。ConversionServiceConversionServiceApplicationContextConversionServiceConversionServicespring-doc.cn

如果 no 注册到 Spring,则基于原始 - 系统。ConversionServicePropertyEditor

要向 Spring 注册默认值,请添加以下 bean 定义 其中 为 :ConversionServiceidconversionServicespring-doc.cn

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

默认值可以在字符串、数字、枚举、集合、 映射和其他常见类型。要使用 own' custom converters,请设置属性。属性值可以实现 任何 、 或 接口。ConversionServiceconvertersConverterConverterFactoryGenericConverterspring-doc.cn

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

在 Spring MVC 应用程序中使用 a 也很常见。参见 Spring MVC 一章中的转换和格式化ConversionServicespring-doc.cn

在某些情况下,您可能希望在转换过程中应用格式。有关使用 的详细信息,请参阅 FormatterRegistry SPIFormattingConversionServiceFactoryBeanspring-doc.cn

3.4.6. 以编程方式使用ConversionService

要以编程方式使用实例,您可以注入对 就像你对任何其他豆子所做的那样。以下示例显示了如何执行此操作:ConversionServicespring-doc.cn

Java
@Service
public class MyService {

    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}
Kotlin
@Service
class MyService(private val conversionService: ConversionService) {

    fun doIt() {
        conversionService.convert(...)
    }
}

对于大多数使用案例,您可以使用指定 , 但 不适用于更复杂的类型,例如参数化元素的集合。 例如,如果要以编程方式将 a of 转换为 a of, 您需要提供源类型和目标类型的正式定义。converttargetTypeListIntegerListStringspring-doc.cn

幸运的是,提供了各种选项来简化操作, 如下例所示:TypeDescriptorspring-doc.cn

Java
DefaultConversionService cs = new DefaultConversionService();

List<Integer> input = ...
cs.convert(input,
    TypeDescriptor.forObject(input), // List<Integer> type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
Kotlin
val cs = DefaultConversionService()

val input: List<Integer> = ...
cs.convert(input,
        TypeDescriptor.forObject(input), // List<Integer> type descriptor
        TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))

请注意,会自动注册 适用于大多数环境。这包括集合转换器、标量 转换器和基本 -to- 转换器。您可以注册相同的转换器 with any 通过对类使用 static 方法。DefaultConversionServiceObjectStringConverterRegistryaddDefaultConvertersDefaultConversionServicespring-doc.cn

值类型的转换器被重新用于数组和集合,因此有 无需创建特定的转换器即可从 a of 转换为 of ,前提是标准集合处理是合适的。CollectionSCollectionTspring-doc.cn

3.5. Spring Field 格式化

如上一节所述,core.convert 是一个 通用型转换系统。它提供了一个统一的 API,如 以及一个强类型 SPI,用于实现一种类型的转换逻辑 到另一个。Spring 容器使用此系统来绑定 bean 属性值。在 此外,Spring 表达式语言 (SpEL) 和使用此系统都可以 bind 字段值。例如,当 SPEL 需要强制 a 到 a 到 完成 an attempt,系统执行强制转换。ConversionServiceConverterDataBinderShortLongexpression.setValue(Object bean, Object value)core.convertspring-doc.cn

现在考虑典型客户端环境的类型转换要求,例如 Web 或桌面应用程序。在此类环境中,您通常会从 转换为 以支持客户端回发过程,以及转换回以支持 View 渲染过程。此外,您通常需要本地化值。越多 通用 SPI 不满足此类格式要求 径直。为了直接解决这些问题,Spring 3 引入了一个方便的 SPI,它 为客户端环境的实现提供了一种简单而强大的替代方案。StringStringStringcore.convertConverterFormatterPropertyEditorspring-doc.cn

一般来说,当你需要实现通用类型时,你可以使用 SPI 转换逻辑 — 例如,用于在 a 和 a 之间进行转换。 当您在客户端环境(例如 Web application),并且需要解析和打印本地化的字段值。它为两个 SPI 提供了一个统一的类型转换 API。Converterjava.util.DateLongFormatterConversionServicespring-doc.cn

3.5.1. SPIFormatter

用于实现字段格式化逻辑的 SPI 很简单,并且是强类型的。这 以下清单显示了接口定义:FormatterFormatterspring-doc.cn

Java
package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter从 和 Building Block 接口扩展。这 下面的清单显示了这两个接口的定义:PrinterParserspring-doc.cn

Java
public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
Kotlin
interface Printer<T> {

    fun print(fieldValue: T, locale: Locale): String
}
Java
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}
Kotlin
interface Parser<T> {

    @Throws(ParseException::class)
    fun parse(clientValue: String, locale: Locale): T
}

要创建自己的 ,请实现前面显示的接口。 参数化为要格式化的对象类型,例如 .实现打印 for 实例的操作 display in client locale 中。实现该操作以从客户端区域设置返回的格式化表示形式解析 的实例。如果解析尝试失败,您应该抛出 a 或 an。拿 注意确保您的实现是线程安全的。FormatterFormatterTjava.util.Dateprint()Tparse()TFormatterParseExceptionIllegalArgumentExceptionFormatterspring-doc.cn

为方便起见,子包提供了多种实现。 该软件包提供 、 和 格式使用 . 该软件包提供了一个用于格式化对象的 一个。该软件包提供了全面的日期时间 基于 Joda-Time 库的格式设置支持。formatFormatternumberNumberStyleFormatterCurrencyStyleFormatterPercentStyleFormatterNumberjava.text.NumberFormatdatetimeDateFormatterjava.util.Datejava.text.DateFormatdatetime.jodaspring-doc.cn

下面是一个示例实现:DateFormatterFormatterspring-doc.cn

Java
package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
Kotlin
class DateFormatter(private val pattern: String) : Formatter<Date> {

    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)

    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)

    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}

Spring 团队欢迎社区驱动的贡献。请参阅 GitHub Issues to contribute。Formatterspring-doc.cn

3.5.2. 注解驱动的格式化

字段格式可以按字段类型或注释进行配置。绑定 对 , implement 的注释。以下内容 清单显示了接口的定义:FormatterAnnotationFormatterFactoryAnnotationFormatterFactoryspring-doc.cn

Java
package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}
Kotlin
package org.springframework.format

interface AnnotationFormatterFactory<A : Annotation> {

    val fieldTypes: Set<Class<*>>

    fun getPrinter(annotation: A, fieldType: Class<*>): Printer<*>

    fun getParser(annotation: A, fieldType: Class<*>): Parser<*>
}

要创建实施: .将 A 参数化为要关联的字段 格式设置逻辑 — 例如 . .Have 返回可以使用注释的字段类型。 .Have return a 打印带注释的字段的值。 .Have return a 解析带注释字段的 a。annotationTypeorg.springframework.format.annotation.DateTimeFormatgetFieldTypes()getPrinter()PrintergetParser()ParserclientValuespring-doc.cn

以下示例实现将 annotation 绑定到格式化程序,以使数字样式或模式为 指定:AnnotationFormatterFactory@NumberFormatspring-doc.cn

Java
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
Kotlin
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {

    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }

    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
}

要触发格式设置,您可以使用 @NumberFormat 注释字段,如下所示 示例显示:spring-doc.cn

Java
public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
Kotlin
class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式注释 API

包中存在可移植格式注释 API。您可以用于设置字段的格式,例如 and ,以及设置、 (用于毫秒时间戳) 以及 JSR-310 和 Joda-Time 值类型的格式。org.springframework.format.annotation@NumberFormatNumberDoubleLong@DateTimeFormatjava.util.Datejava.util.CalendarLongjava.timespring-doc.cn

以下示例用于将 a 格式化为 ISO 日期 (yyyy-MM-dd):@DateTimeFormatjava.util.Datespring-doc.cn

Java
public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
Kotlin
class MyModel(
    @DateTimeFormat(iso= ISO.DATE) private val date: Date
)

3.5.3. SPIFormatterRegistry

这是一个用于注册格式化程序和转换器的 SPI。 是 适用于 大多数环境。您可以以编程方式或声明方式配置此变体 作为 Spring bean 进行设置,例如通过使用 .因为这个 implementation 也实现了,可以直接配置 用于 Spring 和 Spring 表达式语言 (SpEL)。FormatterRegistryFormattingConversionServiceFormatterRegistryFormattingConversionServiceFactoryBeanConversionServiceDataBinderspring-doc.cn

下面的清单显示了 SPI:FormatterRegistryspring-doc.cn

Java
package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory);
}
Kotlin
package org.springframework.format

interface FormatterRegistry : ConverterRegistry {

    fun addFormatterForFieldType(fieldType: Class<*>, printer: Printer<*>, parser: Parser<*>)

    fun addFormatterForFieldType(fieldType: Class<*>, formatter: Formatter<*>)

    fun addFormatterForFieldType(formatter: Formatter<*>)

    fun addFormatterForAnnotation(factory: AnnotationFormatterFactory<*>)
}

如前面的清单所示,您可以按字段类型或注释注册格式化程序。spring-doc.cn

SPI 允许您集中配置格式规则,而不是 在您的控制器之间复制此类配置。例如,您可能希望 强制所有日期字段都以某种方式格式化,或者强制字段具有特定的 annotation 以某种方式格式化。使用共享的 ,您可以定义 这些规则一次,每当需要格式化时都会应用它们。FormatterRegistryFormatterRegistryspring-doc.cn

3.5.4. SPIFormatterRegistrar

FormatterRegistrar是一个 SPI,用于通过 FormatterRegistry 的下面的清单显示了它的接口定义:spring-doc.cn

Java
package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
Kotlin
package org.springframework.format

interface FormatterRegistrar {

    fun registerFormatters(registry: FormatterRegistry)
}

A 在注册多个相关转换器时很有用,并且 给定格式类别的格式化程序,例如日期格式。它也可以是 在声明式注册不足时很有用 — 例如,当格式化程序 需要在不同于其自身的特定字段类型下编制索引,或者 注册 / 对。下一节提供了有关 转换器和格式化程序注册。FormatterRegistrar<T>PrinterParserspring-doc.cn

3.5.5. 在 Spring MVC 中配置格式化

参见 Spring MVC 一章中的转换和格式化spring-doc.cn

3.6. 配置全局日期和时间格式

默认情况下,未注释的日期和时间字段将从 strings 的 Strings。如果您愿意,可以通过以下方式更改此设置 定义您自己的全局格式。@DateTimeFormatDateFormat.SHORTspring-doc.cn

为此,请确保 Spring 不注册默认格式化程序。相反,请注册 格式化程序:spring-doc.cn

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrarspring-doc.cn

  • org.springframework.format.datetime.DateFormatterRegistrar或 Joda-Time 的 Joda-Time 的 Torg.springframework.format.datetime.joda.JodaTimeFormatterRegistrarspring-doc.cn

例如,以下 Java 配置注册了全局格式:yyyyMMddspring-doc.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register JSR-310 date conversion with a specific global format
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): FormattingConversionService {
        // Use the DefaultFormattingConversionService but do not register defaults
        return DefaultFormattingConversionService(false).apply {

            // Ensure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())

            // Register JSR-310 date conversion with a specific global format
            val registrar = DateTimeFormatterRegistrar()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)

            // Register date conversion with a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters(this)
        }
    }
}

如果您更喜欢基于 XML 的配置,可以使用 .以下示例显示了如何执行此操作(这次使用 Joda 时间):FormattingConversionServiceFactoryBeanspring-doc.cn

<?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="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>

请注意,在 Web 中配置日期和时间格式时,还需要注意一些额外的注意事项 应用。请参阅 WebMVC 转换和格式化WebFlux 转换和格式化spring-doc.cn

3.7. Java Bean 验证

Spring Framework 提供了对 Java Bean 验证 API 的支持。spring-doc.cn

3.7.1. Bean 验证概述

Bean Validation 提供了一种通用的验证方法,通过 constraint declaration 和 元数据。要使用它,您可以使用 声明性验证约束,然后由运行时强制执行。有 built-in constraints,您还可以定义自己的自定义 constraints。spring-doc.cn

请考虑以下示例,该示例显示了一个具有两个属性的简单模型:PersonFormspring-doc.cn

Java
public class PersonForm {
    private String name;
    private int age;
}
Kotlin
class PersonForm(
        private val name: String,
        private val age: Int
)

Bean 验证允许您声明约束,如下例所示:spring-doc.cn

Java
public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}
Kotlin
class PersonForm(
    @get:NotNull @get:Size(max=64)
    private val name: String,
    @get:Min(0)
    private val age: Int
)

然后,Bean 验证器根据声明的 约束。有关 API 的 API 创建。请参阅 Hibernate Validator 文档 特定约束。了解如何将 Bean 验证提供程序设置为 Spring Bean,请继续阅读。spring-doc.cn

3.7.2. 配置 Bean 验证提供程序

Spring 提供了对 Bean 验证 API 的全面支持,包括 Bean Validation 提供程序作为 Spring Bean。这允许您注入 or 验证所在的 在您的应用程序中需要。javax.validation.ValidatorFactoryjavax.validation.Validatorspring-doc.cn

你可以使用 将默认的 Validator 配置为 Spring bean,如下例所示:LocalValidatorFactoryBeanspring-doc.cn

Java
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}
XML 格式
<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

前面示例中的基本配置触发 bean 验证以通过以下方式初始化 使用其默认的引导机制。Bean Validation 提供程序,例如 Hibernate Validator 应存在于 Classpath 中,并被自动检测。spring-doc.cn

注入验证器

LocalValidatorFactoryBean实现 和 ,以及 Spring 的 。 您可以将对这些接口中任一接口的引用注入到需要调用 验证逻辑。javax.validation.ValidatorFactoryjavax.validation.Validatororg.springframework.validation.Validatorspring-doc.cn

如果你更喜欢使用 Bean,可以注入一个引用 验证 API,如下例所示:javax.validation.Validatorspring-doc.cn

Java
import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
Kotlin
import javax.validation.Validator;

@Service
class MyService(@Autowired private val validator: Validator)

您可以注入对 bean 的引用 需要 Spring Validation API,如下例所示:org.springframework.validation.Validatorspring-doc.cn

Java
import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
Kotlin
import org.springframework.validation.Validator

@Service
class MyService(@Autowired private val validator: Validator)
配置自定义约束

每个 bean 验证约束由两部分组成:spring-doc.cn

  • 声明约束及其可配置属性的注释。@Constraintspring-doc.cn

  • 实现 约束的行为。javax.validation.ConstraintValidatorspring-doc.cn

为了将声明与实现相关联,每个注解 引用相应的实现类。在运行时,当 约束注释。@ConstraintConstraintValidatorConstraintValidatorFactoryspring-doc.cn

默认情况下,配置使用 Spring 创建实例。这使您的自定义可以像任何其他 Spring bean 一样从依赖关系注入中受益。LocalValidatorFactoryBeanSpringConstraintValidatorFactoryConstraintValidatorConstraintValidatorsspring-doc.cn

以下示例显示了一个自定义声明,后跟一个使用 Spring 进行依赖项注入的关联实现:@ConstraintConstraintValidatorspring-doc.cn

Java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
Kotlin
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
Java
import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

    @Autowired;
    private Foo aDependency;

    // ...
}
Kotlin
import javax.validation.ConstraintValidator

class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {

    // ...
}

如前面的示例所示,实现可以像任何其他 Spring bean 一样具有其依赖项。ConstraintValidator@Autowiredspring-doc.cn

Spring 驱动的方法验证

您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为 自定义扩展,也是由 Hibernate Validator 4.3 提供的)通过 bean 定义进入 Spring 上下文:MethodValidationPostProcessorspring-doc.cn

Java
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}
XML 格式
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

要获得 Spring 驱动的方法验证的资格,所有目标类都需要被注释 替换为 Spring 的注解,它也可以选择性地声明验证 要使用的组。有关 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息,请参见MethodValidationPostProcessor@Validatedspring-doc.cn

方法验证依赖于 AOP 代理 目标类,可以是接口上方法的 JDK 动态代理或 CGLIB 代理。 使用代理存在某些限制,其中一些限制在 了解 AOP 代理 中进行了介绍。此外,请记住 始终在代理类上使用方法和访问器;直接字段访问将不起作用。spring-doc.cn

其他配置选项

默认配置足以满足大多数 例。各种 Bean 验证有许多配置选项 构造,从消息插值到遍历解析。有关这些选项的更多信息,请参见LocalValidatorFactoryBean javadoc。LocalValidatorFactoryBeanspring-doc.cn

3.7.3. 配置DataBinder

从 Spring 3 开始,您可以使用 .一次 配置后,您可以通过调用 来调用 。任何验证都会自动添加到 Binder 的 .DataBinderValidatorValidatorbinder.validate()ErrorsBindingResultspring-doc.cn

以下示例演示如何以编程方式使用 a 调用验证 绑定到目标对象后的逻辑:DataBinderspring-doc.cn

Java
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
Kotlin
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()

// bind to the target object
binder.bind(propertyValues)

// validate the target object
binder.validate()

// get BindingResult that includes any validation errors
val results = binder.bindingResult

您还可以通过 和 配置具有多个实例的 。这在以下情况下很有用 将全局配置的 bean 验证与配置的 Spring 相结合 本地的 DataBinder 实例。参见 Spring MVC 验证配置DataBinderValidatordataBinder.addValidatorsdataBinder.replaceValidatorsValidatorspring-doc.cn

3.7.4. Spring MVC 3 验证

参见 Spring MVC 一章中的 验证spring-doc.cn

4. Spring 表达式语言 (SpEL)

Spring 表达式语言(简称“SPEL”)是一种强大的表达式语言,它 支持在运行时查询和操作对象图。语言语法为 类似于 Unified EL,但提供了额外的功能,最明显的是方法调用和 基本的字符串模板功能。spring-doc.cn

虽然还有其他几种可用的 Java 表达式语言 — OGNL、MVEL 和 JBoss EL,仅举几例 — Spring 表达式语言的创建是为了提供 Spring 社区,该社区具有一种受支持的表达式语言,可用于所有 Spring 产品组合中的产品。它的语言功能由 Spring 产品组合中项目的要求,包括工具要求 以获取 Spring Tools for Eclipse 中的代码完成支持。 也就是说,SPEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,可以集成 implementations。spring-doc.cn

虽然 SpEL 是 Spring 中表达式评估的基础 portfolio 中,它不直接与 Spring 绑定,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是一个 独立的表达式语言。这需要创建一些引导 infrastructure 类,例如 parser 的 Parser 等。大多数 Spring 用户不需要处理 此基础结构,而是只能创作表达式字符串进行评估。 这种典型用途的一个示例是将 SPEL 集成到创建 XML 或 基于注释的 Bean 定义,如定义 Bean 定义的表达式支持中所示。spring-doc.cn

本章介绍表达式语言、其 API 及其语言的功能 语法。在多个地方,类被用作目标 对象进行表达式计算。这些类声明和用于 填充它们列在本章末尾。InventorSocietyspring-doc.cn

表达式语言支持以下功能:spring-doc.cn

4.1. 评估

本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在 语言参考 中找到。spring-doc.cn

以下代码介绍了 SpEL API 来评估文本字符串表达式 .Hello Worldspring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 message 变量的值为 .'Hello World'
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 message 变量的值为 .'Hello World'

您最有可能使用的 SPEL 类和接口位于包及其子包中,例如 .org.springframework.expressionspel.supportspring-doc.cn

该接口负责解析表达式字符串。在 前面的示例,表达式 String 是由周围的 single 表示的字符串文本 引号。该接口负责评估先前定义的 expression 字符串。可以引发的两个异常,在调用 和 时,调用 和 , 分别。ExpressionParserExpressionParseExceptionEvaluationExceptionparser.parseExpressionexp.getValuespring-doc.cn

SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。spring-doc.cn

在以下方法调用示例中,我们在字符串 Literals 上调用该方法:concatspring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 的值现在是 'Hello World!'。message
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 的值现在是 'Hello World!'。message

下面调用 JavaBean 属性的示例调用了该属性:StringBytesspring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 此行将 Literals 转换为字节数组。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 此行将 Literals 转换为字节数组。

SPEL 还通过使用标准点表示法(例如 )以及相应的属性值设置来支持嵌套属性。 还可以访问 Public 字段。prop1.prop2.prop3spring-doc.cn

以下示例演示如何使用点表示法获取文本的长度:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1 'Hello World'.bytes.length给出文本的长度。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 'Hello World'.bytes.length给出文本的长度。

可以调用 String 的构造函数,而不是使用字符串文本,如下所示 示例显示:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 从文本构造一个 new 并将其设置为大写。String
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
1 从文本构造一个 new 并将其设置为大写。String

请注意泛型方法的使用:. 使用此方法无需将表达式的值强制转换为所需的值 result 类型。如果值无法强制转换为 type 或 converted 使用已注册的类型转换器。public <T> T getValue(Class<T> desiredResultType)EvaluationExceptionTspring-doc.cn

SPEL 更常见的用法是提供一个经过评估的表达式字符串 针对特定对象实例 (称为根对象) 。以下示例显示了 如何从类的实例中检索属性,或者 创建一个布尔条件:nameInventorspring-doc.cn

Java
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
Kotlin
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)

// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")

val parser = SpelExpressionParser()

var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true

4.1.1. 理解EvaluationContext

在计算表达式以解析时使用该接口 属性、方法或字段,并帮助执行类型转换。Spring 提供两个 实现。EvaluationContextspring-doc.cn

  • SimpleEvaluationContext:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。spring-doc.cn

  • StandardEvaluationContext:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。spring-doc.cn

SimpleEvaluationContext旨在仅支持 SPEL 语言语法的子集。 它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求 U 显式选择对表达式中属性和方法的支持级别。 默认情况下,static 工厂方法仅允许对属性进行读取访问。 您还可以获取构建器来配置所需的确切支持级别,并针对 以下一项或多项组合:create()spring-doc.cn

类型转换

默认情况下,SPEL 使用 Spring 核心中提供的转换服务 ().此转换服务随之而来 具有许多用于常见转换的内置转换器,但也完全可扩展,因此 您可以在类型之间添加自定义转换。此外,它是 generics-aware。这意味着,当您在 表达式中,SPEL 会尝试转换以保持任何对象的类型正确性 它相遇。org.springframework.core.convert.ConversionServicespring-doc.cn

这在实践中意味着什么?假设正在使用 的赋值 , 以设置属性。属性的类型实际上是 。斯佩尔 识别出列表的元素需要转换为 before 被放置在其中。以下示例显示了如何执行此操作:setValue()ListList<Boolean>Booleanspring-doc.cn

Java
class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);
Kotlin
class Simple {
    var booleanList: MutableList<Boolean> = ArrayList()
}

val simple = Simple()
simple.booleanList.add(true)

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")

// b is false
val b = simple.booleanList[0]

4.1.2. 解析器配置

可以使用解析器配置对象配置 SpEL 表达式解析器 ().配置 object 控制某些表达式组件的行为。例如,如果你 index 添加到数组或集合中,并且指定索引处的元素为 , 您可以自动创建元素。当使用由 属性引用链。如果您索引到数组或列表中 并指定超出数组当前大小末尾的索引,或者 list 中,您可以自动增加数组或列表以容纳该索引。以下内容 示例演示如何自动增加列表:org.springframework.expression.spel.SpelParserConfigurationnullspring-doc.cn

Java
class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
Kotlin
class Demo {
    var list: List<String>? = null
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)

val parser = SpelExpressionParser(config)

val expression = parser.parseExpression("list[3]")

val demo = Demo()

val o = expression.getValue(demo)

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

4.1.3. SPEL 编译

Spring Framework 4.1 包括一个基本的表达式编译器。表达式通常是 interpreted,这在评估过程中提供了很大的动态灵活性,但 不提供最佳性能。对于偶尔的表达式使用, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且没有真正需要动态性。spring-doc.cn

SPEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。spring-doc.cn

请考虑以下基本表达式:spring-doc.cn

someArray[0].someProperty.someOtherProperty < 0.1

因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。spring-doc.cn

编译器配置

默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用系统 当 SPEL 用法嵌入到另一个组件中时,属性。本节 讨论这两个选项。spring-doc.cn

编译器可以在枚举中捕获的三种模式之一运行。模式如下:org.springframework.expression.spel.SpelCompilerModespring-doc.cn

  • OFF(默认):编译器已关闭。spring-doc.cn

  • IMMEDIATE:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。spring-doc.cn

  • MIXED:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户在 mode 中获得的异常则在内部处理。IMMEDIATEspring-doc.cn

IMMEDIATEmode 存在,因为 mode 可能会导致 有副作用。如果编译的表达式在部分成功后崩溃,则 可能已经做了一些影响系统状态的事情。如果此 已发生,调用方可能不希望它在解释模式下静默重新运行。 因为表达式的一部分可能运行两次。MIXEDspring-doc.cn

选择模式后,使用 配置解析器。这 以下示例显示了如何执行此操作:SpelParserConfigurationspring-doc.cn

Java
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
    this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);
Kotlin
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
        this.javaClass.classLoader)

val parser = SpelExpressionParser(config)

val expr = parser.parseExpression("payload")

val message = MyMessage()

val payload = expr.getValue(message)

指定编译器模式时,还可以指定类加载器(允许传递 null)。 编译的表达式在提供的 any 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。spring-doc.cn

配置编译器的第二种方法是在 SpEL 嵌入到其他一些 组件,并且可能无法通过配置对象对其进行配置。在这些 情况下,可以使用 system 属性。您可以将该属性设置为枚举值(、 或 )之一。spring.expression.compiler.modeSpelCompilerModeoffimmediatemixedspring-doc.cn

编译器限制

从 Spring Framework 4.1 开始,基本的编译框架就位了。但是,框架 尚不支持编译各种表达式。最初的重点是 可能在性能关键型上下文中使用的常用表达式。以下内容 kinds of expression 目前无法编译:spring-doc.cn

将来将可编译更多类型的表达式。spring-doc.cn

4.2. Bean 定义中的表达式

您可以将 SPEL 表达式与基于 XML 或基于注释的配置元数据一起使用,以便 定义实例。在这两种情况下,定义表达式的语法都是 形式。BeanDefinition#{ <expression string> }spring-doc.cn

4.2.1. XML配置

可以使用表达式设置属性或构造函数参数值,如下所示 示例显示:spring-doc.cn

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

应用程序上下文中的所有 bean 都可以作为预定义的变量使用,其 通用 bean 名称。这包括用于访问运行时环境的标准上下文 bean,例如 (type ) 和 and (type )。environmentorg.springframework.core.env.EnvironmentsystemPropertiessystemEnvironmentMap<String, Object>spring-doc.cn

下面的示例将对 Bean 的访问作为 SPEL 变量显示:systemPropertiesspring-doc.cn

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

请注意,您不必在此处为预定义变量加上 symbol 前缀。#spring-doc.cn

您还可以按名称引用其他 Bean 属性,如下例所示:spring-doc.cn

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>

4.2.2. 注解配置

要指定默认值,您可以将注释放在字段、方法、 以及方法或构造函数参数。@Valuespring-doc.cn

以下示例设置字段变量的默认值:spring-doc.cn

Java
public class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin
class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}

以下示例显示了等效的 but on a property setter 方法:spring-doc.cn

Java
public class PropertyValueTestBean {

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin
class PropertyValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}

自动装配的方法和构造函数也可以使用 Comments,如下所示 示例显示:@Valuespring-doc.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
Kotlin
class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder
    private lateinit var defaultLocale: String

    @Autowired
    fun configure(movieFinder: MovieFinder,
                @Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
        this.movieFinder = movieFinder
        this.defaultLocale = defaultLocale
    }

    // ...
}
Java
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
Kotlin
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
            @Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
    // ...
}

4.3. 语言参考

本节描述了 Spring 表达式语言的工作原理。它涵盖以下内容 主题:spring-doc.cn

4.3.1. 文字表达式

支持的文字表达式类型包括字符串、数值(int、real、hex)、 boolean 和 null。字符串由单引号分隔。放置单引号本身 在字符串中,使用两个单引号字符。spring-doc.cn

下面的清单显示了 Literals 的简单用法。通常,它们不会被使用 像这样孤立地进行,而是作为更复杂的表达式的一部分——例如, 在逻辑比较运算符的一侧使用 Literals。spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();
Kotlin
val parser = SpelExpressionParser()

// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String

val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double

// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int

val trueValue = parser.parseExpression("true").value as Boolean

val nullValue = parser.parseExpression("null").value

数字支持使用负号、指数表示法和小数点。 默认情况下,使用 Double.parseDouble() 解析实数。spring-doc.cn

4.3.2. 属性、数组、列表、映射和索引器

使用属性引用进行导航非常简单。为此,请使用句点来表示嵌套的 property 值。类的实例 和 中填充了 数据列在 Classes used in the examples 部分中。 要导航“向下”并获取 Tesla 的出生年份和 Pupin 的出生城市,我们使用以下命令 表达 式:Inventorpupinteslaspring-doc.cn

Java
// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
Kotlin
// evals to 1856
val year = parser.parseExpression("Birthdate.Year + 1900").getValue(context) as Int

val city = parser.parseExpression("placeOfBirth.City").getValue(context) as String

属性名称的首字母允许不区分大小写。的内容 数组和列表是使用方括号表示法获取的,如下例所示 显示:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        context, ieee, String.class);
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// Inventions Array

// evaluates to "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String::class.java)

// Members List

// evaluates to "Nikola Tesla"
val name = parser.parseExpression("Members[0].Name").getValue(
        context, ieee, String::class.java)

// List and Array navigation
// evaluates to "Wireless communication"
val invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        context, ieee, String::class.java)

映射的内容是通过在 括弧。在下面的示例中,由于 map 的键是字符串,因此我们可以指定 字符串:Officersspring-doc.cn

Java
// Officer's Dictionary

Inventor pupin = parser.parseExpression("Officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
        societyContext, "Croatia");
Kotlin
// Officer's Dictionary

val pupin = parser.parseExpression("Officers['president']").getValue(
        societyContext, Inventor::class.java)

// evaluates to "Idvor"
val city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
        societyContext, String::class.java)

// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
        societyContext, "Croatia")

4.3.3. 内联列表

您可以使用表示法直接在表达式中表示列表。{}spring-doc.cn

Java
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
Kotlin
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>

val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>

{}本身意味着一个空列表。出于性能原因,如果列表本身是 完全由固定文本组成,创建一个常量列表来表示 expression (而不是在每次评估时构建一个新列表)。spring-doc.cn

4.3.4. 内联映射

您还可以使用 notation 直接在表达式中表示映射。这 以下示例显示了如何执行此操作:{key:value}spring-doc.cn

Java
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
Kotlin
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, >

val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<, *>

{:}本身意味着一张空地图。出于性能原因,如果映射本身是由 的固定文本或其他嵌套常量结构(列表或映射)中,将创建一个常量映射 来表示表达式(而不是在每次求值时构建新 map)。引用 map 键 是可选的。上面的示例不使用带引号的键。spring-doc.cn

4.3.5. 数组构造

您可以使用熟悉的 Java 语法构建数组,并可选择提供初始化器 以在构造时填充数组。以下示例显示了如何执行此操作:spring-doc.cn

Java
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
Kotlin
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray

// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray

// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>

当前,在构造 Multi-dimensional 数组。spring-doc.cn

4.3.6. 方法

您可以使用典型的 Java 编程语法来调用方法。您还可以调用方法 在 Literals 上。还支持变量参数。以下示例说明如何 invoke 方法:spring-doc.cn

Java
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Kotlin
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)

// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean::class.java)

4.3.7. 运算符

Spring 表达式语言支持以下类型的运算符:spring-doc.cn

关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 下面的清单显示了一些 Operators 示例:spring-doc.cn

Java
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
Kotlin
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)

// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)

大于和小于比较遵循一个简单的规则:被视为 nothing(不是零)。因此,任何其他值总是更大 than ( is always ) 并且没有其他值永远小于 none ( 总是 )。nullnullnullX > nulltrueX < nullfalsespring-doc.cn

如果您更喜欢数字比较,请避免基于数字的比较 赞成与 Zero 进行比较(例如 或 )。nullX > 0X < 0spring-doc.cn

除了标准关系运算符之外,SPEL 还支持 和 regular 基于表达式的运算符。下面的清单显示了这两种方法的示例:instanceofmatchesspring-doc.cn

Java
// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

//evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
Kotlin
// evaluates to false
val falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)

//evaluates to false
val falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
小心原始类型,因为它们会立即被装箱到 wrapper 类型。 so 的计算结果为 ,而 则计算结果为 ,正如预期的那样。1 instanceof T(int)false1 instanceof T(Integer)true

每个符号运算符也可以指定为纯字母等效运算符。这 避免了所使用的符号对 表达式嵌入的 (,例如在 XML 文档中)。文本等价物是:spring-doc.cn

所有文本运算符都不区分大小写。spring-doc.cn

逻辑运算符

SPEL 支持以下逻辑运算符:spring-doc.cn

以下示例演示如何使用逻辑运算符spring-doc.cn

Java
// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Kotlin
// -- AND --

// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- OR --

// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- NOT --

// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)

// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
数学运算符

您可以对数字和字符串使用加号运算符。您可以使用减法、乘法、 和除法运算符仅对数字。您还可以使用 模数 (%) 和指数幂 (^) 运算符。强制实施标准运算符优先级。这 以下示例显示了正在使用的数学运算符:spring-doc.cn

Java
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0

// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0

// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1

// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
Kotlin
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java)  // 2

val testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String::class.java)  // 'test string'

// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java)  // 4

val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java)  // -9000

// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java)  // 6

val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java)  // 24.0

// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java)  // -2

val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java)  // 1.0

// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java)  // 3

val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java)  // 1

// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java)  // -21
赋值运算符

要设置属性,请使用赋值运算符 ()。这通常是 在对 的调用中完成,但也可以在对 的调用中完成。这 下面的清单显示了使用 ASSIGNMENT 运算符的两种方法:=setValuegetValuespring-doc.cn

Java
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
Kotlin
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()

parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic")

// alternatively
val aleks = parser.parseExpression(
        "Name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)

4.3.8. 类型

您可以使用特殊运算符指定 ( type) 的 intent 的 intent静态方法也是使用此运算符调用的。使用 a 来查找类型,而 (可以替换) 是在了解包的基础上构建的。这意味着对 中的类型的引用不需要是 完全限定,但所有其他类型的引用都必须是。以下示例显示了如何操作 要使用运算符:Tjava.lang.ClassStandardEvaluationContextTypeLocatorStandardTypeLocatorjava.langT()java.langTspring-doc.cn

Java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);
Kotlin
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)

val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)

val trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean::class.java)

4.3.9. 构造函数

您可以使用 operator 调用构造函数。您应该使用完全限定的类名 对于除基元类型(、 、 等)和 String 之外的所有类型。以下内容 示例演示如何使用 Operator 调用构造函数:newintfloatnewspring-doc.cn

Java
Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

//create new inventor instance within add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);
Kotlin
val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)

//create new inventor instance within add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

4.3.10. 变量

您可以使用语法在表达式中引用变量。变量 通过使用 implementation 上的方法进行设置。#variableNamesetVariableEvaluationContextspring-doc.cn

有效的变量名称必须由以下一个或多个受支持的变量组成 字符。spring-doc.cn

以下示例演示如何使用变量。spring-doc.cn

Java
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
Kotlin
val tesla = Inventor("Nikola Tesla", "Serbian")

val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")

parser.parseExpression("Name = #newName").getValue(context, tesla)
println(tesla.name)  // "Mike Tesla"
和 变量#this#root

变量始终是定义的,并引用当前评估对象 (根据这些引用解析不合格的引用)。变量始终为 定义并引用根上下文对象。虽然 表达式,始终引用根。以下示例 演示如何使用 AND 变量:#this#root#this#root#this#rootspring-doc.cn

Java
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);
Kotlin
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))

// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
        "#primes.?[#this>10]").getValue(context) as List<Int>

4.3.11. 函数

您可以通过注册可在 expression 字符串。该函数通过 注册 .这 以下示例演示如何注册用户定义的函数:EvaluationContextspring-doc.cn

Java
Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
Kotlin
val method: Method = ...

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)

例如,请考虑以下反转字符串的实用程序方法:spring-doc.cn

Java
public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
Kotlin
fun reverseString(input: String): String {
    val backwards = StringBuilder(input.length)
    for (i in 0 until input.length) {
        backwards.append(input[input.length - 1 - i])
    }
    return backwards.toString()
}

然后,您可以注册并使用上述方法,如下例所示:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
Kotlin
val parser = SpelExpressionParser()

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)

val helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String::class.java)

4.3.12. Bean 引用

如果评估上下文已经配置了 bean 解析器,则可以 使用 symbol 从表达式中查找 bean。以下示例显示了如何操作 为此,请执行以下操作:@spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)

要访问工厂 Bean 本身,您应该在 Bean 名称前加上一个符号。 以下示例显示了如何执行此操作:&spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)

4.3.13. 三元运算符 (If-Then-Else)

您可以使用三元运算符在 表达式。下面的清单显示了一个最小示例:spring-doc.cn

Java
String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Kotlin
val falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String::class.java)

在这种情况下,布尔值会导致返回字符串值 。一个 更多 实际示例如下:false'falseExp'spring-doc.cn

Java
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
Kotlin
parser.parseExpression("Name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"

val queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

请参阅 Elvis 运算符的下一节,了解 三元运算符。spring-doc.cn

4.3.14. Elvis 运算符

Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须将变量重复两次,因为 以下示例显示:spring-doc.cn

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,您可以使用 Elvis 运算符(因与 Elvis 的发型相似而命名)。 以下示例演示如何使用 Elvis 运算符:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'
Kotlin
val parser = SpelExpressionParser()

val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name)  // 'Unknown'

下面的清单显示了一个更复杂的示例:spring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Nikola Tesla

tesla.setName(null)
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Elvis Presley

您可以使用 Elvis 运算符在表达式中应用默认值。以下内容 example 演示如何在表达式中使用 Elvis 运算符:@Valuespring-doc.cn

@Value("#{systemProperties['pop3.port'] ?: 25}")

如果已定义,这将注入一个系统属性,如果未定义,则注入 25。pop3.portspring-doc.cn

4.3.15. Safe Navigation 操作符

安全导航运算符用于避免 a 和 comes from Groovy 语言。通常,当您引用某个对象时,可能需要验证 在访问对象的方法或属性之前,它不为 null。为避免这种情况, Safe Navigation 运算符返回 null,而不是引发异常。以下内容 示例演示如何使用 Safe Navigation 运算符:NullPointerExceptionspring-doc.cn

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))

var city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city)  // Smiljan

tesla.setPlaceOfBirth(null)
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city)  // null - does not throw NullPointerException!!!

4.3.16. 集合选择

选择项是一项强大的表达式语言功能,可用于转换 source 集合添加到另一个集合中。spring-doc.cn

选择使用 .它会过滤集合和 返回包含原始元素子集的新集合。例如 选择让我们轻松获得塞尔维亚发明家的列表,如下例所示:.?[selectionExpression]spring-doc.cn

Java
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);
Kotlin
val list = parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext) as List<Inventor>

可以在列表和地图上进行选择。对于列表,选择 criteria 根据每个单独的 list 元素进行评估。对于地图, 根据每个映射条目(Java 类型的对象)评估选择标准 。每个映射条目都有其键和值,可作为属性访问 选择。Map.Entryspring-doc.cn

以下表达式返回一个由原始 map 的那些元素组成的新 map 其中 entry 值小于 27:spring-doc.cn

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

除了返回所有选定的元素外,您还可以仅检索 first 或 last 值。要获取与所选内容匹配的第一个条目,语法为 。要获取最后一个匹配的选择,语法为 。.^[selectionExpression].$[selectionExpression]spring-doc.cn

4.3.17. 集合投影

Projection 允许集合驱动子表达式的计算,而 result 是一个新集合。projection 的语法为 。为 例如,假设我们有一个发明人列表,但希望 他们出生的城市。实际上,我们想评估 'placeOfBirth.city' Inventor 列表中的每个条目。以下示例使用 projection 来执行此操作:.![projectionExpression]spring-doc.cn

Java
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
Kotlin
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("Members.![placeOfBirth.city]") as List<*>

您还可以使用地图来驱动投影,在这种情况下,投影表达式为 根据 Map 中的每个条目进行评估(表示为 Java )。结果 的 of a projection across a map 是一个列表,其中包含对投影的评估 expression 来触发每个 Map 条目。Map.Entryspring-doc.cn

4.3.18. 表达式模板

表达式模板允许将文本文本与一个或多个评估块混合。 每个评估块都用前缀和后缀字符分隔,您可以 定义。常见的选择是用作分隔符,如下例所示 显示:#{ }spring-doc.cn

Java
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"
Kotlin
val randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        TemplateParserContext()).getValue(String::class.java)

// evaluates to "random number is 0.7038186818312008"

通过将文本文本与 在分隔符内计算表达式的结果(在本例中为结果 调用该方法)。该方法的第二个参数 的类型为 。该接口用于影响 解析表达式以支持表达式模板化功能。 的定义如下:'random number is '#{ }random()parseExpression()ParserContextParserContextTemplateParserContextspring-doc.cn

Java
public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}
Kotlin
class TemplateParserContext : ParserContext {

    override fun getExpressionPrefix(): String {
        return "#{"
    }

    override fun getExpressionSuffix(): String {
        return "}"
    }

    override fun isTemplate(): Boolean {
        return true
    }
}

4.4. 示例中使用的类

本节列出了本章中示例中使用的类。spring-doc.cn

发明家.Java
package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
发明家.kt
class Inventor(
    var name: String,
    var nationality: String,
    var inventions: Array<String>? = null,
    var birthdate: Date =  GregorianCalendar().time,
    var placeOfBirth: PlaceOfBirth? = null)
PlaceOfBirth.java
package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

    private String city;
    private String country;

    public PlaceOfBirth(String city) {
        this.city=city;
    }

    public PlaceOfBirth(String city, String country) {
        this(city);
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String s) {
        this.city = s;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
出生地点.kt
class PlaceOfBirth(var city: String, var country: String? = null) {
Society.java
package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

    private String name;

    public static String Advisors = "advisors";
    public static String President = "president";

    private List<Inventor> members = new ArrayList<Inventor>();
    private Map officers = new HashMap();

    public List getMembers() {
        return members;
    }

    public Map getOfficers() {
        return officers;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isMember(String name) {
        for (Inventor inventor : members) {
            if (inventor.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }
}
社会.kt
package org.spring.samples.spel.inventor

import java.util.*

class Society {

    val Advisors = "advisors"
    val President = "president"

    var name: String? = null

    val members = ArrayList<Inventor>()
    val officers = mapOf<Any, Any>()

    fun isMember(name: String): Boolean {
        for (inventor in members) {
            if (inventor.name == name) {
                return true
            }
        }
        return false
    }
}

5. 使用 Spring 进行面向方面编程

面向方面编程 (AOP) 通过以下方式补充面向对象编程 (OOP) 提供了另一种思考程序结构的方法。模块化的关键单元 在 OOP 中是类,而在 AOP 中,模块化单元是方面。方面 实现跨关注点(例如事务管理)的模块化 多个类型和对象。(此类担忧通常被称为“横切”担忧 在 AOP 文献中。spring-doc.cn

Spring 的关键组件之一是 AOP 框架。虽然 Spring IoC 容器不依赖于 AOP(这意味着如果您不想,则不需要使用 AOP to),AOP 补充了 Spring IoC,提供了一个非常强大的中间件解决方案。spring-doc.cn

Spring AOP 与 AspectJ 切入点

Spring 通过使用基于 schema 的方法或 @AspectJ Comments 样式,提供了编写自定义切面的简单而强大的方法。 这两种样式都提供了完全类型的建议和 AspectJ 切入点语言的使用 同时仍然使用 Spring AOP 进行编织。spring-doc.cn

本章讨论基于 Schema 和 @AspectJ 的 AOP 支持。 下一章将讨论较低级别的 AOP 支持。spring-doc.cn

AOP 在 Spring Framework 中用于:spring-doc.cn

如果您只对通用的声明式服务或其他预打包的服务感兴趣 声明式中间件服务(如池)中,你不需要直接使用 Spring AOP,并且可以跳过本章的大部分内容。

5.1. AOP 概念

让我们从定义一些核心的 AOP 概念和术语开始。这些术语不是 特定于 Spring。遗憾的是,AOP 术语并不是特别直观。 但是,如果 Spring 使用自己的术语,那将更加令人困惑。spring-doc.cn

  • Aspect:跨多个类的关注点的模块化。 事务管理是企业 Java 中横切关注点的一个很好的例子 应用。在 Spring AOP 中,切面是使用常规类实现的 (基于架构的方法)或带有注释注释的常规类(@AspectJ样式)。@Aspectspring-doc.cn

  • 连接点:程序执行过程中的一个点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点始终是 表示方法执行。spring-doc.cn

  • Advice:一个 aspect 在特定连接点采取的行动。不同类型的 建议包括 “around”、“before” 和 “after” 建议。(讨论了建议类型 稍后。许多 AOP 框架,包括 Spring,将通知建模为拦截器,并且 在 Join Point 周围维护一个拦截器链。spring-doc.cn

  • 切入点:与连接点匹配的谓词。建议与 pointcut 表达式并在与该 pointcut 匹配的任何连接点(例如 执行具有特定名称的方法)。匹配连接点的概念 by pointcut 表达式是 AOP 的核心,而 Spring 使用 AspectJ pointcut 表达式语言。spring-doc.cn

  • 简介:代表类型声明其他方法或字段。Spring AOP 允许您将新接口(和相应的实现)引入到任何 advised 对象。例如,你可以使用 introduction 使 bean 实现一个接口,以简化缓存。(引言称为 inter-type 声明。IsModifiedspring-doc.cn

  • Target object:由一个或多个方面通知的对象。也称为 “Advised Object” 的 Visd 对象。由于 Spring AOP 是使用运行时代理实现的,因此 object 始终是代理对象。spring-doc.cn

  • AOP proxy:由 AOP 框架创建的对象,用于实现 aspect 合约(通知、方法执行等)。在 Spring 框架中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。spring-doc.cn

  • 编织:将方面与其他应用程序类型或对象链接起来,创建一个 advised 对象。这可以在编译时完成(使用 AspectJ 编译器,用于 example)、load time 或 runtime 中。Spring AOP 与其他纯 Java AOP 框架一样, 在运行时执行 weaving。spring-doc.cn

Spring AOP 包括以下类型的建议:spring-doc.cn

  • Before advice:在连接点之前运行但没有 防止执行流继续到连接点的能力(除非它抛出 一个例外)。spring-doc.cn

  • After returning advice:在连接点完成后运行的通知 通常(例如,如果方法返回而不引发异常)。spring-doc.cn

  • 抛出后通知:如果方法通过抛出 例外。spring-doc.cn

  • After (finally) advice:无论 连接点退出 (正常或异常返回)。spring-doc.cn

  • Around advice:围绕连接点的建议,例如方法调用。 这是最有力的建议。Around advice 可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续执行连接点,或者通过返回其 自己的返回值或引发异常。spring-doc.cn

围绕建议是最普遍的建议。从 Spring AOP 开始,就像 AspectJ 一样, 提供了全系列的建议类型,我们建议你使用最弱的 advice 类型。例如,如果您只需要 使用方法的返回值更新 Cache,则最好实现 返回 advice 后比 around 建议,虽然 around 建议可以完成 同样的事情。使用最具体的 Advice 类型提供更简单的编程模型 出错的可能性较小。例如,您不需要调用 used for around 通知上的方法,因此,您不能不调用它。proceed()JoinPointspring-doc.cn

所有通知参数都是静态类型的,因此您可以使用 适当的类型(例如,方法执行的返回值的类型),而不是 than 数组。Objectspring-doc.cn

由切入点匹配的连接点的概念是 AOP 的关键,它区分了 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象的层次结构进行定位。例如,您可以应用 around advice 为一组跨 多个对象(例如服务层中的所有业务操作)。spring-doc.cn

5.2. Spring AOP 的功能和目标

Spring AOP 是用纯 Java 实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适合在 Servlet 容器或应用程序服务器中使用。spring-doc.cn

Spring AOP 目前只支持方法执行连接点(通知执行 of 方法)。字段拦截未实现,尽管支持 可以在不破坏核心 Spring AOP API 的情况下添加字段拦截。如果您需要 要建议字段访问和更新连接点,请考虑使用 AspectJ 等语言。spring-doc.cn

Spring AOP 的 AOP 方法与大多数其他 AOP 框架不同。目标是 而不是提供最完整的 AOP 实现(尽管 Spring AOP 相当 有能力的)。相反,目标是在 AOP 实现和 Spring IoC,帮助解决企业应用程序中的常见问题。spring-doc.cn

因此,例如,Spring 框架的 AOP 功能通常用于 与 Spring IoC 容器结合使用。Aspect 是使用普通 bean 配置的 定义语法(尽管这允许强大的 “自动代理” 功能)。这是一个 与其他 AOP 实现的关键区别。你不能做一些事情 轻松或高效地使用 Spring AOP,例如通知非常细粒度的对象(通常为 domain 对象)。在这种情况下,AspectJ 是最好的选择。但是,我们的 经验表明,Spring AOP 为大多数 适合 AOP 的 enterprise Java 应用程序。spring-doc.cn

Spring AOP 从不努力与 AspectJ 竞争以提供全面的 AOP 溶液。我们相信,基于代理的框架(如 Spring AOP)和成熟的 像 AspectJ 这样的框架很有价值,而且它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有使用都在一个一致的基于 Spring 的应用程序中的应用 架构。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参见下一章spring-doc.cn

Spring Framework 的核心原则之一是非侵入性。这 是不应强制引入特定于框架的类的想法, 接口连接到您的业务或域模型。但是,在某些地方, Spring 框架 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是因为,在某些情况下,它 可能只是更容易阅读或编写此类 一种方法。但是,Spring Framework(几乎)始终为您提供选择:您有 自由地决定哪种选项最适合您的特定用途 案例或场景。spring-doc.cn

与本章相关的一个选择是 AOP 框架(以及 which AOP style) 进行选择。您可以选择 AspectJ 和/或 Spring AOP。你 还可以选择 @AspectJ 注解样式方法或 Spring XML configuration-style 方法。本章选择介绍 @AspectJ 风格 First 的方法不应被视为 Spring 团队 更喜欢 @AspectJ Comments 样式的方法,而不是 Spring XML 配置样式。spring-doc.cn

有关“为什么和为什么”的更完整讨论,请参见选择要使用的 AOP 声明样式 每种样式。spring-doc.cn

5.3. AOP 代理

Spring AOP 默认使用标准 JDK 动态代理作为 AOP 代理。这 允许代理任何接口(或一组接口)。spring-doc.cn

Spring AOP 也可以使用 CGLIB 代理。这对于代理类而不是 接口。默认情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见的)情况下,您可以强制使用 CGLIB 需要通知未在接口上声明的方法,或者需要通知 将代理对象作为具体类型传递给方法。spring-doc.cn

掌握 Spring AOP 是基于代理的事实是很重要的。请参阅 Understanding AOP Proxies 以彻底检查它的具体内容 implementation detail 实际上是 Implementation。spring-doc.cn

5.4. @AspectJ 支持

@AspectJ 指的是一种将方面声明为常规 Java 类的样式,这些类带有 附注。@AspectJ 样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注解 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或 weaver。spring-doc.cn

使用 AspectJ 编译器和 weaver 可以使用完整的 AspectJ 语言和 在 Using AspectJ with Spring Applications中进行了讨论。

5.4.1. 启用 @AspectJ 支持

要在 Spring 配置中使用 @AspectJ 方面,您需要启用 Spring 对 基于 @AspectJ 方面配置 Spring AOP 并基于 无论他们是否受到这些方面的建议。自动代理是指,如果 Spring 确定 bean 由一个或多个 aspect 通知,则它会自动生成 该 bean 的代理,用于拦截方法调用并确保 Advice 运行 根据需要。spring-doc.cn

可以通过 XML 或 Java 样式的配置来启用 @AspectJ 支持。在任一 case,你还需要确保 AspectJ 的库位于 classpath 的 classpath (版本 1.8 或更高版本)。此库位于 AspectJ 发行版的目录中,也可从 Maven Central 存储库获得。aspectjweaver.jarlibspring-doc.cn

使用 Java 配置启用 @AspectJ 支持

要使用 Java 启用@AspectJ支持,请添加注释,如下例所示:@Configuration@EnableAspectJAutoProxyspring-doc.cn

Java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
Kotlin
@Configuration
@EnableAspectJAutoProxy
class AppConfig
使用 XML 配置启用 @AspectJ 支持

要使用基于 XML 的配置启用 @AspectJ 支持,请使用 element,如下例所示:aop:aspectj-autoproxyspring-doc.cn

<aop:aspectj-autoproxy/>

这假定您使用架构支持,如 基于 XML 架构的配置中所述。 请参阅 AOP 架构以了解如何 导入命名空间中的标记。aopspring-doc.cn

5.4.2. 声明一个 Aspect

启用 @AspectJ 支持后,在应用程序上下文中定义的任何 bean 都会显示 class 是 @AspectJ 方面(具有注解)会自动 由 Spring 检测到,并用于配置 Spring AOP。接下来的两个示例显示了 不是很有用的 aspect 所需的最小定义。@Aspectspring-doc.cn

两个示例中的第一个示例显示了应用程序中的常规 bean 定义 context 指向具有 Comments 的 Bean 类:@Aspectspring-doc.cn

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>

两个示例中的第二个示例显示了类定义 它用注释进行注释;NotVeryUsefulAspectorg.aspectj.lang.annotation.Aspectspring-doc.cn

Java
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}
Kotlin
package org.xyz

import org.aspectj.lang.annotation.Aspect;

@Aspect
class NotVeryUsefulAspect

Aspects (classes ancomments with ) 可以具有方法和字段,与任何 其他类。它们还可以包含切入点、建议和引言(类型间) 声明。@Aspectspring-doc.cn

通过组件扫描自动检测各个方面
你可以在 Spring XML 配置中将 aspect 类注册为常规 bean,或者 通过 Classpath 扫描自动检测它们——与任何其他 Spring 管理的 bean 相同。 但是,请注意,注释不足以在 类路径。为此,您需要添加单独的注释 (或者,根据 Spring 的组件扫描仪)。@Aspect@Component
用其他方面提供建议?
在 Spring AOP 中,方面本身不能成为 advice 的目标 从其他方面来看。类上的注释将其标记为一个方面,并且 因此,将其排除在自动代理之外。@Aspect

5.4.3. 声明切入点

切入点确定感兴趣的连接点,从而使我们能够控制 当 Advice 运行时。Spring AOP 仅支持 Spring 的方法执行连接点 beans,因此你可以将切入点视为与 Spring 上方法的执行相匹配 豆。切入点声明有两个部分:由 name 和 any 组成的签名 参数和精确确定方法的切入点表达式 我们感兴趣的执行。在 AOP 的 @AspectJ 注解样式中,一个切入点 signature 由常规方法定义提供,切入点表达式为 通过使用注释(用作切入点签名的方法)指示 必须具有 return 类型)。@Pointcutvoidspring-doc.cn

一个示例可能有助于区分切入点签名和切入点 表达式 clear。下面的示例定义了一个名为 匹配任何名为 :anyOldTransfertransferspring-doc.cn

Java
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
Kotlin
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature

形成注释值的切入点表达式是常规的 AspectJ 5 切入点表达式。有关 AspectJ 的切入点语言的完整讨论,请参阅 AspectJ 编程指南(对于扩展,还有 AspectJ 5 Developer's Notebook)或关于 AspectJ 的书籍之一(例如 Colyer 编写的 Eclipse AspectJ 等。al.,或 AspectJ in Action,Ramnivas Laddad 著)。@Pointcutspring-doc.cn

支持的切入点标号

Spring AOP 支持以下 AspectJ 切入点指示符 (PCD) 用于切入点 表达 式:spring-doc.cn

  • execution:用于匹配方法执行连接点。这是主要的 使用 Spring AOP 时使用的切入点指示符。spring-doc.cn

  • within:将匹配限制为某些类型中的连接点(执行 在使用 Spring AOP 时在匹配类型中声明的方法)。spring-doc.cn

  • this:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中 bean 引用(Spring AOP 代理)是给定类型的实例。spring-doc.cn

  • target:限制与连接点的匹配(使用 Spring AOP),其中目标对象(被代理的应用程序对象)是一个实例 的spring-doc.cn

  • args:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中参数是给定类型的实例。spring-doc.cn

  • @target:限制与连接点的匹配(使用 Spring AOP),其中执行对象的类具有给定类型的 Comments。spring-doc.cn

  • @args:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。spring-doc.cn

  • @within:限制匹配到具有给定 annotation(执行在具有给定注解的类型中声明的方法,当 使用 Spring AOP)。spring-doc.cn

  • @annotation:将匹配限制为连接点的主题 (在 Spring AOP 中运行的方法)具有给定的注解。spring-doc.cn

其他切入点类型

完整的 AspectJ 切入点语言支持其他不是 在 Spring 中支持: 、 、 、 和 。在切入点中使用这些切入点指示符 由 Spring AOP 解释的表达式会导致一个 being 扔。callgetsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@this@withincodeIllegalArgumentExceptionspring-doc.cn

Spring AOP 支持的切入点指示符集将来可能会扩展 版本以支持更多的 AspectJ 切入点指示符。spring-doc.cn

由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论 的切入点指示符给出的定义比您在 AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且在 执行连接点,两者都指向同一个对象: 对象执行该方法。Spring AOP 是一个基于代理的系统,它与众不同 代理对象本身(绑定到 )和 proxy(绑定到 )。thistargetthistargetspring-doc.cn

由于 Spring 的 AOP 框架基于代理的性质,目标对象内的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方法 可以拦截对代理的调用。使用 CGLIB 时,对 public 和 protected 方法调用 代理被拦截(如有必要,甚至是包可见的方法)。然而 通过代理的常见交互应始终通过公共签名进行设计。spring-doc.cn

请注意,切入点定义通常与任何截获的方法匹配。 如果切入点严格来说是公开的,即使在 CGLIB 代理场景中 通过代理进行潜在的非公开交互,则需要相应地定义。spring-doc.cn

如果您的拦截需要包括目标中的方法调用甚至构造函数 类中,请考虑使用 Spring 驱动的原生 AspectJ 编织 Spring 的基于代理的 AOP 框架。这构成了 AOP 使用的不同模式 具有不同的特性,所以一定要让自己熟悉编织 在做出决定之前。spring-doc.cn

Spring AOP 还支持一个名为 .此 PCD 允许您限制 连接点与特定命名 Spring bean 或一组命名 Spring bean(使用通配符时)。PCD 具有以下形式:beanbeanspring-doc.cn

Java
bean(idOrNameOfBean)
Kotlin
bean(idOrNameOfBean)

令牌可以是任何 Spring bean 的名称。有限通配符 提供了使用该字符的支持,因此,如果您建立一些命名 约定,您可以编写一个 PCD 表达式 以选择它们。与其他切入点指示符一样,PCD 可以 也可以与 (and)、(or) 和 (negation) 运算符一起使用。idOrNameOfBean*beanbean&&||!spring-doc.cn

PCD 仅在 Spring AOP 中受支持,在 原生 AspectJ 编织。它是标准 PCD 的 Spring 特定扩展,它 AspectJ 定义了模型中描述的 aspects,因此,它不可用于模型中声明的 aspects。bean@Aspectspring-doc.cn

PCD 在实例级别运行(基于 Spring bean 名称 概念),而不仅仅是在类型级别(基于编织的 AOP 仅限于类型级别)。 基于实例的切入点指示符是 Spring 的 基于代理的 AOP 框架及其与 Spring bean 工厂的紧密集成,其中 按名称识别特定 bean 是自然而直接的。beanspring-doc.cn

组合切入点表达式

可以使用 和 组合切入点表达式。您还可以参考 按名称切入表达式。下面的示例显示了三个切入点表达式:&&,||!spring-doc.cn

Java
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} (3)
1 anyPublicOperation如果 Method Execution 连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在 Trading 模块中,则匹配。
3 tradingOperation如果方法执行表示 trading 模块。
Kotlin
@Pointcut("execution(public * *(..))")
private fun anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private fun inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} (3)
1 anyPublicOperation如果 Method Execution 连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在 Trading 模块中,则匹配。
3 tradingOperation如果方法执行表示 trading 模块。

最佳实践是从较小的命名 组件,如前所述。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以看到相同类型的私有切入点,受保护的切入点位于 层次结构、任意位置的公共切入点等)。可见性不会影响切入点 匹配。spring-doc.cn

共享公共切入点定义

在使用企业应用程序时,开发人员通常希望引用 应用程序和特定操作集从几个方面进行。我们 建议定义捕获常见切入点表达式的 aspect 为此目的。此类方面通常类似于以下示例:CommonPointcutsspring-doc.cn

Java
package com.xyz.myapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.myapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.myapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.myapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
     * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}
Kotlin
package com.xyz.myapp

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut

@Aspect
class CommonPointcuts {

    /**
    * A join point is in the web layer if the method is defined
    * in a type in the com.xyz.myapp.web package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    fun inWebLayer() {
    }

    /**
    * A join point is in the service layer if the method is defined
    * in a type in the com.xyz.myapp.service package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    fun inServiceLayer() {
    }

    /**
    * A join point is in the data access layer if the method is defined
    * in a type in the com.xyz.myapp.dao package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    fun inDataAccessLayer() {
    }

    /**
    * A business service is the execution of any method defined on a service
    * interface. This definition assumes that interfaces are placed in the
    * "service" package, and that implementation types are in sub-packages.
    *
    * If you group service interfaces by functional area (for example,
    * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
    * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
    * could be used instead.
    *
    * Alternatively, you can write the expression using the 'bean'
    * PCD, like so "bean(*Service)". (This assumes that you have
    * named your Spring service beans in a consistent fashion.)
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    fun businessService() {
    }

    /**
    * A data access operation is the execution of any method defined on a
    * dao interface. This definition assumes that interfaces are placed in the
    * "dao" package, and that implementation types are in sub-packages.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    fun dataAccessOperation() {
    }

}

你可以在任何需要 切入点表达式。例如,要使服务层具有事务性,您可以 写下以下内容:spring-doc.cn

<aop:config>
    <aop:advisor
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

和 元素在 基于 Schema 的 AOP 支持中讨论。这 事务 Management 中讨论了事务元素<aop:config><aop:advisor>spring-doc.cn

例子

Spring AOP 用户可能最常使用切入点指示符。 执行表达式的格式如下:executionspring-doc.cn

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)

除返回类型模式(在前面的代码段中)之外的所有部分, Name pattern 和 parameters pattern 是可选的。返回的类型模式确定 方法的返回类型必须是什么才能匹配 Join Point。 最常用作返回类型模式。它与任何 return 匹配 类型。仅当方法返回给定的 类型。名称模式与方法名称匹配。您可以将通配符用作 all 或 名称模式的一部分。如果指定声明类型 pattern, 包括一个尾部以将其连接到 Name pattern 组件。 parameters 模式稍微复杂一些:匹配 方法,而匹配任意数量的(零个或多个)参数。 该模式与采用任意类型一个参数的方法匹配。 匹配采用两个参数的方法。第一个可以是任何类型,而 second 必须是 .查阅语言 Semantics 部分。ret-type-pattern**.()(..)(*)(*,String)Stringspring-doc.cn

以下示例显示了一些常见的切入点表达式:spring-doc.cn

  • 任何公共方法的执行:spring-doc.cn

        execution(public * *(..))
  • 执行名称以 : 开头的任何方法setspring-doc.cn

        execution(* set*(..))
  • 执行接口定义的任何方法:AccountServicespring-doc.cn

        execution(* com.xyz.service.AccountService.*(..))
  • 执行包中定义的任何方法:servicespring-doc.cn

        execution(* com.xyz.service.*.*(..))
  • 执行服务包或其子包之一中定义的任何方法:spring-doc.cn

        execution(* com.xyz.service..*.*(..))
  • 服务包中的任何连接点(仅在 Spring AOP 中执行方法):spring-doc.cn

        within(com.xyz.service.*)
  • 服务包中的任何连接点(仅在 Spring AOP 中执行方法)或其 子包:spring-doc.cn

        within(com.xyz.service..*)
  • 代理实现接口的任何连接点(仅在 Spring AOP 中执行方法):AccountServicespring-doc.cn

        this(com.xyz.service.AccountService)
    'this' 更常用于 binding 形式。有关如何在通知正文中使 proxy 对象可用的信息,请参见 Declaring Advice 部分。
  • 目标对象 实现接口:AccountServicespring-doc.cn

        target(com.xyz.service.AccountService)
    'target' 更常用于 binding 形式。参见 Declaring Advice 部分 了解如何使目标对象在通知正文中可用。
  • 任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 其中,在运行时传递的参数是 :Serializablespring-doc.cn

        args(java.io.Serializable)
    'args' 更常以绑定形式使用。参见 Declaring Advice 部分 了解如何使方法参数在 Advice Body 中可用。

    请注意,此示例中给出的切入点与 不同。如果在运行时传递的参数为 ,则 args 版本匹配,如果方法签名声明单个 类型为 的参数。execution(* *(java.io.Serializable))SerializableSerializablespring-doc.cn

  • 目标对象具有 Comments 的任何连接点(仅在 Spring AOP 中执行方法):@Transactionalspring-doc.cn

        @target(org.springframework.transaction.annotation.Transactional)
    您也可以在装订形式中使用 '@target' 。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中 target 对象具有注释:@Transactionalspring-doc.cn

        @within(org.springframework.transaction.annotation.Transactional)
    您还可以在装订形式中使用 '@within'。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。
  • 执行方法具有 Comments 的任何连接点(仅在 Spring AOP 中执行方法):@Transactionalspring-doc.cn

        @annotation(org.springframework.transaction.annotation.Transactional)
    您还可以在装订形式中使用 '@annotation'。参见 Declaring Advice 部分 了解如何使 Annotation 对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法)采用单个参数 其中传递的参数的运行时类型具有 Annotation:@Classifiedspring-doc.cn

        @args(com.xyz.security.Classified)
    您还可以在绑定形式中使用 '@args'。参见 Declaring Advice 部分 如何使 Annotation 对象在 Advice Body 中可用。
  • 名为tradeServicespring-doc.cn

        bean(tradeService)
  • Spring bean 上任何名称为 match the wildcard expression :*Servicespring-doc.cn

        bean(*Service)
编写好的切入点

在编译期间,AspectJ 处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态地)给定的切入点是一个昂贵的过程。(动态匹配是指匹配 无法从静态分析中完全确定,并且测试放置在代码中以 确定代码运行时是否存在实际匹配项)。首次遇到 pointcut 声明时,AspectJ 会将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用 DNF 重写的(析取 Normal Form) 和切入点的组件进行排序,以便这些组件 首先检查评估成本较低的 URL。这意味着您不必担心 关于了解各种切入点指示符的性能并可能提供它们 在切入点声明中以任何顺序。spring-doc.cn

然而,AspectJ 只能与它被告知的内容一起工作。为了获得最佳性能 匹配,您应该考虑他们想要实现的目标并缩小搜索范围 space for 在定义中尽可能匹配。现有标号 自然属于以下三组之一:kinded、scope 和 contextual:spring-doc.cn

  • Kinded 标号选择特定类型的连接点: 、 、 、 和 。executiongetsetcallhandlerspring-doc.cn

  • 范围界定号选择一组感兴趣的连接点 (可能有很多种):和withinwithincodespring-doc.cn

  • 上下文指示符根据上下文匹配(并选择性地绑定):、 和thistarget@annotationspring-doc.cn

一个写得好的切入点应该至少包括前两种类型(kinded 和 范围界定)。您可以包含上下文指示符以根据 join point context 或 bind 该上下文以在通知中使用。仅提供 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),由于额外的处理和分析。范围 标号的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地 关闭不应进一步处理的联接点组。一个好的 如果可能,切入点应始终包含一个。spring-doc.cn

5.4.4. 声明通知

Advice 与切入点表达式相关联,并在之前、之后或周围运行 与切入点匹配的方法执行。切入点表达式可以是 对命名切入点或就地声明的切入点表达式的简单引用。spring-doc.cn

建议前

你可以使用注解在一个 aspect 中声明 before advice:@Beforespring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}

如果我们使用就地切入点表达式,我们可以将前面的示例重写为 以下示例:spring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    fun doAccessCheck() {
        // ...
    }
}
退货后通知

返回后,通知将在匹配的方法执行正常返回时运行。 您可以使用注释来声明它:@AfterReturningspring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
您可以有多个 advice 声明(以及其他成员), 都在同一个方面。在这些 examples 来集中每个 API 的效果。

有时,您需要在通知正文中访问返回的实际值。 你可以使用 that 的形式来绑定返回值来获取它 access,如下例所示:@AfterReturningspring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning = "retVal")
    fun doAccessCheck(retVal: Any) {
        // ...
    }
}

属性中使用的名称必须与参数的名称相对应 在 Advice 方法中。当方法执行返回时,返回值将传递给 将 Advice 方法作为相应的参数值。子句还 将匹配限制为仅返回 指定类型(在本例中为 ,它与任何返回值匹配)。returningreturningObjectspring-doc.cn

请注意,在以下情况下,无法返回完全不同的引用 使用后返回建议。spring-doc.cn

抛出后的建议

抛出后,当匹配的方法执行退出时,通过抛出一个 例外。您可以使用注解来声明它,因为 以下示例显示:@AfterThrowingspring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doRecoveryActions() {
        // ...
    }
}

通常,您希望通知仅在引发给定类型的异常时运行。 而且你还经常需要访问 Advice Body 中引发的异常。您可以 使用该属性来限制匹配(如果需要 — 否则用作异常类型)并将引发的异常绑定到 Advice 参数。 以下示例显示了如何执行此操作:throwingThrowablespring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing = "ex")
    fun doRecoveryActions(ex: DataAccessException) {
        // ...
    }
}

属性中使用的名称必须与 建议方法。当方法执行通过引发异常退出时,异常 作为相应的参数值传递给通知方法。A 子句 还将匹配限制为仅那些抛出 指定类型(在本例中为)。throwingthrowingDataAccessExceptionspring-doc.cn

请注意,这并不表示一般的异常处理回调。 具体来说,通知方法只应该接收异常 从 Join Point(用户声明的 Target 方法)本身,而不是从附带的 / 方法。@AfterThrowing@AfterThrowing@After@AfterReturningspring-doc.cn

之后(最后)建议

After (finally) 通知在匹配的方法执行退出时运行。它由 使用注释。建议后必须准备好处理正常和 异常返回条件。它通常用于释放资源和类似的 目的。以下示例演示如何使用 after finally 建议:@Afterspring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After

@Aspect
class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doReleaseLock() {
        // ...
    }
}

请注意,AspectJ 中的 advice 被定义为“after finally advice”,类似于 添加到 try-catch 语句中的 finally 块。它将针对任何结果调用 从连接点(用户声明的目标方法)引发的正常返回或异常, 相比之下,它仅适用于成功的正常回报。@After@AfterReturningspring-doc.cn

周边建议

最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行(例如,启动和停止计时器)。 始终使用满足您要求的最弱的建议形式(即 如果 Before advice 可以,请不要使用 around advice)。spring-doc.cn

Around 通知是使用 annotation 声明的。第一个参数 通知方法的类型必须为 。在建议的正文中, 调用 on 会导致底层方法运行。 该方法还可以传入 .使用数组中的值 作为方法继续执行时的参数。@AroundProceedingJoinPointproceed()ProceedingJoinPointproceedObject[]spring-doc.cn

使用 an 调用时的行为与 由 AspectJ 编译器编译的 for around 通知的行为。对于周围 advice,则传递给的参数数量必须与传递给 around 通知的参数数量匹配(而不是数量 的参数),并将传递给 continue 的值以 given argument position 替换实体联接点处的原始值 该值已绑定到 (如果现在没有意义,请不要担心)。方法 taken by Spring 更简单,并且更匹配其基于代理的、仅执行的 语义学。只有在编译 @AspectJ 时,您才需要注意这种差异 为 Spring 编写的方面,并与 AspectJ 编译器的参数一起使用 和 weaver 一起。有一种方法可以编写这样的方面,它在两者之间 100% 兼容 Spring AOP 和 AspectJ,这将在下面关于通知参数的部分中讨论。proceedObject[]proceedproceedproceed

下面的示例展示了如何使用 around 建议:spring-doc.cn

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint

@Aspect
class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
        // start stopwatch
        val retVal = pjp.proceed()
        // stop stopwatch
        return retVal
    }
}

around 通知返回的值是 方法。例如,如果 有一个,如果没有,则调用 INVOKE。请注意,可以调用一次, 很多时候,或者根本不在周围的建议体内。所有这些都是合法的。proceed()proceedspring-doc.cn

Advice 参数

Spring 提供了完全类型化的通知,这意味着您可以在 advice 签名(正如我们之前看到的 return 和 throw 示例)而不是 一直使用数组。我们了解如何进行论证和其他上下文 值可用于本节后面的通知正文。首先,我们来看看如何 编写 generic advice,可以了解该 Advice 当前建议的方法。Object[]spring-doc.cn

访问当前JoinPoint

任何通知方法都可以将 type 为 parameter 类型为 ,它是 的子类。 该接口提供了许多有用的方法:org.aspectj.lang.JoinPointProceedingJoinPointJoinPointJoinPointspring-doc.cn

有关更多详细信息,请参阅 javadocspring-doc.cn

将参数传递给 Advice

我们已经看到了如何绑定返回值或异常值(使用 after 返回和抛出建议后)。使参数值可用于通知 body 中,可以使用 .如果使用参数名称代替 type name 时,相应参数的值将作为 调用通知时的 parameter 值。一个例子应该更清楚地说明这一点。 假设您要通知执行将对象作为第一个参数的 DAO 操作,并且您需要访问通知正文中的账户。 您可以编写以下内容:argsAccountspring-doc.cn

Java
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
Kotlin
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
    // ...
}

切入点表达式的部分有两个用途。首先,它 将匹配限制为仅该方法至少采用一个 参数,并且传递给该参数的参数是 的实例。 其次,它通过 parameter 使实际对象可用于通知。args(account,..)AccountAccountaccountspring-doc.cn

另一种写法是声明一个切入点,当对象与连接点匹配时“提供”对象值,然后引用命名的切入点 来自建议。这将如下所示:Accountspring-doc.cn

Java
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}
Kotlin
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}

@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
    // ...
}

有关更多信息,请参阅 AspectJ 编程指南 详。spring-doc.cn

代理对象 ( )、目标对象 () 和批注 ( 、 、 和 )都可以以类似的方式绑定。接下来的两个 示例展示了如何匹配带有 Comments 的方法的执行并提取审计代码:thistarget@within@target@annotation@args@Auditablespring-doc.cn

两个示例中的第一个显示了 Comments 的定义:@Auditablespring-doc.cn

Java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)

两个示例中的第二个示例显示了与方法执行匹配的建议:@Auditablespring-doc.cn

Java
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
Kotlin
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
    val code = auditable.value()
    // ...
}
通知参数和泛型

Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个泛型类型,如下所示:spring-doc.cn

Java
public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
Kotlin
interface Sample<T> {
    fun sampleGenericMethod(param: T)
    fun sampleGenericCollectionMethod(param: Collection<T>)
}

您可以通过以下方式将方法类型的拦截限制为某些参数类型 将 advice 参数键入要拦截方法的 parameter type:spring-doc.cn

Java
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}
Kotlin
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
    // Advice implementation
}

此方法不适用于泛型集合。所以你不能定义 切入如下:spring-doc.cn

Java
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}
Kotlin
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
    // Advice implementation
}

要做到这一点,我们必须检查集合的每个元素,而 合理的,因为我们也无法决定如何一般地对待 value。为了实现 与此类似,您必须手动键入参数 检查元素的类型。nullCollection<?>spring-doc.cn

确定参数名称

通知调用中的参数绑定依赖于切入点中使用的匹配名称 Advice 和 PointCut 方法签名中声明的参数名称的表达式。 参数名称不能通过 Java 反射获得,因此 Spring AOP 使用 以下策略来确定参数名称:spring-doc.cn

  • 如果用户已显式指定参数名称,则指定的 参数名称。建议和切入点注释都有 一个可选属性,可用于指定 带注释的方法。这些参数名称在运行时可用。以下示例 演示如何使用该属性:argNamesargNamesspring-doc.cn

Java
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}
Kotlin
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code and bean
}

如果第一个参数是 、 或 类型,则可以从值中省略参数的名称 的属性。例如,如果您将前面的通知修改为接收 join point 对象中,该属性不需要包含它:JoinPointProceedingJoinPointJoinPoint.StaticPartargNamesargNamesspring-doc.cn

Java
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}
Kotlin
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code, bean, and jp
}

对 、 、 和 类型的第一个参数进行特殊处理特别方便 不收集任何其他 Join Point Context 的 Advice 实例。在这种情况下,您可以 省略该属性。例如,以下建议不需要声明 属性:JoinPointProceedingJoinPointJoinPoint.StaticPartargNamesargNamesspring-doc.cn

Java
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}
Kotlin
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
    // ... use jp
}
  • 使用 attribute 有点笨拙,所以如果 attribute 未指定,则 Spring AOP 会查看 类,并尝试从局部变量表中确定参数名称。这 只要类已使用 debug 编译,信息就存在 信息(至少)。使用此标志进行编译的后果 上是:(1) 你的代码稍微更容易理解(逆向工程),(2) 类文件大小略大(通常无关紧要),(3) 编译器不会应用用于删除未使用的局部变量的优化。在 换句话说,使用此标志进行构建应该不会遇到任何困难。'argNames''argNames''-g:vars'spring-doc.cn

    如果 @AspectJ 方面已经由 AspectJ 编译器 (ajc) 编译,即使没有 debug 信息,则无需添加 attribute 作为编译器 保留所需的信息。argNames
  • 如果代码在编译时没有必要的调试信息,则 Spring AOP 尝试推断绑定变量与参数的配对(例如,如果 切入点表达式中只绑定了一个变量,而 Advice 方法 只接受一个参数,配对是显而易见的)。如果变量的绑定是 模棱两可,则 扔。AmbiguousBindingExceptionspring-doc.cn

  • 如果上述策略都失败了,则会抛出 an。IllegalArgumentExceptionspring-doc.cn

继续参数

我们之前说过,我们将描述如何编写 call 在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是 以确保 Advice 签名按顺序绑定每个 Method 参数。 以下示例显示了如何执行此操作:proceedspring-doc.cn

Java
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}
Kotlin
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
                        accountHolderNamePattern: String): Any {
    val newPattern = preProcess(accountHolderNamePattern)
    return pjp.proceed(arrayOf<Any>(newPattern))
}

在许多情况下,您仍然会执行此绑定(如前面的示例所示)。spring-doc.cn

建议订购

当多个建议都想在同一个连接点运行时,会发生什么情况? Spring AOP 遵循与 AspectJ 相同的优先规则来确定通知的顺序 执行。最高优先级的建议首先运行“在途中”(因此,给定两个部分 of before 建议,则优先级最高的那个先运行)。“On the way out” 从 join point 时,最高优先级 advice 将运行在 last 之后(因此,给定两个 after advice 的 Advice 中,优先级最高的 ID 将排在第二位)。spring-doc.cn

当在不同方面定义的两条通知都需要在同一条下运行时 join point 的执行顺序,除非你另有指定,否则执行顺序是 undefined。您可以 通过指定 Precedence 来控制执行顺序。这是在正常情况下完成的 Spring 方式,通过在 aspect 类或使用 Annotation 对其进行注释。给定两个方面, aspect 返回较低的值 from(或 annotation 值)具有 优先级更高。org.springframework.core.Ordered@OrderOrdered.getOrder()spring-doc.cn

特定方面的每种不同的建议类型在概念上都是要适用的 直接连接到连接点。因此,通知方法不是 应该从附带的 / 方法中接收异常。@AfterThrowing@After@AfterReturningspring-doc.cn

从 Spring Framework 5.2.7 开始,在同一个类中定义的通知方法 需要在同一连接点运行,则根据它们在 以下顺序,从最高到最低优先级:、、、、 .但请注意,通知方法将 在 any 或 advice 方法之后有效地调用 在同一方面,遵循 AspectJ 的 “after finally advice” 语义。@Aspect@Around@Before@After@AfterReturning@AfterThrowing@After@AfterReturning@AfterThrowing@Afterspring-doc.cn

当两条相同类型的建议(例如,两种建议方法) 定义在同一个类中都需要在同一个连接点运行,则 Ordering 未定义(因为无法通过 Reflection for JavaC 编译的类)。考虑将这些 advice 方法合并为一个 通知方法,或者将通知片段重构为 可以通过 或 在 aspect 级别订购的单独类。@After@Aspect@Aspect@AspectOrdered@Orderspring-doc.cn

5.4.5. 简介

介绍(在 AspectJ 中称为类型间声明)使一个 aspect 能够声明 ,建议对象实现给定的接口,并且要提供 该接口代表这些对象。spring-doc.cn

您可以使用注释进行介绍。此批注 用于声明匹配类型具有新的父级(因此得名)。例如 给定一个名为 的接口和该接口的实现,以下方面声明 Service 的所有实现者 接口也实现了接口(例如,用于通过 JMX 进行统计):@DeclareParentsUsageTrackedDefaultUsageTrackedUsageTrackedspring-doc.cn

Java
@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}
Kotlin
@Aspect
class UsageTracking {

    companion object {
        @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
        lateinit var mixin: UsageTracked
    }

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    fun recordUsage(usageTracked: UsageTracked) {
        usageTracked.incrementUseCount()
    }
}

要实现的接口由带注释的字段的类型决定。注解的属性是 AspectJ 类型的模式。任何 匹配类型的 bean 实现接口。请注意,在 在前面的示例的 advice 之前,服务 bean 可以直接用作 接口的实现。如果以编程方式访问 Bean,则 您将编写以下内容:value@DeclareParentsUsageTrackedUsageTrackedspring-doc.cn

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.4.6. Aspect 实例化模型

这是一个高级主题。如果您刚开始使用 AOP,则可以安全地跳过 它直到以后。

默认情况下,应用程序中的每个方面都有一个实例 上下文。AspectJ 称其为单例实例化模型。可以定义 具有替代生命周期的 aspects。Spring 支持 AspectJ 和实例化模型;、 和 当前不是 支持。perthispertargetpercflowpercflowbelowpertypewithinspring-doc.cn

您可以通过在注释中指定子句来声明 face。请考虑以下示例:perthisperthis@Aspectspring-doc.cn

Java
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {

    private int someState;

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}
Kotlin
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {

    private val someState: Int = 0

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    fun recordServiceUsage() {
        // ...
    }
}

在前面的示例中,子句的效果是 1 个 aspect 实例 为执行业务服务的每个唯一服务对象创建(每个唯一的 对象绑定到 pointcut 表达式匹配的连接点处)。坡向 实例是在第一次对 Service 对象调用方法时创建的。这 当 Service 对象超出范围时,aspect 超出范围。aspect 之前 实例,则其中的任何 Advice 都不会运行。一旦 aspect 实例 已创建,则其中声明的通知将在匹配的连接点运行,但只有 当 Service 对象是与此 aspect 关联的对象时。请参阅 AspectJ 编程指南,了解有关子句的更多信息。perthisthisperspring-doc.cn

实例化模型的工作方式与 完全相同,但它 在匹配的连接点处为每个唯一的目标对象创建一个 aspect 实例。pertargetperthisspring-doc.cn

5.4.7. AOP 示例

现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起进行 一些有用的东西。spring-doc.cn

业务服务的执行有时会由于并发问题(对于 例如,一个死锁失败者)。如果重试操作,则可能会成功 下次尝试时。对于适合在此类 条件(无需因冲突而返回给用户的幂等操作 resolution),我们希望以透明方式重试该操作,以避免客户端看到 .这是一个明确贯穿的要求 服务层中的多个服务,因此非常适合通过 方面。PessimisticLockingFailureExceptionspring-doc.cn

因为我们想要重试操作,所以我们需要使用 around advice,以便我们可以 多次调用。下面的清单显示了基本的 aspect 实现:proceedspring-doc.cn

Java
@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin
@Aspect
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

请注意,该 aspect 实现了接口,以便我们可以设置 方面高于 Transaction Advice(我们希望每次 retry)。和 属性都是由 Spring 配置的。这 主要操作发生在 Around 建议中。请注意,对于 矩,我们将重试逻辑应用于每个 .我们努力进行, 如果我们失败了 ,我们会再试一次,除非 我们已经用尽了所有的重试尝试。OrderedmaxRetriesorderdoConcurrentOperationbusinessService()PessimisticLockingFailureExceptionspring-doc.cn

相应的 Spring 配置如下:spring-doc.cn

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

要优化 aspect 以便它仅重试幂等操作,我们可以定义以下注释:Idempotentspring-doc.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation

然后,我们可以使用 annotation 来注释服务操作的实现。变化 到仅重试幂等操作涉及优化切入点 表达式,以便仅匹配操作,如下所示:@Idempotentspring-doc.cn

Java
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
Kotlin
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
    // ...
}

5.5. 基于 Schema 的 AOP 支持

如果你更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持 使用 namespace 标签。完全相同的切入点表达式和通知类型 与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注 该语法,并让读者参考上一节中的讨论 (@AspectJ支持)用于理解编写切入点表达式和绑定 的建议参数。aopspring-doc.cn

要使用本节中描述的 aop 命名空间标记,您需要导入架构,如基于 XML 架构的配置中所述。请参阅 AOP 架构,了解如何在命名空间中导入标签。spring-aopaopspring-doc.cn

在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在 元素(您可以在 应用程序上下文配置)。元素可以包含切入点、 advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。<aop:config><aop:config><aop:config>spring-doc.cn

配置风格大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议 not being wovening),如果你已经通过使用或类似的东西使用了显式的自动代理。建议的使用模式是 仅使用 style 或 only the style 和 切勿混合使用它们。<aop:config>BeanNameAutoProxyCreator<aop:config>AutoProxyCreator

5.5.1. 声明一个 Aspect

当您使用 schema 支持时,aspect 是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 方法,以及 XML 中的切入点和通知信息。spring-doc.cn

你可以使用元素声明一个 face,并引用支持 bean 通过使用 Attribute,如下例所示:<aop:aspect>refspring-doc.cn

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

支持切面的 bean(在本例中)当然可以配置并且 依赖项注入,就像任何其他 Spring bean 一样。aBeanspring-doc.cn

5.5.2. 声明切入点

您可以在元素中声明命名切入点,让切入点 定义在多个方面和顾问之间共享。<aop:config>spring-doc.cn

表示服务层中任何业务服务的执行的切入点可以 定义如下:spring-doc.cn

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式 语言,如 @AspectJ 支持中所述。如果使用基于架构的声明 样式中,@Aspects您可以在 切入点表达式。定义上述切入点的另一种方法如下:spring-doc.cn

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>

</aop:config>

假设您有一个 共享公共切入点定义 中所述的方面。CommonPointcutsspring-doc.cn

那么在一个 aspect 中声明一个切入点与声明一个顶级切入点非常相似。 如下例所示:spring-doc.cn

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...
    </aop:aspect>

</aop:config>

与 @AspectJ 方面大致相同,使用基于 schema 的 schema 声明的切入点 定义样式可以收集连接点上下文。例如,以下切入点 收集对象作为连接点上下文并将其传递给通知:thisspring-doc.cn

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>

</aop:config>

必须声明通知以接收收集的加入点上下文,方法是包含 参数,如下所示:spring-doc.cn

Java
public void monitor(Object service) {
    // ...
}
Kotlin
fun monitor(service: Any) {
    // ...
}

组合切入点子表达式时,在 XML 中很尴尬 document,因此您可以分别使用 、 、 和 关键字来代替 、 和 。例如,前面的切入点可以更好地写成 遵循:&amp;&amp;andornot&amp;&amp;||!spring-doc.cn

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

请注意,以这种方式定义的切入点由其 XML 引用,并且不能 用作命名切入点以形成复合切入点。命名切入点支持 因此,基于架构的定义样式比 @AspectJ 提供的定义样式更受限制 风格。idspring-doc.cn

5.5.3. 声明 Advice

基于 schema 的 AOP 支持使用与 @AspectJ 样式相同的五种通知,并且它们具有 完全相同的语义。spring-doc.cn

建议前

Before 通知在匹配的方法执行之前运行。它是使用 element 在 an 中声明的,如下例所示:<aop:aspect><aop:before>spring-doc.cn

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

这里 是定义在顶部的切入点 () 水平。要定义内联切入点,请将 attribute 替换为 a 属性,如下所示:dataAccessOperationid<aop:config>pointcut-refpointcutspring-doc.cn

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...
</aop:aspect>

正如我们在讨论 @AspectJ 样式时所指出的,使用命名切入点可以 显著提高代码的可读性。spring-doc.cn

该属性标识一个方法 (),该方法提供 建议。必须为 aspect 元素引用的 bean 定义此方法 其中包含建议。在执行数据访问操作之前(方法执行 join point matched by the pointcut 表达式),则 aspect 上的 bean 被调用。methoddoAccessCheckdoAccessCheckspring-doc.cn

退货后通知

返回后,通知在匹配的方法执行正常完成时运行。是的 声明的方式与之前的建议相同。以下示例 演示如何声明它:<aop:aspect>spring-doc.cn

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...
</aop:aspect>

与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。 为此,请使用 attribute 指定参数的名称,该参数 应传递 return 值,如下例所示:returningspring-doc.cn

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...
</aop:aspect>

该方法必须声明一个名为 .此 type of this parameter 约束匹配的方式与 中所述的相同。为 example,您可以按如下方式声明 Method Signature:doAccessCheckretVal@AfterReturningspring-doc.cn

Java
public void doAccessCheck(Object retVal) {...
Kotlin
fun doAccessCheck(retVal: Any) {...
抛出后的建议

抛出后,当匹配的方法执行退出时,通过抛出一个 例外。它是通过使用元素 如下例所示:<aop:aspect>after-throwingspring-doc.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

与 @AspectJ 样式一样,您可以在通知正文中获取 thrown 异常。 为此,请使用 attribute 指定参数的名称 应传递该异常,如下例所示:throwingspring-doc.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

该方法必须声明一个名为 . 此参数的类型以与 中所述相同的方式约束匹配。例如,方法签名可以按如下方式声明:doRecoveryActionsdataAccessEx@AfterThrowingspring-doc.cn

Java
public void doRecoveryActions(DataAccessException dataAccessEx) {...
Kotlin
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议

After (finally) 通知运行,无论匹配的方法执行如何退出。 您可以使用 element 声明它,如下例所示:afterspring-doc.cn

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...
</aop:aspect>
周边建议

最后一种建议是围绕建议。Around 建议 “around” 匹配的方法 执行。它有机会在方法运行之前和之后执行工作 并确定该方法何时、如何运行,甚至是否真的开始运行。 Around 通知通常用于在 线程安全的方式(例如,启动和停止计时器)。始终使用最少的 满足您要求的强大建议形式。如果出现以下情况,请勿在建议周围使用 之前 建议 可以 完成 这项工作。spring-doc.cn

你可以使用 element 来声明 around advice。的第一个参数 通知方法的类型必须为 .在建议的正文中, 调用 on 会导致底层方法运行。 也可以使用 .数组中的值 在方法执行过程中用作方法执行的参数。 有关使用 . 下面的示例展示了如何在 XML 中声明 around advice:aop:aroundProceedingJoinPointproceed()ProceedingJoinPointproceedObject[]proceedObject[]spring-doc.cn

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...
</aop:aspect>

建议的实现可以与 @AspectJ示例(当然不包括 Annotation),如下例所示:doBasicProfilingspring-doc.cn

Java
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}
Kotlin
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
    // start stopwatch
    val retVal = pjp.proceed()
    // stop stopwatch
    return pjp.proceed()
}
Advice 参数

基于 schema 的声明样式支持完全类型化的通知,其方式与 针对 @AspectJ 支持进行描述 — 通过按名称将切入点参数与 通知方法参数。有关详细信息,请参阅 Advice Parameters 。如果您愿意 要显式指定通知方法的参数名称(不依赖于 检测策略),您可以通过使用 Advice 元素的属性来实现此目的,该属性的处理方式与通知注释中的属性相同(如确定参数名称中所述)。 以下示例演示如何在 XML 中指定参数名称:arg-namesargNamesspring-doc.cn

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

该属性接受以逗号分隔的参数名称列表。arg-namesspring-doc.cn

以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用:spring-doc.cn

Java
package x.y.service;

public interface PersonService {

    Person getPerson(String personName, int age);
}

public class DefaultPersonService implements PersonService {

    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}
Kotlin
package x.y.service

interface PersonService {

    fun getPerson(personName: String, age: Int): Person
}

class DefaultPersonService : PersonService {

    fun getPerson(name: String, age: Int): Person {
        return Person(name, age)
    }
}

接下来是方面。请注意,该方法接受多个 强类型参数,其中第一个参数恰好是用于 继续执行 method 调用。此参数的存在表明 将用作建议,如下例所示:profile(..)profile(..)aroundspring-doc.cn

Java
package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}
Kotlin
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch

class SimpleProfiler {

    fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
        val clock = StopWatch("Profiling for '$name' and '$age'")
        try {
            clock.start(call.toShortString())
            return call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        }
    }
}

最后,以下示例 XML 配置会影响 针对特定连接点的上述建议:spring-doc.cn

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

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                expression="execution(* x.y.service.PersonService.getPerson(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>

请考虑以下驱动程序脚本:spring-doc.cn

Java
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        PersonService person = (PersonService) ctx.getBean("personService");
        person.getPerson("Pengo", 12);
    }
}
Kotlin
fun main() {
    val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
    val person = ctx.getBean("personService") as PersonService
    person.getPerson("Pengo", 12)
}

使用这样的 Boot 类,我们将在标准输出上获得类似于以下内容的输出:spring-doc.cn

StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)
建议订购

当多个通知需要在同一个连接点运行时(执行方法) 排序规则如 Advice Ordering 中所述。优先权 between aspects 是通过元素中的属性确定的,或者 通过将注释添加到支持该方面的 bean 中,或者通过使用 bean 实现接口。order<aop:aspect>@OrderOrderedspring-doc.cn

与同一类中定义的通知方法的优先规则相反,当同一元素中定义的两条通知都需要 run 相同的 join point 时,优先级由通知的顺序决定 元素在封闭元素中按从最高到最低的顺序声明 优先。@Aspect<aop:aspect><aop:aspect>spring-doc.cn

例如,给定一个 advice 和一个在同一个元素中定义的 advice,它们适用于同一个连接点,为了确保 advice 的优先级高于 advice,该元素必须是 在 element 之前声明。aroundbefore<aop:aspect>aroundbefore<aop:around><aop:before>spring-doc.cn

作为一般的经验法则,如果您发现您定义了多条建议 在应用于同一连接点的同一元素中,考虑折叠 此类通知方法转换为每个元素中每个连接点的一个通知方法 或者将建议片段重构为您可以订购的单独元素 在 aspect 级别。<aop:aspect><aop:aspect><aop:aspect>spring-doc.cn

5.5.4. 简介

介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。spring-doc.cn

您可以通过在 . 你可以使用 element 来声明匹配的类型有一个新的父级(因此得名)。 例如,给定一个名为 的接口和该接口的实现,以下方面声明 service 的所有实现者 interfaces 还实现 interface。(为了公开统计数据 例如,通过 JMX。aop:declare-parentsaop:aspectaop:declare-parentsUsageTrackedDefaultUsageTrackedUsageTrackedspring-doc.cn

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

然后,支持 bean 的类将包含以下方法:usageTrackingspring-doc.cn

Java
public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
Kotlin
fun recordUsage(usageTracked: UsageTracked) {
    usageTracked.incrementUseCount()
}

要实现的接口由 属性 确定。这 value 是 AspectJ 类型的模式。任何 bean 的 匹配类型实现接口。请注意,在之前的 建议,服务 Bean 可以直接用作 界面。要以编程方式访问 bean,您可以编写 以后:implement-interfacetypes-matchingUsageTrackedUsageTrackedspring-doc.cn

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.5.5. 方面实例化模型

模式定义的方面唯一支持的实例化模型是 singleton 型。将来的版本可能支持其他实例化模型。spring-doc.cn

5.5.6. 顾问

“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。spring-doc.cn

Spring 通过元素支持 advisor 概念。你最 通常看到它与 transactional advice 结合使用,后者也有自己的 namespace 支持。以下示例显示了一个 advisor:<aop:advisor>spring-doc.cn

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

除了前面示例中使用的属性外,您还可以使用该属性来内联定义切入点表达式。pointcut-refpointcutspring-doc.cn

要定义 advisor 的优先级,以便 advice 可以参与排序, 使用 attribute 来定义 advisor 的值。orderOrderedspring-doc.cn

5.5.7. AOP 模式示例

本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。spring-doc.cn

业务服务的执行有时会由于并发问题(对于 例如,一个死锁失败者)。如果重试操作,则可能会成功 下次尝试时。对于适合在此类 条件(无需因冲突而返回给用户的幂等操作 resolution),我们希望以透明方式重试该操作,以避免客户端看到 .这是一个明确贯穿的要求 服务层中的多个服务,因此非常适合通过 方面。PessimisticLockingFailureExceptionspring-doc.cn

因为我们想要重试操作,所以我们需要使用 around advice,以便我们可以 多次调用。下面的清单显示了基本的 aspect 实现 (这是一个使用 schema 支持的常规 Java 类):proceedspring-doc.cn

Java
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2

    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

请注意,该 aspect 实现了接口,以便我们可以设置 方面高于 Transaction Advice(我们希望每次 retry)。和 属性都是由 Spring 配置的。这 主要操作发生在 Around Advice 方法中。我们尝试 进行。如果我们失败了 ,我们再试一次, 除非我们已经用尽了所有的重试尝试。OrderedmaxRetriesorderdoConcurrentOperationPessimisticLockingFailureExceptionspring-doc.cn

这个类与@AspectJ示例中使用的类相同,但使用 已删除注释。

对应的 Spring 配置如下:spring-doc.cn

<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

        <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

请注意,目前,我们假设所有业务服务都是幂等的。如果 事实并非如此,我们可以优化 Aspect 以便它只真正重试 幂等操作,通过引入注解并使用注解 对 Service 操作的实现进行注释,如下例所示:Idempotentspring-doc.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // marker annotation
}

这 将 aspect 更改为仅重试幂等操作涉及优化 pointcut 表达式,以便仅匹配操作,如下所示:@Idempotentspring-doc.cn

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>

5.6. 选择要使用的 AOP 声明样式

一旦您确定某个 aspect 是实现给定 需求,你如何决定是使用 Spring AOP 还是 AspectJ 以及 方面语言(代码)样式、@AspectJ 注释样式还是 Spring XML 样式?这些 决策受多种因素影响,包括应用程序要求、 开发工具,以及团队对 AOP 的熟悉程度。spring-doc.cn

5.6.1. Spring AOP 还是完整的 AspectJ?

使用最简单的方法。Spring AOP 比使用完整的 AspectJ 更简单,因为 不需要将 AspectJ 编译器/编织器引入到你的开发中 并构建流程。如果只需要在 Spring 上通知操作的执行 beans 中,Spring AOP 是正确的选择。如果您需要通知 不受 管理 的对象 Spring 容器(通常例如域对象)中,您需要使用 AspectJ 的如果你希望通知 join points 而不是 简单的方法执行(例如,字段 Get 或 Set 连接点等)。spring-doc.cn

当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,则已为您做出选择:使用代码样式。如果 aspect 播放 large 角色,并且您可以使用 AspectJ Development Tools (AJDT) 插件,AspectJ 语言语法是 首选选项。它更简洁、更简单,因为该语言是有意为之的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中没有主要作用,您可能需要考虑使用 @AspectJ样式,在 IDE 中坚持使用常规 Java 编译,并在 IDE 中添加 构建脚本的 aspect 编织阶段。spring-doc.cn

5.6.2. Spring AOP 的 @AspectJ 还是 XML?

如果您选择使用 Spring AOP,则可以选择 @AspectJ 或 XML 样式。 需要考虑各种权衡。spring-doc.cn

XML 样式对于现有的 Spring 用户来说可能是最熟悉的,并且它由真正的 POJO 的。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个很好的 choice(一个很好的测试是,您是否认为 pointcut 表达式是 配置)。对于 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。spring-doc.cn

XML 样式有两个缺点。首先,它没有完全封装 它在一个地方实现它所解决的需求。DRY 原则说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解需求如何 的实现被拆分到支持 Bean 类的声明和 XML 中 配置文件。当您使用 @AspectJ 样式时,此信息将被封装 在单个模块中:Aspect。其次,XML 样式在哪些方面稍受限制 它能表达的比@AspectJ风格:只有 “singleton” 方面实例化模型 ,并且无法组合在 XML 中声明的命名切入点。 例如,在 @AspectJ 样式中,您可以编写如下内容:spring-doc.cn

Java
@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
Kotlin
@Pointcut("execution(* get*())")
fun propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}

在 XML 样式中,您可以声明前两个切入点:spring-doc.cn

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XML 方法的缺点是不能通过组合这些定义来定义切入点。accountPropertyAccessspring-doc.cn

@AspectJ 样式支持其他实例化模型和更丰富的切入点 组成。它的优点是将方面保持为模块化单元。它还具有 @AspectJ方面可以被理解(从而消费)的优势 Spring AOP 和 AspectJ.因此,如果您稍后决定需要 AspectJ 的功能 为了实现其他需求,您可以轻松地迁移到经典的 AspectJ 设置。 总的来说,Spring 团队更喜欢 @AspectJ 风格来自定义 aspects,而不仅仅是简单的 企业服务的配置。spring-doc.cn

5.7. 混合 aspect 类型

通过使用自动代理支持,完全可以混合@AspectJ样式方面, 模式定义的方面、声明的顾问程序,甚至代理 以及相同配置中其他样式的拦截器。所有这些都已实现 通过使用相同的底层支持机制,可以毫无困难地共存。<aop:aspect><aop:advisor>spring-doc.cn

5.8. 代理机制

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的 target 对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是一种常见的 开源类定义库(重新打包为 )。spring-corespring-doc.cn

如果要代理的目标对象实现了至少一个接口,则 JDK 动态 proxy 的 Proxy 的 S S S T目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则会创建一个 CGLIB 代理。spring-doc.cn

如果要强制使用 CGLIB 代理(例如,代理每个方法 为目标对象定义,而不仅仅是由其接口实现的对象), 您可以这样做。但是,您应该考虑以下问题:spring-doc.cn

  • 使用 CGLIB 时,不能建议方法,因为它们不能在 运行时生成的子类。finalspring-doc.cn

  • 从 Spring 4.0 开始,你的代理对象的构造函数不再被调用两次, 因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当您的 JVM 执行 不允许绕过构造函数,则可能会看到 double invocations 和 来自 Spring 的 AOP 支持的相应调试日志条目。spring-doc.cn

要强制使用 CGLIB 代理,请设置属性 的元素设置为 true,如下所示:proxy-target-class<aop:config>spring-doc.cn

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用 @AspectJ 自动代理支持时强制使用 CGLIB 代理,请将元素的属性设置为 , 如下:proxy-target-class<aop:aspectj-autoproxy>truespring-doc.cn

<aop:aspectj-autoproxy proxy-target-class="true"/>

多个部分折叠为单个统一的自动代理创建器 在运行时,这将应用任何部分(通常来自不同的 XML Bean 定义文件)指定的最强代理设置。 这也适用于 和 元素。<aop:config/><aop:config/><tx:annotation-driven/><aop:aspectj-autoproxy/>spring-doc.cn

需要明确的是,使用 on 、 或 elements 会强制使用 CGLIB 他们三个的代理。proxy-target-class="true"<tx:annotation-driven/><aop:aspectj-autoproxy/><aop:config/>spring-doc.cn

5.8.1. 理解 AOP 代理

Spring AOP 是基于代理的。掌握 在你编写自己的方面或使用任何 Spring 框架提供的基于 Spring AOP 的方面。spring-doc.cn

首先考虑你有一个普通的、未代理的、 nothing-special-about-it,直接对象引用,如下所示 代码片段显示:spring-doc.cn

Java
public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}
Kotlin
class SimplePojo : Pojo {

    fun foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar()
    }

    fun bar() {
        // some logic...
    }
}

如果在对象引用上调用方法,则会直接在 该对象引用,如下图所示:spring-doc.cn

AOP 代理 PLAIN POJO Call
Java
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}
Kotlin
fun main() {
    val pojo = SimplePojo()
    // this is a direct method call on the 'pojo' reference
    pojo.foo()
}

当客户端代码具有的引用是代理时,情况会略有变化。考虑一下 下图和代码片段如下:spring-doc.cn

AOP 代理调用
Java
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
Kotlin
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

这里要了解的关键是,方法 的类具有对代理的引用。这意味着该方法调用 对象引用是对代理的调用。因此,代理可以将 与该特定方法调用相关的拦截器 (通知)。然而 一旦调用最终到达目标对象( 在这种情况下),它可能对自身进行的任何方法调用(例如 或 )都将针对引用调用,而不是针对代理调用。 这具有重要意义。这意味着 self-invocation 不会产生 在与方法调用关联的建议中,获得运行的机会。main(..)MainSimplePojothis.bar()this.foo()thisspring-doc.cn

那么,该怎么办呢?最佳方法(使用术语“最佳” 松散地在这里)是重构你的代码,这样就不会发生自调用。 这确实需要您做一些工作,但这是最好的、侵入性最小的方法。 接下来的方法绝对是可怕的,我们不愿准确地指出它 因为它太可怕了。你可以(尽管这对我们来说很痛苦)完全捆绑了这个逻辑 在你的类中添加到 Spring AOP,如下例所示:spring-doc.cn

Java
public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}
Kotlin
class SimplePojo : Pojo {

    fun foo() {
        // this works, but... gah!
        (AopContext.currentProxy() as Pojo).bar()
    }

    fun bar() {
        // some logic...
    }
}

这将你的代码完全耦合到 Spring AOP,并且它使类本身知道 它被用于 AOP 上下文的事实,这与 AOP 背道而驰。它 在创建代理时还需要一些额外的配置,因为 以下示例显示:spring-doc.cn

Java
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
Kotlin
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    factory.isExposeProxy = true

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

最后,必须注意的是,AspectJ 没有这个自调用问题,因为 它不是一个基于代理的 AOP 框架。spring-doc.cn

5.9. 以编程方式创建 @AspectJ 代理

除了使用 or 在配置中声明 aspects 之外,还可以以编程方式创建代理 通知目标对象。有关 Spring 的 AOP API 的完整详细信息,请参见下一章。在这里,我们想重点介绍自动 使用 @AspectJ 方面创建代理。<aop:config><aop:aspectj-autoproxy>spring-doc.cn

您可以使用 class 为一个或多个 @AspectJ 方面建议的目标对象创建代理。 此类的基本用法非常简单,如下例所示:org.springframework.aop.aspectj.annotation.AspectJProxyFactoryspring-doc.cn

Java
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Kotlin
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager::class.java)

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker)

// now get the proxy object...
val proxy = factory.getProxy<Any>()

有关更多信息,请参阅 javadocspring-doc.cn

5.10. 在 Spring 应用程序中使用 AspectJ

到目前为止,我们在本章中介绍的所有内容都是纯粹的 Spring AOP。在本节中, 我们看看如何使用 AspectJ 编译器或 weaver 来代替 OR IN 如果您的需求超出了 Spring AOP 提供的功能,则可添加到 Spring AOP 中 独自。spring-doc.cn

Spring 附带了一个小型的 AspectJ 方面库,该库可在 分发为 .您需要按顺序将其添加到 Classpath 中 以使用其中的 aspects。使用 AspectJ 对 Spring 和 Spring 的其他 Spring 方面进行依赖注入 Domain Objects 和 AspectJ 讨论 此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面 讨论了如何 dependency 注入使用 AspectJ 编译器编织的 AspectJ 切面。最后,Spring 框架中使用 AspectJ 的加载时编织 提供了对 Spring 应用程序的加载时编织的介绍 使用 AspectJ。spring-aspects.jarspring-doc.cn

5.10.1. 使用 AspectJ 对 Spring 的域对象进行依赖注入

Spring 容器实例化并配置应用程序中定义的 bean 上下文。也可以要求 bean factory 配置预先存在的 object,给定包含要应用的配置的 bean 定义的名称。 包含一个 annotation-driven 切面,该 aspect 利用此 允许对任何对象进行依赖关系注入的能力。该支持旨在 用于在任何容器的控制之外创建的对象。域对象 通常属于这一类,因为它们通常是使用 Operator 以编程方式创建的,或者作为数据库查询的结果由 ORM 工具创建。spring-aspects.jarnewspring-doc.cn

该 Comments 将类标记为符合 Spring 驱动的条件 配置。在最简单的情况下,您可以将其单独用作标记注释,因为 以下示例显示:@Configurablespring-doc.cn

Java
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}
Kotlin
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
    // ...
}

当以这种方式用作标记接口时, Spring 会配置 注解类型(在本例中为)使用 Bean 定义(通常为 prototype-scoped),其名称与完全限定类型名称相同 ().由于 Bean 的默认名称是 其类型的完全限定名称,这是声明原型定义的便捷方式 是省略该属性,如下例所示:Accountcom.xyz.myapp.domain.Accountidspring-doc.cn

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果要显式指定要使用的原型 bean 定义的名称,则 可以直接在 Comments 中执行此操作,如下例所示:spring-doc.cn

Java
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}
Kotlin
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable("account")
class Account {
    // ...
}

Spring 现在查找名为 的 bean 定义,并将其用作 定义来配置新实例。accountAccountspring-doc.cn

您还可以使用自动装配来避免在 都。要让 Spring 应用自动装配,请使用 Comments 的属性。您可以按类型或名称为自动装配指定 或 。 分别。作为替代方案,最好指定显式的、注解驱动的 通过字段或方法级别或在字段或方法级别对 bean 进行依赖注入(有关更多详细信息,请参阅基于 Comments 的容器配置)。autowire@Configurable@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME@Configurable@Autowired@Injectspring-doc.cn

最后,您可以为新的 使用属性 (例如 ) 创建和配置对象 。如果此属性为 设置为 ,Spring 在配置后验证所有属性(其 不是基元或集合)的dependencyCheck@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)truespring-doc.cn

请注意,单独使用 Comments 不会执行任何操作。正是 in 作用于 注释。从本质上讲,该 aspect 表示,“从 带有 注释 的新对象,配置新创建的对象 根据注释的属性使用 Spring”。在此上下文中, “initialization” 是指新实例化的对象(例如,实例化的对象 使用运算符)以及正在发生的对象 反序列化(例如,通过 readResolve())。AnnotationBeanConfigurerAspectspring-aspects.jar@ConfigurablenewSerializablespring-doc.cn

上一段中的关键词之一是“本质上”。在大多数情况下, “After returning from the initialization of a new object” 的确切语义是 好。在这种情况下,“初始化后”意味着依赖项是 在构造对象后注入。这意味着依赖项 不能在类的构造函数主体中使用。如果您希望 依赖项,以便在构造函数主体运行之前注入,因此 可用于构造函数的主体,您需要在声明中定义它,如下所示:@Configurablespring-doc.cn

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

您可以找到有关各种切入点的语言语义的更多信息 类型 AspectJ 的附录 编程指南spring-doc.cn

为此,必须用 AspectJ weaver 编织带注释的类型。您可以 使用构建时 Ant 或 Maven 任务来执行此操作(例如,参见 AspectJ 开发 Environment Guide)或加载时编织(参见 Spring 框架中使用 AspectJ 的加载时编织)。本身需要由 Spring 配置(为了获得 对用于配置新对象的 Bean Factory 的引用)。如果你 使用基于 Java 的配置,您可以添加到任何类,如下所示:AnnotationBeanConfigurerAspect@EnableSpringConfigured@Configurationspring-doc.cn

Java
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
Kotlin
@Configuration
@EnableSpringConfigured
class AppConfig {
}

如果你更喜欢基于 XML 的配置,Spring 上下文名称空间定义了一个方便的元素,你可以按如下方式使用该元素:context:spring-configuredspring-doc.cn

<context:spring-configured/>

在配置 aspect 之前创建的对象的实例 导致向调试日志发出一条消息,并且没有配置 对象正在发生。一个例子可能是 Spring 配置中的一个 bean,它创建 domain 对象。在这种情况下,您可以使用 bean 属性手动指定 bean 依赖于 configuration 方面。以下示例演示如何使用该属性:@Configurabledepends-ondepends-onspring-doc.cn

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>
不要通过 bean 配置器方面激活处理,除非你 真的意味着在运行时依赖它的语义。特别是,请确保您这样做 不用于注册为常规 Spring bean 的 bean 类 与容器一起。这样做会导致双重初始化,一次通过 容器和一次通过方面。@Configurable@Configurable
单元测试对象@Configurable

支持的目标之一是启用独立的单元测试 的域对象,而没有与硬编码查找相关的困难。 如果类型尚未被 AspectJ 编织,则注解没有影响 在单元测试期间。您可以在 测试并照常进行。如果类型已经被 AspectJ 编织,则 您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告 message 中,每次构造一个对象时,都会显示该对象具有 未由 Spring 配置。@Configurable@Configurable@Configurable@Configurablespring-doc.cn

使用多个应用程序上下文

用于实施支持的 是 AspectJ 单例 aspect。单例 aspect 的 scope 与 scope 相同 of members:每个 classloader 都有一个定义类型的 aspect 实例。 这意味着,如果在同一类加载器中定义多个应用程序上下文 hierarchy,则需要考虑在何处定义 bean 和 在 Classpath 上放置的位置。AnnotationBeanConfigurerAspect@Configurablestatic@EnableSpringConfiguredspring-aspects.jarspring-doc.cn

考虑一个典型的 Spring Web 应用程序配置,该配置具有共享的父应用程序 定义常见业务服务的上下文,支持这些服务所需的一切, 以及每个 servlet 的一个子应用程序上下文(其中包含特定于 添加到该 Servlet 中)。所有这些上下文都共存于同一个 classloader 层次结构中, 因此 可以只保存对其中一个的引用。 在这种情况下,我们建议在共享的 (父)应用程序上下文。这定义了你可能想要的服务 inject 到 domain objects 中。结果是您无法配置域对象 引用子(特定于 servlet)上下文中定义的 bean,方法是使用 @Configurable机制(这可能不是你想做的事情)。AnnotationBeanConfigurerAspect@EnableSpringConfiguredspring-doc.cn

在同一容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的 ClassLoader 加载类型 (例如,通过放置在 中)。If 仅添加到容器范围的 Classpath 中(因此由共享父级加载 classloader),所有 Web 应用程序共享相同的 aspect 实例(可能是 不是你想要的)。spring-aspects.jarspring-aspects.jar'WEB-INF/lib'spring-aspects.jarspring-doc.cn

5.10.2. AspectJ 的其他 Spring 方面

除了 aspect 之外,还包含一个 AspectJ 方面,您可以使用它来驱动 Spring 的类型和方法的事务管理 使用注释进行注释。这主要适用于满足以下条件的用户 希望在 Spring 容器之外使用 Spring Framework 的事务支持。@Configurablespring-aspects.jar@Transactionalspring-doc.cn

解释注释的方面是 .当您使用此 aspect 时,您必须注释 implementation 类(或该类中的方法,或两者),而不是接口(如果 any) 实现。AspectJ 遵循 Java 的规则,即 接口不是继承的。@TransactionalAnnotationTransactionAspectspring-doc.cn

类上的注释指定了 类中任何公共操作的执行。@Transactionalspring-doc.cn

类中方法的注释将覆盖默认值 类注解(如果存在)给出的交易语义。任何 visibility 可以被注释,包括 private methods。对非公共方法进行注解 directly 是获取执行此类方法的事务划分的唯一方法。@Transactionalspring-doc.cn

从 Spring Framework 4.2 开始,提供了一个类似的方面,它提供了 与 Standard 注释的功能完全相同。查看更多详情。spring-aspectsjavax.transaction.TransactionalJtaAnnotationTransactionAspect

对于想要使用 Spring 配置和事务的 AspectJ 程序员 管理支持,但不想(或不能)使用注解,也包含您可以扩展以提供自己的切入点的方面 定义。有关更多信息,请参阅 和 方面的来源。例如,以下 Excerpt 展示了如何编写一个 aspect 来配置对象的所有实例 使用与 完全限定的类名:spring-aspects.jarabstractAbstractBeanConfigurerAspectAbstractTransactionAspectspring-doc.cn

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        CommonPointcuts.inDomainModel() &&
        this(beanInstance);
}

5.10.3. 使用 Spring IoC 配置 AspectJ 方面

当你在 Spring 应用程序中使用 AspectJ 方面时,很自然地需要和 期望能够使用 Spring 配置这些方面。AspectJ 运行时本身是 负责 aspect 的创建,以及配置 AspectJ 创建的 通过 Spring 的 aspects 依赖于 AspectJ 实例化模型(子句) 由 aspect 使用。per-xxxspring-doc.cn

大多数 AspectJ 方面都是单例方面。这些的配置 方面很容易。您可以创建一个引用 aspect 类型的 bean 定义 normal 并包含 bean 属性。这可确保 Spring 通过向 AspectJ 请求 aspect 实例来获取它,而不是尝试创建 实例本身。以下示例演示如何使用该属性:factory-method="aspectOf"factory-method="aspectOf"spring-doc.cn

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> (1)

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 请注意属性factory-method="aspectOf"

非单例方面更难配置。但是,可以通过以下方式执行此操作 创建原型 bean 定义并使用 support from 来配置 aspect 实例,一旦它们由 AspectJ 运行时。@Configurablespring-aspects.jarspring-doc.cn

如果你有一些 @AspectJ 方面你想用 AspectJ 编织(例如, 对域模型类型使用加载时编织)和其他所需的@AspectJ方面 与 Spring AOP 一起使用,并且这些方面都在 Spring 中配置,那么 需要告诉 Spring AOP @AspectJ 自动代理支持是 配置中定义的@AspectJ方面都应用于自动代理。您可以 通过在声明中使用一个或多个元素来执行此操作。每个元素都指定一个名称模式,并且只有 与至少一个模式匹配的名称用于 Spring AOP 自动代理 配置。以下示例演示如何使用元素:<include/><aop:aspectj-autoproxy/><include/><include/>spring-doc.cn

<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被元素的名称误导。使用 导致创建 Spring AOP 代理。aspect 的 @AspectJ 样式 声明,但不涉及 AspectJ 运行时。<aop:aspectj-autoproxy/>

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

加载时编织 (LTW) 是指将 AspectJ 方面编织成 应用程序的类文件,因为它们正在加载到 Java 虚拟机 (JVM) 中。 本节的重点是在 Spring 框架。本节不是 LTW 的一般介绍。有关完整详细信息 LTW 的细节和仅使用 AspectJ 配置 LTW(Spring 不是 involved 的 lw),参见 AspectJ 的 LTW 部分 开发环境指南spring-doc.cn

Spring 框架为 AspectJ LTW 带来的价值在于支持很多 对编织过程进行更精细的控制。'Vanilla' AspectJ LTW 通过使用 Java (5+) 代理,在启动 JVM 的 JVM 中。因此,它是一个 JVM 范围的设置,在某些情况下可能很好,但通常是一个 有点太粗糙了。启用 Spring 的 LTW 允许您在 per- basis 的 在“single-JVM-multiple-application”环境中(例如在典型的 应用程序服务器环境)。ClassLoaderspring-doc.cn

此外,在某些环境中,此支持支持 加载时编织,而无需对 Application Server 的启动进行任何修改 需要添加的脚本或 (正如我们所描述的 本节稍后部分)。开发人员配置 应用程序上下文,用于启用加载时编织,而不是依赖管理员 通常负责部署配置(如 Launch Script)的人员。-javaagent:path/to/aspectjweaver.jar-javaagent:path/to/spring-instrument.jarspring-doc.cn

现在销售宣传已经结束,让我们首先看一个 AspectJ 的快速示例 LTW,后跟有关 例。有关完整示例,请参阅 Petclinic 示例应用程序spring-doc.cn

第一个例子

假设您是一名应用程序开发人员,其任务是诊断 系统中某些性能问题的原因。而不是分解 profiling 工具中,我们将打开一个简单的性能分析方面,它允许我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具复制到该特定区域。spring-doc.cn

此处显示的示例使用 XML 配置。您还可以配置和 将 @AspectJ 与 Java 配置结合使用。具体来说,您可以使用 annotation 作为 Comments 的替代方法(有关详细信息,请参阅下文)。@EnableLoadTimeWeaving<context:load-time-weaver/>

下面的示例显示了性能分析方面,这并不花哨。 它是一个基于时间的分析器,它使用 @AspectJ 样式的 aspect 声明:spring-doc.cn

Java
package foo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch sw = new StopWatch(getClass().getSimpleName());
        try {
            sw.start(pjp.getSignature().getName());
            return pjp.proceed();
        } finally {
            sw.stop();
            System.out.println(sw.prettyPrint());
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    public void methodsToBeProfiled(){}
}
Kotlin
package foo

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order

@Aspect
class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    fun profile(pjp: ProceedingJoinPoint): Any {
        val sw = StopWatch(javaClass.simpleName)
        try {
            sw.start(pjp.getSignature().getName())
            return pjp.proceed()
        } finally {
            sw.stop()
            println(sw.prettyPrint())
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    fun methodsToBeProfiled() {
    }
}

我们还需要创建一个文件,以通知 AspectJ weaver 我们想将 OUR 编织到我们的 class 中。这个文件约定,即 Java 类路径上存在一个名为 is 的文件 标准 AspectJ.以下示例显示了该文件:META-INF/aop.xmlProfilingAspectMETA-INF/aop.xmlaop.xmlspring-doc.cn

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>

</aspectj>

现在我们可以继续进行配置的 Spring 特定部分。我们需要 来配置 A(稍后解释)。此加载时 weaver 是 essential 组件,负责将 aspect 配置编织到一个 OR more 文件添加到应用程序的类中。好处 问题是它不需要很多配置(还有更多 选项,但稍后会详细介绍),如 以下示例:LoadTimeWeaverMETA-INF/aop.xmlspring-doc.cn

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

    <!-- a service object; we will be profiling its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>

    <!-- this switches on the load-time weaving -->
    <context:load-time-weaver/>
</beans>

现在所有必需的工件(方面、文件和 Spring 配置)都已就位,我们可以创建以下内容 driver 类,其中包含一个演示 LTW 的方法:META-INF/aop.xmlmain(..)spring-doc.cn

Java
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");

        // the profiling aspect is 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService

    // the profiling aspect is 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}

我们还有最后一件事要做。本节的引言确实说过,人们可以 使用 Spring 有选择地打开 LTW,这是真的。 但是,在此示例中,我们使用 Java 代理(随 Spring 提供)来开启 LTW。 我们使用以下命令来运行前面显示的类:ClassLoaderMainspring-doc.cn

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

是用于指定和启用代理的标志 检测在 JVM 上运行的程序。Spring Framework 附带了这样一个 agent 的 ,它打包在 中,作为 中的参数值提供 前面的示例。-javaagentInstrumentationSavingAgentspring-instrument.jar-javaagentspring-doc.cn

程序执行的输出类似于下一个示例。 (我在实现中引入了一个语句,以便分析器实际捕获 0 以外的内容 毫秒(毫秒不是 AOP 引入的开销)。 下面的清单显示了我们在运行 profiler 时得到的输出:MainThread.sleep(..)calculateEntitlement()01234spring-doc.cn

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于这个 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于提供建议 春豆。程序的以下细微变化产生相同的结果 结果:Mainspring-doc.cn

Java
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                new StubEntitlementCalculationService();

        // the profiling aspect will be 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main(args: Array<String>) {
    ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = StubEntitlementCalculationService()

    // the profiling aspect will be 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}

请注意,在前面的程序中,我们如何引导 Spring 容器并 然后创建一个 Totally Outside 的新实例 Spring 的上下文。分析建议仍然被编织在一起。StubEntitlementCalculationServicespring-doc.cn

诚然,这个例子很简单。但是,Spring 中 LTW 支持的基础知识 在前面的示例中都已介绍过,本节的其余部分将解释 每个配置和用法背后的 “为什么” 都很详细。spring-doc.cn

此示例中使用的 可能是基本的,但它非常有用。它是一个 开发人员可以在开发过程中使用的开发时方面的一个很好的示例 然后轻松地从正在部署的应用程序的构建中排除 进入 UAT 或生产。ProfilingAspect
方面

你在 LTW 中使用的 aspect 必须是 AspectJ 方面。您可以将它们写入 要么是 AspectJ 语言本身,要么你可以用 @AspectJ 风格编写你的 Aspects。 因此,你的 aspect 都是有效的 AspectJ 和 Spring AOP 方面。 此外,编译后的 aspect 类需要在 Classpath 上可用。spring-doc.cn

'元信息/aop.xml'

AspectJ LTW 基础结构是通过使用 Java 类路径上的一个或多个文件(直接或更常见的,在 jar 文件中)来配置的。META-INF/aop.xmlspring-doc.cn

此文件的结构和内容在 AspectJ 参考的 LTW 部分中有详细说明 文档。因为这个文件是 100% 的 AspectJ,所以我们在这里不再进一步描述它。aop.xmlspring-doc.cn

必需的库 (JAR)

至少,您需要以下库才能使用 Spring Framework 的支持 对于 AspectJ LTW:spring-doc.cn

Spring 配置

Spring 的 LTW 支持的关键组件是接口(在包中)和众多实现 的 Spring 发行版。A 负责 向 AT 添加一个或多个 runtime 的 runtime,它为各种有趣的应用程序打开了大门,其中之一就是 恰好是 aspects 的 LTW。LoadTimeWeaverorg.springframework.instrument.classloadingLoadTimeWeaverjava.lang.instrument.ClassFileTransformersClassLoaderspring-doc.cn

如果您不熟悉运行时类文件转换的概念,请参阅 javadoc API 文档。 虽然该文档并不全面,但至少您可以看到关键界面 和 classes (供你阅读本节时参考)。java.lang.instrument

为特定配置 可以像 添加一行。(请注意,您几乎肯定需要使用 an 作为 Spring 容器 — 通常,a 不是 足够了,因为 LTW 支持使用 .)LoadTimeWeaverApplicationContextApplicationContextBeanFactoryBeanFactoryPostProcessorsspring-doc.cn

要启用 Spring 框架的 LTW 支持,你需要配置一个 , 这通常通过使用 Annotation 来完成,如下所示:LoadTimeWeaver@EnableLoadTimeWeavingspring-doc.cn

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

或者,如果您更喜欢基于 XML 的配置,请使用 element.请注意,该元素是在命名空间中定义的。以下示例演示如何使用:<context:load-time-weaver/>context<context:load-time-weaver/>spring-doc.cn

<?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:load-time-weaver/>

</beans>

前面的配置会自动定义并注册一些特定于 LTW 的 基础结构 Bean,例如 a 和 an 。 默认值是类,该类会尝试 以装饰自动检测到的 .“自动检测”的确切类型取决于您的运行时环境。 下表总结了各种实现:LoadTimeWeaverAspectJWeavingEnablerLoadTimeWeaverDefaultContextLoadTimeWeaverLoadTimeWeaverLoadTimeWeaverLoadTimeWeaverspring-doc.cn

表 13.DefaultContextLoadTimeWeaver LoadTimeWeaver
运行时环境 LoadTimeWeaver实现

Apache Tomcat 中运行spring-doc.cn

TomcatLoadTimeWeaverspring-doc.cn

GlassFish 中运行(仅限于 EAR 部署)spring-doc.cn

GlassFishLoadTimeWeaverspring-doc.cn

在 Red Hat 的 JBoss ASWildFly 中运行spring-doc.cn

JBossLoadTimeWeaverspring-doc.cn

在 IBM 的 WebSphere 中运行spring-doc.cn

WebSphereLoadTimeWeaverspring-doc.cn

在 Oracle 的 WebLogic 中运行spring-doc.cn

WebLogicLoadTimeWeaverspring-doc.cn

JVM 始于 SpringInstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)spring-doc.cn

InstrumentationLoadTimeWeaverspring-doc.cn

Fallback,期望底层 ClassLoader 遵循通用约定 (即和可选的方法)addTransformergetThrowawayClassLoaderspring-doc.cn

ReflectiveLoadTimeWeaverspring-doc.cn

请注意,该表仅列出了在以下情况下自动检测到的 使用 .您可以准确指定要使用的实现。LoadTimeWeaversDefaultContextLoadTimeWeaverLoadTimeWeaverspring-doc.cn

要使用 Java 配置指定特定,请实现接口并覆盖该方法。 以下示例指定 :LoadTimeWeaverLoadTimeWeavingConfigurergetLoadTimeWeaver()ReflectiveLoadTimeWeaverspring-doc.cn

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {

    override fun getLoadTimeWeaver(): LoadTimeWeaver {
        return ReflectiveLoadTimeWeaver()
    }
}

如果使用基于 XML 的配置,则可以指定完全限定的类名 作为元素上的属性值。同样,以下示例指定了一个 :weaver-class<context:load-time-weaver/>ReflectiveLoadTimeWeaverspring-doc.cn

<?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:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

由配置定义和注册的 可以稍后 使用众所周知的名称从 Spring 容器中检索。 请记住,它仅作为 Spring 的 LTW 的机制存在 基础结构以添加一个或多个 .执行 LTW 的实际值是 (从 package) 类。有关更多详细信息,请参阅类的类级 javadoc,因为具体操作 编织实际发生超出了本文档的范围。LoadTimeWeaverloadTimeWeaverLoadTimeWeaverClassFileTransformersClassFileTransformerClassPreProcessorAgentAdapterorg.aspectj.weaver.loadtimeClassPreProcessorAgentAdapterspring-doc.cn

还有最后一个配置属性需要讨论:属性(或者如果您使用 XML)。此属性控制 LTW 是否启用。它接受三个可能的值之一,默认值为属性不存在。下表总结了这三种 可能的值:aspectjWeavingaspectj-weavingautodetectspring-doc.cn

表 14.AspectJ 编织属性值
注释值 XML 值 解释

ENABLEDspring-doc.cn

onspring-doc.cn

AspectJ 编织已打开,并且 aspect 会根据需要在 load 时进行编织。spring-doc.cn

DISABLEDspring-doc.cn

offspring-doc.cn

LTW 已关闭。在加载时没有编织任何方面。spring-doc.cn

AUTODETECTspring-doc.cn

autodetectspring-doc.cn

如果 Spring LTW 基础结构可以找到至少一个文件, 然后 AspectJ weaving 开启。否则,它处于关闭状态。这是默认值。META-INF/aop.xmlspring-doc.cn

特定于环境的配置

最后一部分包含您需要的任何其他设置和配置 当您在应用程序服务器和 Web 等环境中使用 Spring 的 LTW 支持时 器皿。spring-doc.cn

Tomcat、JBoss、WebSphere、WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 提供能够进行本地检测的通用应用程序。Spring的 native LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。 如前所述,您只需启用 load-time weaving 具体来说,您无需修改 JVM 启动脚本即可添加 .ClassLoader-javaagent:path/to/spring-instrument.jarspring-doc.cn

请注意,在 JBoss 上,你可能需要禁用应用服务器扫描以防止它 在应用程序实际启动之前加载类。快速解决方法是将 将一个名为以下内容的文件添加到项目中:WEB-INF/jboss-scanning.xmlspring-doc.cn

<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序

当在 不支持的环境中需要类插桩时 具体实现,JVM 代理是通用的解决方案。 对于这种情况, Spring 提供了需要 特定于 Spring 的(但非常通用的)JVM 代理 ,自动检测 由 common 和 setups。LoadTimeWeaverInstrumentationLoadTimeWeaverspring-instrument.jar@EnableLoadTimeWeaving<context:load-time-weaver/>spring-doc.cn

要使用它,您必须通过提供 Spring 代理来启动虚拟机 以下 JVM 选项:spring-doc.cn

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改 JVM 启动脚本,这可能会阻止您 在应用程序服务器环境中使用它(取决于您的服务器和 操作策略)。也就是说,对于每个 JVM 一个应用程序的部署,例如独立的 Spring Boot 应用程序,您通常在任何情况下都可以控制整个 JVM 设置。spring-doc.cn

5.11. 更多资源

有关 AspectJ 的更多信息可以在 AspectJ 网站上找到。spring-doc.cn

Eclipse AspectJ 作者:Adrian Colyer etal. (Addison-Wesley, 2005) 提供了一个 AspectJ 语言的全面介绍和参考。spring-doc.cn

AspectJ in Action, Second Edition 作者:Ramnivas Laddad(Manning,2009 年)高度评价 推荐。本书的重点是 AspectJ,但许多通用的 AOP 主题是 探索(在一定程度上)。spring-doc.cn

6. Spring AOP API

上一章介绍了 Spring 对 AOP 的支持,包括基于 @AspectJ 和 schema aspect 定义。在本章中,我们将讨论较低级别的 Spring AOP API。对于普通 应用程序,我们建议将 Spring AOP 与 AspectJ 切入点一起使用,如 上一章。spring-doc.cn

6.1. Spring 中的 Pointcut API

本节描述了 Spring 如何处理关键的切入点概念。spring-doc.cn

6.1.1. 概念

Spring 的切入点模型支持独立于通知类型的切入点重用。您可以 使用相同的切入点定位不同的建议。spring-doc.cn

interface 是中央接口,用于 将 Advice 定位到特定的类和方法。完整的界面如下:org.springframework.aop.Pointcutspring-doc.cn

Java
public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}
Kotlin
interface Pointcut {

    fun getClassFilter(): ClassFilter

    fun getMethodMatcher(): MethodMatcher

}

将接口拆分为两部分允许重用类和方法 匹配部分和精细组合操作(例如执行 “union” 替换为另一个方法 matcher)。Pointcutspring-doc.cn

该接口用于将切入点限制为一组给定的目标 类。如果该方法始终返回 true,则所有目标类都是 匹配。下面的清单显示了接口定义:ClassFiltermatches()ClassFilterspring-doc.cn

Java
public interface ClassFilter {

    boolean matches(Class clazz);
}
Kotlin
interface ClassFilter {

    fun matches(clazz: Class<*>): Boolean
}

界面通常更重要。完整的界面如下:MethodMatcherspring-doc.cn

Java
public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}
Kotlin
interface MethodMatcher {

    val isRuntime: Boolean

    fun matches(m: Method, targetClass: Class<*>): Boolean

    fun matches(m: Method, targetClass: Class<*>, args: Array<Any>): Boolean
}

该方法用于测试此切入点是否曾经 匹配目标类上的给定方法。当 AOP 创建 proxy 是为了避免在每次方法调用时都需要进行测试。如果 两个参数的方法返回给定方法,并且 MethodMatcher 的方法返回 ,三个参数匹配方法是 在每次方法调用时调用。这允许切入点查看传递的参数 添加到紧接 Target 通知开始之前的方法调用。matches(Method, Class)matchestrueisRuntime()truespring-doc.cn

大多数 implementations 是静态的,这意味着它们的方法 返回。在这种情况下,永远不会调用三参数方法。MethodMatcherisRuntime()falsematchesspring-doc.cn

如果可能,请尝试将切入点设为静态,允许 AOP 框架缓存 创建 AOP 代理时的切入点评估结果。

6.1.2. 对切入点的操作

Spring 支持对切入点进行操作(特别是 union 和 intersection)。spring-doc.cn

Union 表示任一切入点匹配的方法。 Intersection 表示两个切入点匹配的方法。 Union 通常更有用。 您可以通过使用类中的静态方法或在同一包中使用该类来编写切入点。但是,使用 AspectJ 切入点 表达式通常是一种更简单的方法。org.springframework.aop.support.PointcutsComposablePointcutspring-doc.cn

6.1.3. AspectJ 表达式切入点

从 2.0 开始,Spring 使用的最重要的切入点类型是 。这是一个切入点 使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串。org.springframework.aop.aspectj.AspectJExpressionPointcutspring-doc.cn

有关支持的 AspectJ 切入点原语的讨论,请参见上一章spring-doc.cn

6.1.4. 方便的切入点实现

Spring 提供了几个方便的切入点实现。您可以使用其中的一些 径直;其他的旨在在特定于应用程序的切入点中进行子类化。spring-doc.cn

静态切入点

静态切入点基于方法和目标类,不能考虑 方法的参数。对于大多数用法,静态切入点就足够了,而且是最好的。 Spring 只能在首次调用方法时对静态切入点进行一次求值。 之后,无需在每次方法调用时再次评估切入点。spring-doc.cn

本节的其余部分描述了一些静态切入点实现,这些实现是 包含在 Spring 中。spring-doc.cn

正则表达式切入点

指定静态切入点的一种明显方法是正则表达式。多个 AOP 除了 Spring 之外的框架使这成为可能。 是泛型常规 expression 切入点,该切入点使用 JDK 中的正则表达式支持。org.springframework.aop.support.JdkRegexpMethodPointcutspring-doc.cn

使用该类,您可以提供模式字符串列表。 如果其中任何一个匹配项,则切入点的计算结果为 。(因此, 生成的切入点实际上是指定模式的并集。JdkRegexpMethodPointcuttruespring-doc.cn

以下示例演示如何使用:JdkRegexpMethodPointcutspring-doc.cn

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring 提供了一个名为 的便捷类,它允许我们 也引用 an(请记住,an 可以是拦截器,在通知之前, throws advice 等)。在幕后,Spring 使用 . Using 简化了布线,因为一个 bean 封装了 pointcut 和 advice 一起创建,如下例所示:RegexpMethodPointcutAdvisorAdviceAdviceJdkRegexpMethodPointcutRegexpMethodPointcutAdvisorspring-doc.cn

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

您可以与任何类型的设备一起使用。RegexpMethodPointcutAdvisorAdvicespring-doc.cn

属性驱动的切入点

一种重要的静态切入点类型是元数据驱动的切入点。这将使用 元数据属性的值(通常是源级元数据)。spring-doc.cn

动态切入点

动态切入点的评估成本高于静态切入点。他们考虑了 方法参数以及静态信息。这意味着他们必须 每次方法调用都会进行评估,并且结果不能被缓存,因为参数会 不同。spring-doc.cn

主要示例是切入点。control flowspring-doc.cn

控制流切入点

Spring 控制流切入点在概念上类似于 AspectJ 切入点, 虽然威力较弱。(目前无法指定切入点运行 在与另一个切入点匹配的连接点下方。控制流切入点与 当前调用堆栈。例如,如果连接点由方法 在 package 中或 by 类。控制流切入点 使用类指定。cflowcom.mycompany.webSomeCallerorg.springframework.aop.support.ControlFlowPointcutspring-doc.cn

控制流切入点在运行时的评估成本甚至比 其他动态切入点。在 Java 1.4 中,成本大约是其他动态的 5 倍 切入点。

6.1.5. 切入点超类

Spring 提供了有用的切入点超类来帮助您实现自己的切入点。spring-doc.cn

因为静态切入点最有用,所以您可能应该子类 。这只需要实现一个 abstract 方法(尽管您可以覆盖其他方法来自定义行为)。这 以下示例显示如何 subclass :StaticMethodMatcherPointcutStaticMethodMatcherPointcutspring-doc.cn

Java
class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}
Kotlin
class TestStaticPointcut : StaticMethodMatcherPointcut() {

    override fun matches(method: Method, targetClass: Class<*>): Boolean {
        // return true if custom criteria match
    }
}

还有用于动态切入点的超类。 您可以将自定义切入点与任何通知类型一起使用。spring-doc.cn

6.1.6. 自定义切入点

因为 Spring AOP 中的切入点是 Java 类而不是语言特性(如 AspectJ),你可以声明自定义切入点,无论是静态的还是动态的。习惯 Spring 中的切入点可以是任意复杂的。但是,我们建议使用 AspectJ 切入点 表达式语言(如果可以)。spring-doc.cn

Spring 的更高版本可能会提供对 JAC 提供的“语义切入点”的支持——例如,“更改目标对象中实例变量的所有方法”。

6.2. Spring 中的 Advice API

现在我们可以研究一下 Spring AOP 如何处理建议。spring-doc.cn

6.2.1. 建议生命周期

每个建议都是一个 Spring bean。一个通知实例可以在所有被通知之间共享 对象,或者对于每个被通知的对象是唯一的。这对应于 per-class 或 per-instance 建议。spring-doc.cn

每类建议最常使用。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。它们仅作用于方法和参数。spring-doc.cn

per-instance advice 适合用于 introduction,以支持 mixin。在这种情况下, 该通知将 state 添加到代理对象。spring-doc.cn

您可以在同一个 AOP 代理中混合使用共享建议和每个实例的建议。spring-doc.cn

6.2.2. Spring 中的通知类型

Spring 提供了多种建议类型,并且可扩展以支持 arbitrary advice 类型。本节介绍基本概念和标准通知类型。spring-doc.cn

Interception Around 建议

Spring 中最基本的 advice 类型是 catchion around advice。spring-doc.cn

Spring 与 AOP 接口兼容,用于使用 method 的 around 通知 拦截。实现和围绕 advice 实现的类也应该实现 以下接口:AllianceMethodInterceptorspring-doc.cn

Java
public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}
Kotlin
interface MethodInterceptor : Interceptor {

    fun invoke(invocation: MethodInvocation) : Any
}

该方法的参数公开了 invoked、目标连接点、AOP 代理和方法的参数。该方法应返回调用的结果:join 的返回值 点。MethodInvocationinvoke()invoke()spring-doc.cn

以下示例显示了一个简单的实现:MethodInterceptorspring-doc.cn

Java
public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}
Kotlin
class DebugInterceptor : MethodInterceptor {

    override fun invoke(invocation: MethodInvocation): Any {
        println("Before: invocation=[$invocation]")
        val rval = invocation.proceed()
        println("Invocation returned")
        return rval
    }
}

请注意对 .这将沿着 interceptor 链。大多数拦截器调用此方法,并且 返回其返回值。但是,与 周围的任何建议一样,a 可以 返回不同的值或引发异常,而不是调用 proceed 方法。 但是,您不想在没有充分理由的情况下这样做。proceed()MethodInvocationMethodInterceptorspring-doc.cn

MethodInterceptor实现提供与其他符合 AOP Alliance 的 AOP 的互操作性 实现。本节其余部分讨论的其他建议类型 实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势 在使用最具体的 advice 类型时,如果 您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点 目前无法在框架之间互操作,并且 AOP 联盟不支持 当前定义切入点接口。MethodInterceptor
建议前

更简单的 advice 类型是 before advice。这不需要对象,因为它仅在进入方法之前调用。MethodInvocationspring-doc.cn

before 通知的主要优点是不需要调用该方法,因此,不可能无意中无法继续 拦截器链。proceed()spring-doc.cn

下面的清单显示了该接口:MethodBeforeAdvicespring-doc.cn

Java
public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}
Kotlin
interface MethodBeforeAdvice : BeforeAdvice {

    fun before(m: Method, args: Array<Any>, target: Any)
}

(Spring 的 API 设计将允许 field 之前,尽管通常的对象适用于字段拦截,并且它是 Spring 不太可能实现它。spring-doc.cn

请注意,返回类型为 .Before advice 可以在 join 之前插入自定义行为 point 运行,但无法更改返回值。如果 before 通知抛出 exception,它会停止拦截器链的进一步执行。异常 沿拦截器链向上传播。如果未选中或在 调用的方法,它将直接传递给客户端。否则,它是 包装在 AOP 代理的未选中异常中。voidspring-doc.cn

以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:spring-doc.cn

Java
public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
Kotlin
class CountingBeforeAdvice : MethodBeforeAdvice {

    var count: Int = 0

    override fun before(m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}
Before 建议可以与任何切入点一起使用。
抛出建议

如果 join point 抛出 异常。Spring 提供 typed throws 建议。请注意,这意味着该接口不包含任何方法。它是一个 标记接口,用于标识给定对象实现一个或多个类型化 throw 建议方法。这些应采用以下形式:org.springframework.aop.ThrowsAdvicespring-doc.cn

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于通知方法是否对该方法感兴趣,以及 参数。接下来的两个清单显示了作为 throws advice 示例的类。spring-doc.cn

如果抛出 a (包括来自子类),则调用以下建议:RemoteExceptionspring-doc.cn

Java
public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}
Kotlin
class RemoteThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }
}

与前面的 advice 中,下一个示例声明了四个参数,以便它可以访问被调用的方法 Method arguments 和 target 对象。如果抛出 a,则调用以下建议:ServletExceptionspring-doc.cn

Java
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
Kotlin
class ServletThrowsAdviceWithArguments : ThrowsAdvice {

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}

最后一个示例说明了如何在单个类中使用这两种方法 ,它同时处理 和 。任意数量的投掷建议 方法可以组合到一个类中。下面的清单显示了最后一个示例:RemoteExceptionServletExceptionspring-doc.cn

Java
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
Kotlin
class CombinedThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}
如果 throws-advice 方法本身引发异常,它会覆盖 原始异常 (即,它更改引发给用户的异常) 。覆盖 exception 通常是 RuntimeException,它与任何方法都兼容 签名。但是,如果 throws-advice 方法引发 checked 异常,则它必须 匹配 Target 方法的声明的异常,因此,在某种程度上是 与特定的 Target 方法签名耦合。不要抛出未声明的 checked 与 Target 方法的签名不兼容的 exception!
投掷建议可用于任何切入点。
退货后通知

Spring 中的 after returning 通知必须实现该接口,下面的清单显示了该接口:org.springframework.aop.AfterReturningAdvicespring-doc.cn

Java
public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}
Kotlin
interface AfterReturningAdvice : Advice {

    fun afterReturning(returnValue: Any, m: Method, args: Array<Any>, target: Any)
}

返回后通知可以访问返回值(它无法修改), 调用的方法、方法的参数和 Target。spring-doc.cn

以下返回 advice 后,将计算所有具有 not throwown 异常:spring-doc.cn

Java
public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
Kotlin
class CountingAfterReturningAdvice : AfterReturningAdvice {

    var count: Int = 0
        private set

    override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}

此建议不会更改执行路径。如果它引发异常,则为 抛出拦截器链而不是返回值。spring-doc.cn

返回后,建议可以与任何切入点一起使用。
介绍建议

Spring 将 introduction advice 视为一种特殊的拦截 advice。spring-doc.cn

引言需要一个 an 和一个 that 实现以下接口:IntroductionAdvisorIntroductionInterceptorspring-doc.cn

Java
public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
Kotlin
interface IntroductionInterceptor : MethodInterceptor {

    fun implementsInterface(intf: Class<*>): Boolean
}

从 AOP Alliance 接口继承的方法必须 实施 Introduction。也就是说,如果调用的方法位于引入的 interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它 无法调用 。invoke()MethodInterceptorproceed()spring-doc.cn

Introduction advice 不能与任何切入点一起使用,因为它仅适用于类 而不是 method, level.您只能将 introduction advice 与 一起使用,它具有以下方法:IntroductionAdvisorspring-doc.cn

Java
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
Kotlin
interface IntroductionAdvisor : Advisor, IntroductionInfo {

    val classFilter: ClassFilter

    @Throws(IllegalArgumentException::class)
    fun validateInterfaces()
}

interface IntroductionInfo {

    val interfaces: Array<Class<*>>
}

没有,因此,也没有与引言相关 建议。只有类过滤是合乎逻辑的。MethodMatcherPointcutspring-doc.cn

该方法返回此 advisor 引入的接口。getInterfaces()spring-doc.cn

该方法在内部用于查看 引入的接口可以通过配置的 来实现。validateInterfaces()IntroductionInterceptorspring-doc.cn

考虑 Spring 测试套件中的一个示例,假设我们想 将以下接口引入一个或多个对象:spring-doc.cn

Java
public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
Kotlin
interface Lockable {
    fun lock()
    fun unlock()
    fun locked(): Boolean
}

这说明了一个 mixin。我们希望能够将 Advice 对象转换为 , 无论它们的类型和调用 lock 和 unlock 方法。如果我们调用该方法,则 希望所有 setter 方法都抛出一个 .因此,我们可以添加一个 aspect 提供了使对象不可变的能力,而无需他们知道它: AOP 的一个很好的例子。Lockablelock()LockedExceptionspring-doc.cn

首先,我们需要一个能完成繁重工作的人。在这个 case 中,我们扩展了 convenience 类。我们可以直接实现,但在大多数情况下,使用是最好的。IntroductionInterceptororg.springframework.aop.support.DelegatingIntroductionInterceptorIntroductionInterceptorDelegatingIntroductionInterceptorspring-doc.cn

旨在将 introduction 委托给 实际实现引入的接口,隐藏使用拦截 执行此操作。您可以使用 constructor 参数将委托设置为任何对象。这 default delegate(使用无参数构造函数时)为 .因此,在下一个示例中, 委托是 的子类。 给定一个委托(默认情况下,它本身),一个实例 查找由 delegate (except than )实现的所有接口,并支持针对其中任何一个接口的介绍。 子类(如 can 调用该方法)来禁止显示不应公开的接口。然而,无论多少 接口 an 准备支持,使用的控件实际公开了哪些接口。一 introduced interface 隐藏了目标对同一接口的任何实现。DelegatingIntroductionInterceptorthisLockMixinDelegatingIntroductionInterceptorDelegatingIntroductionInterceptorIntroductionInterceptorLockMixinsuppressInterface(Class intf)IntroductionInterceptorIntroductionAdvisorspring-doc.cn

因此,扩展并实现自身。超类会自动拾取可以支持的 introduction,所以我们不需要指定。我们可以引入任意数量的 接口。LockMixinDelegatingIntroductionInterceptorLockableLockablespring-doc.cn

请注意 instance 变量的使用。这有效地添加了额外的状态 附加到目标对象中持有的 ID。lockedspring-doc.cn

以下示例显示了示例类:LockMixinspring-doc.cn

Java
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}
Kotlin
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {

    private var locked: Boolean = false

    fun lock() {
        this.locked = true
    }

    fun unlock() {
        this.locked = false
    }

    fun locked(): Boolean {
        return this.locked
    }

    override fun invoke(invocation: MethodInvocation): Any? {
        if (locked() && invocation.method.name.indexOf("set") == 0) {
            throw LockedException()
        }
        return super.invoke(invocation)
    }

}

通常,您不需要重写该方法。实现(调用 if 该方法,否则继续向连接点前进)通常 够。在当前情况下,我们需要添加一个检查:不能调用 setter 方法 如果处于锁定模式。invoke()DelegatingIntroductionInterceptordelegatespring-doc.cn

所需的 introduction 只需要持有一个不同的实例并指定引入的接口(在本例中,only )。更复杂的示例可能会引用 introduction interceptor (将被定义为原型)。在这种情况下,没有 与 A 相关的配置,因此我们使用 来创建它。 以下示例显示了我们的类:LockMixinLockableLockMixinnewLockMixinAdvisorspring-doc.cn

Java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
Kotlin
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)

我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它 不能在没有 .) 的情况下使用 an与通常的 introduction 一样,顾问程序必须是每个实例的, 因为它是有状态的。对于每个建议的对象,我们需要一个不同的 , 实例 ,因此 。顾问程序包含被建议对象的 州。IntroductionInterceptorIntroductionAdvisorLockMixinAdvisorLockMixinspring-doc.cn

我们可以使用以下方法以编程方式应用此 advisor 或 (推荐的方法)在 XML 配置中,就像任何其他 advisor 一样。所有代理创建 下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍 和有状态的 mixin 中。Advised.addAdvisor()spring-doc.cn

6.3. Spring 中的 Advisor API

在 Spring 中,Advisor 是一个只包含单个关联 advice 对象的方面 替换为切入点表达式。spring-doc.cn

除了介绍的特殊情况外,任何顾问都可以与任何建议一起使用。 是最常用的 advisor 类。它可以与 、 或 一起使用。org.springframework.aop.support.DefaultPointcutAdvisorMethodInterceptorBeforeAdviceThrowsAdvicespring-doc.cn

可以在同一个 AOP 代理中混合使用 Spring 中的 advisor 和 advice 类型。为 例如,您可以在 Advice 中使用 Interception Around、Throws Advice 和 Before Advice 一个代理配置。Spring 会自动创建必要的拦截器 链。spring-doc.cn

6.4. 使用 创建 AOP 代理ProxyFactoryBean

如果你将 Spring IoC 容器(或 )用于 business 对象(您应该是!),您希望使用 Spring 的 AOP 实现之一。(请记住,工厂 Bean 引入了一个间接层,让 它创建不同类型的对象。ApplicationContextBeanFactoryFactoryBeanspring-doc.cn

Spring AOP 支持还在幕后使用工厂 bean。

在 Spring 中创建 AOP 代理的基本方法是使用 .这提供了对 切入点、任何适用的建议以及它们的顺序。然而,还有更简单的 如果您不需要此类控制,则首选选项。org.springframework.aop.framework.ProxyFactoryBeanspring-doc.cn

6.4.1. 基础

与其他 Spring 实现一样,这 引入了一个 间接级别。如果定义一个命名的 , 对象 引用看不到实例本身,而是一个对象 由 .这 方法创建包装目标对象的 AOP 代理。ProxyFactoryBeanFactoryBeanProxyFactoryBeanfoofooProxyFactoryBeangetObject()ProxyFactoryBeanspring-doc.cn

使用 IoC 感知的最重要好处之一 类来创建 AOP 代理,则 advice 和 pointcuts 也可以是 由 IoC 管理。这是一个强大的功能,支持某些难以 实现。例如,通知本身可以引用 application 对象(除了目标,它应该在任何 AOP 中都可用 框架),受益于 Dependency Injection 提供的所有可插拔性。ProxyFactoryBeanspring-doc.cn

6.4.2. JavaBean 属性

与 Spring 提供的大多数实现一样,该类本身就是一个 JavaBean。其属性用于:FactoryBeanProxyFactoryBeanspring-doc.cn

一些关键属性继承自(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括 以下内容:org.springframework.aop.framework.ProxyConfigspring-doc.cn

  • proxyTargetClass:如果要代理目标类,而不是 Target 类的接口。如果此属性值设置为 ,则 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。truetruespring-doc.cn

  • optimize:控制是否对代理应用主动优化 通过 CGLIB 创建。除非您完全 了解相关的 AOP 代理如何处理优化。这是当前使用的 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。spring-doc.cn

  • frozen:如果代理配置为 ,则对配置的更改为 不再允许。这在轻微优化和那些情况下都很有用 当您不希望调用方在创建代理后能够 (通过界面) 操作代理时。此属性的默认值为 ,因此允许进行更改(例如添加其他建议)。frozenAdvisedfalsespring-doc.cn

  • exposeProxy:确定是否应在 中公开当前代理,以便目标可以访问它。如果目标需要获取 代理和属性设置为 ,则目标可以使用该方法。ThreadLocalexposeProxytrueAopContext.currentProxy()spring-doc.cn

特定于 to 的其他属性包括:ProxyFactoryBeanspring-doc.cn

  • proxyInterfaces:接口名称数组。如果未提供,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。Stringspring-doc.cn

  • interceptorNames:、interceptor 或其他通知名称的数组 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。StringAdvisorspring-doc.cn

    这些名称是当前工厂中的 bean 名称,包括来自祖先的 bean 名称 工厂。你不能在这里提到 bean 引用,因为这样做会导致忽略 advice 的 singleton 设置。ProxyFactoryBeanspring-doc.cn

    您可以在侦听器名称后附加星号 ()。这样做会导致 应用程序名称以星号前部分开头的所有 advisor bean 以应用。您可以在使用 “Global” Advisors 中找到使用此功能的示例。*spring-doc.cn

  • singleton:工厂是否应该返回单个对象,无论如何 通常会调用 Method。多种实施方式 这样的方法。默认值为 .如果你想使用有状态通知 - 对于 例如,对于有状态的混合 - 使用 prototype 建议以及单例值 。getObject()FactoryBeantruefalsespring-doc.cn

6.4.3. 基于 JDK 和 CGLIB 的代理

本节是有关如何为特定目标选择创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档 object (要代理的)。ProxyFactoryBeanspring-doc.cn

创建基于 JDK 或 CGLIB 的行为 代理在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。现在 在自动检测接口方面表现出与类的语义相似的语义。ProxyFactoryBeanProxyFactoryBeanTransactionProxyFactoryBean

如果要代理的目标对象的类(以下简称为 目标类)不实现任何接口,则基于 CGLIB 的代理是 创建。这是最简单的方案,因为 JDK 代理是基于接口的,没有 interfaces 意味着 JDK 代理甚至是不可能的。您可以插入目标 bean 并通过设置属性来指定拦截器列表。请注意, 即使 的属性已设置为 ,也会创建基于 CGLIB 的代理。(这样做没有意义,而且是最好的 从 Bean 定义中删除,因为它充其量是多余的,最坏的情况是 令人困惑。interceptorNamesproxyTargetClassProxyFactoryBeanfalsespring-doc.cn

如果目标类实现一个(或多个)接口,则 created 取决于 的配置。ProxyFactoryBeanspring-doc.cn

如果 的属性已设置为 , 将创建基于 CGLIB 的代理。这是有道理的,并且符合 最小惊喜原则。即使 的属性已设置为一个或多个完全限定的接口名称,事实 该属性设置为导致基于 CGLIB 代理生效。proxyTargetClassProxyFactoryBeantrueproxyInterfacesProxyFactoryBeanproxyTargetClasstruespring-doc.cn

如果 的属性已设置为 1 或多个 完全限定的接口名称,则会创建一个基于 JDK 的代理。创建的 proxy 实现 property 中指定的所有接口。如果目标类恰好实现了比 那些在属性中指定的,那都是好的,但是那些 返回的代理不会实现其他接口。proxyInterfacesProxyFactoryBeanproxyInterfacesproxyInterfacesspring-doc.cn

如果 的属性尚未设置,但 Target 类确实实现了一个(或多个)接口,它会自动检测 Target 类实际上确实实现了 实现至少一个接口,并创建基于 JDK 的代理。接口 实际上是 Target 类 实现。实际上,这与提供每个 目标类实现到属性的接口。然而 它明显减少了工作量,并且不易出现印刷错误。proxyInterfacesProxyFactoryBeanProxyFactoryBeanproxyInterfacesspring-doc.cn

6.4.4. 代理接口

考虑一个 in action 的简单示例。此示例涉及:ProxyFactoryBeanspring-doc.cn

  • 代理的目标 Bean。这是 示例。personTargetspring-doc.cn

  • An 和 an 用于提供建议。AdvisorInterceptorspring-doc.cn

  • 用于指定目标对象(Bean)的 AOP 代理 Bean 定义, 代理的接口和应用的建议。personTargetspring-doc.cn

下面的清单显示了该示例:spring-doc.cn

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

请注意,该属性采用一个列表 ,其中包含 当前工厂中的 interceptor 或 advisor。您可以使用 advisors、interceptor、before、after returning,并抛出 Advice 对象。顾问的排序很重要。interceptorNamesStringspring-doc.cn

您可能想知道为什么该列表不包含 bean 引用。这样做的原因是 如果 的 singleton 属性设置为 ,则它必须能够 返回独立的代理实例。如果任何 advisor 本身就是一个原型,则 需要返回独立实例,因此需要能够获取 工厂中的原型实例。持有参考是不够的。ProxyFactoryBeanfalse

前面显示的 bean 定义可以代替实现,因为 遵循:personPersonspring-doc.cn

Java
Person person = (Person) factory.getBean("person");
Kotlin
val person = factory.getBean("person") as Person;

同一 IoC 上下文中的其他 bean 可以表示对它的强类型依赖关系,如 替换为普通的 Java 对象。以下示例显示了如何执行此操作:spring-doc.cn

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

此示例中的类公开了 .就 值得一提的是,AOP 代理可以透明地代替“真实”人使用 实现。但是,它的类将是动态代理类。这是可能的 将其强制转换为接口(稍后讨论)。PersonUserPersonAdvisedspring-doc.cn

您可以通过使用匿名 内 Bean 的 Bean 中。只是定义不同。这 包含建议只是为了完整性。以下示例演示如何使用 匿名内部 Bean:ProxyFactoryBeanspring-doc.cn

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

使用匿名内部 bean 的优点是只有一个 type 为 的对象。如果我们想要,这很有用 防止应用程序上下文的用户获取对 un-advised 的引用 对象或需要避免 Spring IoC 自动装配的任何歧义。还有, 可以说,一个优势在于该定义是自包含的。 但是,有时能够从 Factory 实际上可能是一个优势(例如,在某些测试场景中)。PersonProxyFactoryBeanspring-doc.cn

6.4.5. 代理类

如果您需要代理一个类,而不是一个或多个接口,该怎么办?spring-doc.cn

想象一下,在我们前面的示例中,没有接口。我们需要提供建议 一个名为 的类,它没有实现任何业务接口。在这种情况下,您 可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将前面显示的 上的属性设置为 。虽然最好 program 添加到接口而不是类,能够通知没有 在处理遗留代码时,实现接口可能很有用。(一般来说,Spring 不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制 特定方法。PersonPersonproxyTargetClassProxyFactoryBeantruespring-doc.cn

如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你有 接口。spring-doc.cn

CGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 配置此生成的子类以将方法调用委托给原始目标。这 subclass 用于实现 Decorator 模式,并在 advice 中编织。spring-doc.cn

CGLIB 代理通常应该对用户透明。但是,存在一些问题 考虑:spring-doc.cn

  • Final不能建议方法,因为它们不能被覆盖。spring-doc.cn

  • 无需将 CGLIB 添加到您的 Classpath 中。从 Spring 3.2 开始,CGLIB 被重新打包 并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 在“外部 the box“,JDK 动态代理也是如此。spring-doc.cn

CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应该是一个决定性的考虑因素。spring-doc.cn

6.4.6. 使用 “global” Advisor

通过在拦截器名称后附加星号,所有 bean 名称匹配的 advisor 星号前面的部分将添加到 advisor 链中。这可以派上用场 如果您需要添加一组标准的 “global” 顾问。以下示例定义了 两个 Global Advisors:spring-doc.cn

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

6.5. 简明的代理定义

尤其是在定义交易代理时,您最终可能会得到许多类似的代理 定义。父 Bean 定义和子 Bean 定义以及内部 Bean 的使用 定义可以产生更简洁、更简洁的代理定义。spring-doc.cn

首先,我们为代理创建一个父级、模板、bean 定义,如下所示:spring-doc.cn

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

这本身永远不会实例化,因此它实际上可能是不完整的。然后,每个代理 需要创建的是一个子 Bean 定义,它包装了 proxy 作为内部 Bean 定义,因为无论如何,目标永远不会单独使用。 以下示例显示了这样的子 Bean:spring-doc.cn

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

您可以覆盖父模板中的属性。在以下示例中, 我们覆盖交易传播设置:spring-doc.cn

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

请注意,在父 Bean 示例中,我们显式地将父 Bean 定义标记为 如前所述,通过将属性设置为 ,因此它可能实际上永远不会 实例。默认情况下,应用程序上下文(但不是简单的 bean 工厂)中, 预先实例化所有单例。因此,它很重要(至少对于 singleton bean) 也就是说,如果你有一个(父)Bean 定义,你只打算用作模板, 并且此定义指定了一个类,则必须确保将属性设置为 。否则,应用程序上下文实际上会尝试 pre-instantiate 它。abstracttrueabstracttruespring-doc.cn

6.6. 使用ProxyFactory

使用 Spring 以编程方式创建 AOP 代理很容易。这样,您就可以使用 不依赖于 Spring IoC 的 Spring AOP。spring-doc.cn

目标对象实现的接口包括 自动代理。下面的清单显示了为目标对象创建代理,其中有一个 Interceptor 和一个 Advisor:spring-doc.cn

Java
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
Kotlin
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
factory.addAdvisor(myAdvisor)
val tb = factory.proxy as MyBusinessInterface

第一步是构造 类型的对象。您可以使用目标 object,或指定要在备用 构造 函数。org.springframework.aop.framework.ProxyFactoryspring-doc.cn

您可以添加 advice(拦截器作为一种专门的 advice),advisor 或两者兼而有之 并在 .如果添加 ,则可以使代理实现额外的 接口。ProxyFactoryIntroductionInterceptionAroundAdvisorspring-doc.cn

还有方便的方法 on (继承自 ) 允许您添加其他 Advice 类型,例如 before 和 throws advice。 是 和 的超类。ProxyFactoryAdvisedSupportAdvisedSupportProxyFactoryProxyFactoryBeanspring-doc.cn

将 AOP 代理创建与 IoC 框架集成是大多数 应用。我们建议您使用 AOP 从 Java 代码外部化配置。 一般来说,你应该这样做。

6.7. 操作 Suggested Objects

无论您如何创建 AOP 代理,您都可以使用该界面来操作它们。任何 AOP 代理都可以强制转换为此 接口,无论它实现哪些其他接口。此接口包括 方法如下:org.springframework.aop.framework.Advisedspring-doc.cn

Java
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();
Kotlin
fun getAdvisors(): Array<Advisor>

@Throws(AopConfigException::class)
fun addAdvice(advice: Advice)

@Throws(AopConfigException::class)
fun addAdvice(pos: Int, advice: Advice)

@Throws(AopConfigException::class)
fun addAdvisor(advisor: Advisor)

@Throws(AopConfigException::class)
fun addAdvisor(pos: Int, advisor: Advisor)

fun indexOf(advisor: Advisor): Int

@Throws(AopConfigException::class)
fun removeAdvisor(advisor: Advisor): Boolean

@Throws(AopConfigException::class)
fun removeAdvisor(index: Int)

@Throws(AopConfigException::class)
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean

fun isFrozen(): Boolean

该方法为每个 advisor、interceptor 或 其他已添加到 Factory 的通知类型。如果您添加了 , 则 在此索引返回的 advisor 是您添加的对象。如果您添加了 interceptor 或其他 advice 类型,Spring 将其包装在 advisor 中,并带有 始终返回 .因此,如果您添加了 ,则 advisor 返回 A 返回 YOUR 和 POINTCUT 匹配所有类和方法。getAdvisors()AdvisorAdvisortrueMethodInterceptorDefaultPointcutAdvisorMethodInterceptorspring-doc.cn

这些方法可用于添加任何 .通常,顾问持有 pointcut 和 advice 是泛型 ,您可以将其与 任何建议或切入点(但不包括介绍)。addAdvisor()AdvisorDefaultPointcutAdvisorspring-doc.cn

默认情况下,可以添加或删除 advisor 或拦截器,即使一个 proxy 已创建。唯一的限制是无法添加或删除 Introduction Advisor,因为工厂的现有代理不显示界面 改变。(您可以从工厂获取新的 proxy 以避免此问题。spring-doc.cn

以下示例显示了将 AOP 代理强制转换为接口并检查和 操纵其建议:Advisedspring-doc.cn

Java
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
Kotlin
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(DebugInterceptor())

// Add selective advice using a pointcut
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
修改 business 对象,尽管毫无疑问存在合法的用例。 但是,它在开发中可能非常有用(例如,在测试中)。我们有时 发现能够以拦截器或其他 建议,进入我们想要测试的方法调用。(例如,建议可以 进入为该方法创建的事务中,也许是为了运行 SQL 来检查它 在将事务标记为回滚之前,已正确更新了数据库。

根据您创建代理的方式,您通常可以设置一个标志。在那个 case 时,该方法会返回 ,并且任何修改 建议通过添加或删除会导致 。能力 冻结被通知对象的 state 在某些情况下很有用(例如,冻结 防止调用代码删除安全拦截器)。frozenAdvisedisFrozen()trueAopConfigExceptionspring-doc.cn

6.8. 使用 “auto-proxy” 工具

到目前为止,我们已经考虑了通过使用 或 类似的工厂豆。ProxyFactoryBeanspring-doc.cn

Spring 还允许我们使用 “auto-proxy” bean 定义,它可以自动 代理选定的 Bean 定义。这是建立在 Spring 的“bean post processor”之上的 基础结构,它允许在容器加载时修改任何 bean 定义。spring-doc.cn

在此模型中,您可以在 XML Bean 定义文件中设置一些特殊的 Bean 定义 配置自动代理基础架构。这样,您就可以声明目标 符合自动代理条件。您无需使用 .ProxyFactoryBeanspring-doc.cn

有两种方法可以执行此操作:spring-doc.cn

  • 通过使用引用当前上下文中特定 bean 的自动代理创建器。spring-doc.cn

  • 值得单独考虑的自动代理创建的特殊情况: 由源级元数据属性驱动的自动代理创建。spring-doc.cn

6.8.1. 自动代理 Bean 定义

本节介绍包提供的自动代理创建者。org.springframework.aop.framework.autoproxyspring-doc.cn

BeanNameAutoProxyCreator

该类是自动创建 名称与 Literals 值或通配符匹配的 bean 的 AOP 代理。以下内容 示例展示了如何创建一个 bean:BeanNameAutoProxyCreatorBeanPostProcessorBeanNameAutoProxyCreatorspring-doc.cn

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

与 一样,有一个属性而不是一个列表 的拦截器,以允许原型 advisor 的正确行为。命名 “interceptors” 可以是顾问或任何建议类型。ProxyFactoryBeaninterceptorNamesspring-doc.cn

与一般的自动代理一样,使用的重点是 将相同的配置一致地应用于多个对象,并且最小体积为 配置。它是将声明式事务应用于多个 对象。BeanNameAutoProxyCreatorspring-doc.cn

名称匹配的 Bean 定义,例如前面的 和 例如,是具有 Target 类的普通旧 bean 定义。AOP 代理是 由 自动创建的 .同样的建议也适用 添加到所有匹配的 bean 中。请注意,如果使用 advisors(而不是 前面的示例),切入点可能以不同的方式应用于不同的 bean。jdkMyBeanonlyJdkBeanNameAutoProxyCreatorspring-doc.cn

DefaultAdvisorAutoProxyCreator

一个更通用且极其强大的自动代理创建器是 。这会自动将符合条件的顾问程序应用于 当前上下文,而无需在 auto-proxy 中包含特定的 bean 名称 顾问的 bean 定义。它提供了一致的配置和 避免重复为 。DefaultAdvisorAutoProxyCreatorBeanNameAutoProxyCreatorspring-doc.cn

使用此机制涉及:spring-doc.cn

  • 指定 Bean 定义。DefaultAdvisorAutoProxyCreatorspring-doc.cn

  • 在相同或相关上下文中指定任意数量的 advisor。请注意,这些 必须是顾问,而不是拦截器或其他建议。这是必要的, 因为必须有一个切入点进行评估,来检查每个建议的资格 添加到候选 bean 定义中。spring-doc.cn

自动评估包含的切入点 在每个 advisor 中,查看它应该适用于每个业务对象的建议(如果有) (例如示例中的 and)。DefaultAdvisorAutoProxyCreatorbusinessObject1businessObject2spring-doc.cn

这意味着可以自动将任意数量的顾问应用于每个业务 对象。如果任何 advisor 中都没有切入点与业务对象中的任何方法匹配,则 对象未被代理。当为新的业务对象添加 bean 定义时, 如有必要,它们会自动代理。spring-doc.cn

自动代理通常具有使调用者无法或 dependencies 获取 un-advised 对象。调用 this 将返回 AOP 代理,而不是目标业务对象。(“内部 bean“惯用语也提供了这个好处。getBean("businessObject1")ApplicationContextspring-doc.cn

下面的示例创建一个 Bean,另一个 本节讨论的元素:DefaultAdvisorAutoProxyCreatorspring-doc.cn

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

如果您想应用相同的建议,这将非常有用 始终如一地应用于许多业务对象。一旦基础设施定义就位, 您可以添加新的业务对象,而无需包含特定的代理配置。 您还可以轻松加入其他方面(例如,跟踪或 性能监控方面),对配置进行最小更改。DefaultAdvisorAutoProxyCreatorspring-doc.cn

提供对筛选的支持(通过使用命名 约定,以便仅评估某些 advisor,这允许使用多个 配置不同,AdvisorAutoProxyCreators 在同一个工厂中)和排序。 顾问可以实现该接口以确保 如果这是一个问题,请正确排序。用于 前面的示例具有可配置的 order 值。默认设置为 unordered。DefaultAdvisorAutoProxyCreatororg.springframework.core.OrderedTransactionAttributeSourceAdvisorspring-doc.cn

6.9. 使用实现TargetSource

Spring 提供了在接口中表示的 , 的概念。此接口负责 返回实现连接点的 “target object”。每次 AOP 代理处理方法时,都会请求目标实例的实现 调用。TargetSourceorg.springframework.aop.TargetSourceTargetSourcespring-doc.cn

使用 Spring AOP 的开发人员通常不需要直接使用实现,但是 这提供了一种强大的方法来支持池化、热插拔和其他 复杂的目标。例如,池化可以返回不同的目标 instance 的实例,方法是使用池来管理实例。TargetSourceTargetSourcespring-doc.cn

如果未指定 a ,则默认实现用于包装 local 对象。每次调用都会返回相同的目标(如您所料)。TargetSourcespring-doc.cn

本节的其余部分介绍了 Spring 提供的标准目标源以及如何使用它们。spring-doc.cn

使用自定义目标源时,您的目标通常需要是原型 而不是单例 bean 定义。这允许 Spring 创建一个新目标 实例。

6.9.1. 热插拔 Target 源

存在让目标 的 AOP 代理,同时让调用者保留对它的引用。org.springframework.aop.target.HotSwappableTargetSourcespring-doc.cn

更改目标源的目标将立即生效。这是线程安全的。HotSwappableTargetSourcespring-doc.cn

您可以使用 HotSwappableTargetSource 上的方法来更改目标,如下例所示:swap()spring-doc.cn

Java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
Kotlin
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)

以下示例显示了所需的 XML 定义:spring-doc.cn

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>

前面的调用更改了可交换 bean 的目标。持有 引用时,该 bean 不知道更改,但立即开始点击 新目标。swap()spring-doc.cn

虽然这个例子没有添加任何 advice(没有必要在 使用 a ),any 可以与 武断的建议。TargetSourceTargetSourcespring-doc.cn

6.9.2. 池化 Target 源

使用池化目标源提供与无状态会话类似的编程模型 EJB,其中维护了一个相同实例的池,具有方法调用 正在释放池中的对象。spring-doc.cn

Spring 池和 SLSB 池之间的一个关键区别是 Spring 池可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。spring-doc.cn

Spring 提供了对 Commons Pool 2.2 的支持,它提供了一个 相当有效的池实现。您需要 Jar application 的 Classpath 来使用此功能。你也可以子类来支持任何其他 pooling API 的 API 中。commons-poolorg.springframework.aop.target.AbstractPoolingTargetSourcespring-doc.cn

Commons Pool 1.5+ 也受支持,但从 Spring Framework 4.2 开始已弃用。

下面的清单显示了一个示例配置:spring-doc.cn

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>

请注意,目标对象(在前面的示例中为 )必须是 原型。这允许 implementation 创建新实例 的目标以根据需要增加池。请参阅 AbstractPoolingTargetSource 的 javadoc 和您希望使用的具体子类以获取信息 关于其属性。 是最基本的,并且始终保证存在。businessObjectTargetPoolingTargetSourcemaxSizespring-doc.cn

在本例中, 是需要 在相同的 IoC 上下文中定义。但是,您无需指定拦截器来 使用池化。如果只需要 pooling 而不想要其他建议,则根本不要设置该属性。myInterceptorinterceptorNamesspring-doc.cn

你可以配置 Spring 以便能够将任何池化对象强制转换为接口,从而公开信息 通过简介介绍池的配置和当前大小。你 需要定义一个类似于以下内容的 advisor:org.springframework.aop.target.PoolingConfigspring-doc.cn

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

此顾问是通过对类调用便捷方法获得的,因此使用 .这 advisor 的名称 (, here) 必须在 公开 pooled 对象的 API。AbstractPoolingTargetSourceMethodInvokingFactoryBeanpoolConfigAdvisorProxyFactoryBeanspring-doc.cn

演员定义如下:spring-doc.cn

Java
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
Kotlin
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
通常不需要池化无状态服务对象。我们认为不应该 是默认选项,因为大多数无状态对象本质上是线程安全的,并且实例 如果缓存了资源,则池化是有问题的。

使用自动代理可以实现更简单的池化。您可以设置实现 由任何 Auto-Proxy Creator 使用。TargetSourcespring-doc.cn

6.9.3. 原型 Target 源

设置“原型”目标源与设置池类似 。在这个 case,则在每次方法调用时都会创建一个 Target 的新实例。虽然 在现代 JVM 中,创建新对象的成本并不高,将 新对象(满足其 IoC 依赖项)可能更昂贵。因此,您不应该 使用这种方法没有很好的理由。TargetSourcespring-doc.cn

为此,您可以修改前面显示的定义,如下所示 (为清楚起见,我们还更改了名称):poolTargetSourcespring-doc.cn

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一的属性是目标 Bean 的名称。在实现中使用继承来确保命名一致。与池化目标一样 source,则目标 Bean 必须是原型 Bean 定义。TargetSourcespring-doc.cn

6.9.4. 目标源ThreadLocal

ThreadLocal如果需要为每个源创建一个对象,则 target 源非常有用 传入请求(即每个线程)。a 的概念提供了 JDK 范围的 工具以透明方式将资源与线程一起存储。设置 a 与为其他类型的解释几乎相同 的目标源,如下例所示:ThreadLocalThreadLocalTargetSourcespring-doc.cn

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal实例存在严重问题(可能导致内存泄漏),当 在多线程和多类加载器环境中错误地使用它们。你 应该始终考虑将 ThreadLocal 包装在其他类中,并且永远不要直接使用 本身(包装器类除外)。此外,您应该 请始终记住正确设置和取消设置(如果后者仅涉及对 )线程本地资源的调用。取消设置应在 无论如何,因为不取消设置它可能会导致有问题的行为。Spring 的支持为您完成了此操作,并且应始终考虑使用实例,而无需其他适当的处理代码。ThreadLocalThreadLocal.set(null)ThreadLocalThreadLocal

6.10. 定义新的建议类型

Spring AOP 被设计为可扩展的。虽然拦截实现策略 目前在内部使用,则可以在 除了围绕 Interception Advice 之外,before、throws advice 和 返回建议后。spring-doc.cn

该软件包是一个 SPI 软件包,它允许 在不更改核心框架的情况下添加新的自定义通知类型的支持。 自定义类型的唯一约束是它必须实现 marker 接口。org.springframework.aop.framework.adapterAdviceorg.aopalliance.aop.Advicespring-doc.cn

有关更多信息,请参见org.springframework.aop.framework.adapter javadoc。spring-doc.cn

7. 空安全

尽管 Java 不允许使用其类型系统来表示 null 安全,但 Spring 框架 现在在包中提供了以下注解,以便您 声明 API 和字段的可为 null 性:org.springframework.langspring-doc.cn

  • @Nullable:Annotation 表示 Specific parameter、return value 或 field 可以是 。nullspring-doc.cn

  • @NonNull:Annotation 表示特定的 parameter、return value 或 field 不能 (参数 / 返回值不需要 和字段 where 和 apply)。null@NonNullApi@NonNullFieldsspring-doc.cn

  • @NonNullApi:包级别的注释 将 Non-Null 声明为参数和返回值的默认语义。spring-doc.cn

  • @NonNullFields:包中的注释 级别,该级别将非 null 声明为字段的默认语义。spring-doc.cn

Spring 框架本身利用了这些 Comments,但它们也可以在任何 基于 Spring 的 Java 项目,用于声明 null 安全的 API 和可选的 null 安全字段。 尚不支持泛型类型参数、varargs 和数组元素可为 null 性,但 应该在即将发布的发行版中,请参阅 SPR-15942 了解最新信息。可为 Null 性声明应在 Spring Framework 版本,包括次要版本。method 内部使用的类型的可为 null 性 bodies 不在此功能的范围之内。spring-doc.cn

其他常见库(如 Reactor 和 Spring Data)提供了 null 安全的 API,这些 API 使用类似的可为 null 性安排,为 Spring 应用程序开发人员。

7.1. 使用案例

除了为 Spring 框架 API 可空性提供显式声明外, IDE (比如 IDEA 或 Eclipse) 可以使用这些注解来提供有用的 与 Null 安全相关的警告,以便在运行时避免。NullPointerExceptionspring-doc.cn

它们还用于在 Kotlin 项目中使 Spring API 为空安全,因为 Kotlin 本身就是 支持 NULL 安全。更多详情 在 Kotlin 支持文档中提供。spring-doc.cn

7.2. JSR-305 元注解

Spring 注解使用 JSR 305 注解(一种休眠但广泛传播的 JSR)进行元注解。JSR-305 元注解允许工具供应商 一样,以通用方式提供 null 安全支持,而无需 对 Spring 注解的硬编码支持。spring-doc.cn

没有必要也不建议将 JSR-305 依赖项添加到项目类路径中 利用 Spring 空安全 API。只有使用 null-safety 注解应使用 Gradle 配置或 Maven 范围添加,以避免编译警告。com.google.code.findbugs:jsr305:3.0.2compileOnlyprovidedspring-doc.cn

8. 数据缓冲区和编解码器

Java NIO 提供了许多库在顶部构建了自己的字节缓冲区 API, 特别是对于重用缓冲区和/或使用直接缓冲区的网络操作 有利于性能。例如,Netty 具有层次结构,Undertow 使用 XNIO,Jetty 使用池化字节缓冲区和要释放的回调,依此类推。 该模块提供了一组抽象来处理各种字节缓冲区 API 如下:ByteBufferByteBufspring-corespring-doc.cn

8.1.DataBufferFactory

DataBufferFactory用于通过以下两种方式之一创建数据缓冲区:spring-doc.cn

  1. 分配新的数据缓冲区,可以选择预先指定容量(如果已知),即 即使 的实现可以按需增长和收缩,也更高效。DataBufferspring-doc.cn

  2. 包装现有的 或 ,它用 一个实现,并且不涉及分配。byte[]java.nio.ByteBufferDataBufferspring-doc.cn

请注意,WebFlux 应用程序不会直接创建一个 通过客户端上的 或 访问它。 工厂的类型取决于底层的客户端或服务器,例如 对于 Reactor Netty,对于其他人。DataBufferFactoryServerHttpResponseClientHttpRequestNettyDataBufferFactoryDefaultDataBufferFactoryspring-doc.cn

8.2.DataBuffer

该界面提供与 but also 类似的操作 带来一些额外的好处,其中一些是受到 Netty 的启发。 以下是部分好处列表:DataBufferjava.nio.ByteBufferByteBufspring-doc.cn

8.3.PooledDataBuffer

ByteBuffer 的 Javadoc 中所述, 字节缓冲区可以是 direct 或 non-direct。直接缓冲区可能位于 Java 堆之外 这样就无需复制本机 I/O 操作。这使得直接缓冲区 对于通过 socket 接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。spring-doc.cn

PooledDataBuffer是 的扩展,它有助于进行引用计数 对于字节缓冲池至关重要。它是如何工作的?当 为 allocated 的引用计数为 1。调用 Count,而 调用来递减它。只要计数大于 0,缓冲区 保证不会被释放。当计数减少到 0 时,池化缓冲区可以是 released,这实际上可能意味着缓冲区的预留内存将返回到 内存池。DataBufferPooledDataBufferretain()release()spring-doc.cn

请注意,在大多数情况下,与其直接操作 on 更好 使用其中的便捷方法,仅当 a 是 的实例时,才将 release 或 retain 应用于 。PooledDataBufferDataBufferUtilsDataBufferPooledDataBufferspring-doc.cn

8.4.DataBufferUtils

DataBufferUtils提供了许多 Utility 方法来操作数据缓冲区:spring-doc.cn

  • 将数据缓冲区流加入单个缓冲区中,可能具有零拷贝,例如通过 复合缓冲区,如果底层字节缓冲区 API 支持的话。spring-doc.cn

  • 将 或 NIO 转换为 ,反之亦然 a 变为 或 NIO 。InputStreamChannelFlux<DataBuffer>Publisher<DataBuffer>OutputStreamChannelspring-doc.cn

  • 如果缓冲区是 的实例,则释放或保留 的方法。DataBufferPooledDataBufferspring-doc.cn

  • 跳过或获取字节流,直到达到特定的字节计数。spring-doc.cn

8.5. 编解码器

该软件包提供以下策略接口:org.springframework.core.codecspring-doc.cn

  • Encoder编码为数据缓冲区流。Publisher<T>spring-doc.cn

  • Decoder解码为更高级别对象的流。Publisher<DataBuffer>spring-doc.cn

该模块提供 、 、 、 以及 编码器 和 解码器 实现。该模块添加了 Jackson JSON、 Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器spring-corebyte[]ByteBufferDataBufferResourceStringspring-webspring-doc.cn

8.6. 使用DataBuffer

使用数据缓冲区时,必须特别小心以确保释放缓冲区 因为它们可能是共用的。我们将使用编解码器来说明 这是如何运作的,但概念更普遍地适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。spring-doc.cn

A 是在创建更高级别之前最后读取 input data buffers 的 对象,因此它必须按如下方式释放它们:Decoderspring-doc.cn

  1. 如果 a 只是读取每个输入缓冲区并准备好 立即释放它,它可以通过 来实现。DecoderDataBufferUtils.release(dataBuffer)spring-doc.cn

  2. 如果 a 正在使用 或 运算符,例如 、 、 和 其他在内部预取和缓存数据项,或者使用运算符(如 、 、 )和其他省略项的运算符,则必须添加到 组合链来确保此类缓冲区在被丢弃之前被释放(可能 也是错误或取消信号的结果。DecoderFluxMonoflatMapreducefilterskipdoOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)spring-doc.cn

  3. 如果 a 以任何其他方式保留一个或多个数据缓冲区,则它必须 确保在完全读取时释放它们,或者如果错误或取消发出 在读取和释放缓存的数据缓冲区之前进行。Decoderspring-doc.cn

请注意,提供了一种安全有效的方法来聚合数据 buffer 流传输到单个数据缓冲区中。同样,和 是解码器可以使用的其他安全方法。DataBufferUtils#joinskipUntilByteCounttakeUntilByteCountspring-doc.cn

An 分配其他人必须读取 (和释放) 的数据缓冲区。所以 an 没有太多事情要做。但是,如果 使用数据填充缓冲区时发生序列化错误。例如:EncoderEncoderEncoderspring-doc.cn

Java
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
    // serialize and populate buffer..
    release = false;
}
finally {
    if (release) {
        DataBufferUtils.release(buffer);
    }
}
return buffer;
Kotlin
val buffer = factory.allocateBuffer()
var release = true
try {
    // serialize and populate buffer..
    release = false
} finally {
    if (release) {
        DataBufferUtils.release(buffer)
    }
}
return buffer

的使用者负责释放它接收的数据缓冲区。 在 WebFlux 应用程序中,输出用于写入 HTTP 服务器 响应或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是 负责将代码写入服务器响应或客户端请求。EncoderEncoderspring-doc.cn

请注意,在 Netty 上运行时,有一些调试选项可用于排查缓冲区泄漏问题。spring-doc.cn

9. 附录

9.1. XML 架构

附录的这一部分列出了与核心容器相关的 XML 模式。spring-doc.cn

9.1.1. 模式util

顾名思义,这些标签处理常见的实用程序配置 问题,例如配置集合、引用常量等。 要在架构中使用标记,您需要在顶部具有以下序言 的 Spring XML 配置文件中(代码段中的文本引用了 correct schema,以便命名空间中的标记可供您使用):utilutilutilspring-doc.cn

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

        <!-- bean definitions here -->

</beans>
<util:constant/>

考虑以下 bean 定义:spring-doc.cn

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

前面的配置使用 Spring 实现 (the ) 来设置 bean 上属性的值 设置为 constant 的值。这是 一切都很好,但它很冗长,并且(不必要地)暴露了 Spring 的内部 向最终用户提供管道。FactoryBeanFieldRetrievingFactoryBeanisolationjava.sql.Connection.TRANSACTION_SERIALIZABLEspring-doc.cn

下面基于 XML Schema 的版本更简洁,清楚地表达了 developer's intent (“inject this constant value”),它读起来更好:spring-doc.cn

<bean id="..." class="...">
    <property name="isolation">
        <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
</bean>
从字段值设置 Bean 属性或构造函数参数

FieldRetrievingFactoryBean 是检索或非静态字段值的 Cookie。它通常是 用于检索常量,然后可以使用这些常量来设置 另一个 bean 的 property 值或 constructor 参数。FactoryBeanstaticpublicstaticfinalspring-doc.cn

下面的示例演示如何使用 staticField 属性公开字段:staticspring-doc.cn

<bean id="myField"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

还有一个方便的使用表单,其中字段被指定为 bean name,如下例所示:staticspring-doc.cn

<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

这确实意味着 bean 是什么不再有任何选择(因此任何其他 引用它的 bean 也必须使用这个更长的名称),但这种形式非常 定义简洁,并且非常方便用作内部 bean,因为 没有 为 Bean 引用指定,如下例所示:ididspring-doc.cn

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

您还可以访问另一个 Bean 的非静态(实例)字段,例如 在 FieldRetrievingFactoryBean 类的 API 文档中进行了描述。spring-doc.cn

将枚举值作为 property 或 constructor 参数注入 bean 是 Spring很容易做到。您实际上不需要做任何事情或了解任何事情 Spring 内部结构(甚至关于诸如 the 等类)。 以下示例枚举显示了注入枚举值是多么容易:FieldRetrievingFactoryBeanspring-doc.cn

Java
package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
Kotlin
package javax.persistence

enum class PersistenceContextType {

    TRANSACTION,
    EXTENDED
}

现在考虑以下类型的 setter 和相应的 bean 定义:PersistenceContextTypespring-doc.cn

Java
package example;

public class Client {

    private PersistenceContextType persistenceContextType;

    public void setPersistenceContextType(PersistenceContextType type) {
        this.persistenceContextType = type;
    }
}
Kotlin
package example

class Client {

    lateinit var persistenceContextType: PersistenceContextType
}
<bean class="example.Client">
    <property name="persistenceContextType" value="TRANSACTION"/>
</bean>
<util:property-path/>

请考虑以下示例:spring-doc.cn

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

前面的配置使用 Spring 实现( ) 创建一个名为 的值等于 Bean 的属性。FactoryBeanPropertyPathFactoryBeaninttestBean.ageagetestBeanspring-doc.cn

现在考虑以下示例,该示例添加了一个元素:<util:property-path/>spring-doc.cn

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>

元素的属性值遵循 .在这种情况下,它会选取名为 .该属性的值为 .path<property-path/>beanName.beanPropertyagetestBeanage10spring-doc.cn

用于设置 Bean 属性或构造函数参数<util:property-path/>

PropertyPathFactoryBean是一个 a,用于评估给定 target 对象。可以直接指定目标对象,也可以通过 Bean 名称指定。然后,您可以使用此 value 作为属性值或构造函数 论点。FactoryBeanspring-doc.cn

以下示例显示了按名称对另一个 bean 使用的路径:spring-doc.cn

<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetBeanName" value="person"/>
    <property name="propertyPath" value="spouse.age"/>
</bean>

在下面的示例中,根据内部 Bean 评估路径:spring-doc.cn

<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetObject">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="12"/>
        </bean>
    </property>
    <property name="propertyPath" value="age"/>
</bean>

还有一个快捷方式表单,其中 bean 名称是属性路径。 以下示例显示了快捷方式表单:spring-doc.cn

<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

这种形式确实意味着 bean 的名称没有选择。对它的任何引用 也必须使用相同的 ,即 路径。如果用作内部 bean,则完全不需要引用它,如下例所示:idspring-doc.cn

<bean id="..." class="...">
    <property name="age">
        <bean id="person.age"
                class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>

您可以在实际定义中专门设置结果类型。这不是必需的 对于大多数用例,但有时它可能很有用。请参阅 javadoc 以获取有关 此功能。spring-doc.cn

<util:properties/>

请考虑以下示例:spring-doc.cn

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>

前面的配置使用 Spring 实现 (the ) 来实例化具有 values 从提供的 Resource 位置加载)。FactoryBeanPropertiesFactoryBeanjava.util.Propertiesspring-doc.cn

下面的示例使用元素来制作更简洁的表示形式:util:propertiesspring-doc.cn

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
<util:list/>

请考虑以下示例:spring-doc.cn

<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

前面的配置使用 Spring 实现 (the ) 创建实例并使用采用的值对其进行初始化 从提供的 .FactoryBeanListFactoryBeanjava.util.ListsourceListspring-doc.cn

下面的示例使用元素来制作更简洁的表示形式:<util:list/>spring-doc.cn

<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:list>

您还可以显式控制实例化的确切类型,并且 使用元素上的属性填充。为 例如,如果我们真的需要实例化 A,我们可以使用 以下配置:Listlist-class<util:list/>java.util.LinkedListspring-doc.cn

<util:list id="emails" list-class="java.util.LinkedList">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>d'[email protected]</value>
</util:list>

如果未提供任何属性,则容器将选择一个实现。list-classListspring-doc.cn

<util:map/>

请考虑以下示例:spring-doc.cn

<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
    <property name="sourceMap">
        <map>
            <entry key="pechorin" value="[email protected]"/>
            <entry key="raskolnikov" value="[email protected]"/>
            <entry key="stavrogin" value="[email protected]"/>
            <entry key="porfiry" value="[email protected]"/>
        </map>
    </property>
</bean>

前面的配置使用 Spring implementation (the ) 创建使用键值对初始化的实例 取自提供的 .FactoryBeanMapFactoryBeanjava.util.Map'sourceMap'spring-doc.cn

下面的示例使用元素来制作更简洁的表示形式:<util:map/>spring-doc.cn

<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
    <entry key="pechorin" value="[email protected]"/>
    <entry key="raskolnikov" value="[email protected]"/>
    <entry key="stavrogin" value="[email protected]"/>
    <entry key="porfiry" value="[email protected]"/>
</util:map>

您还可以显式控制实例化的确切类型,并且 使用元素上的属性填充。为 例如,如果我们真的需要实例化 A,我们可以使用 以下配置:Map'map-class'<util:map/>java.util.TreeMapspring-doc.cn

<util:map id="emails" map-class="java.util.TreeMap">
    <entry key="pechorin" value="[email protected]"/>
    <entry key="raskolnikov" value="[email protected]"/>
    <entry key="stavrogin" value="[email protected]"/>
    <entry key="porfiry" value="[email protected]"/>
</util:map>

如果未提供任何属性,则容器将选择一个实现。'map-class'Mapspring-doc.cn

<util:set/>

请考虑以下示例:spring-doc.cn

<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
    <property name="sourceSet">
        <set>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </set>
    </property>
</bean>

前面的配置使用 Spring implementation (the ) 创建实例,该实例使用采用的值进行初始化 从提供的 .FactoryBeanSetFactoryBeanjava.util.SetsourceSetspring-doc.cn

下面的示例使用元素来制作更简洁的表示形式:<util:set/>spring-doc.cn

<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:set>

您还可以显式控制实例化的确切类型,并且 使用元素上的属性填充。为 例如,如果我们真的需要实例化 A,我们可以使用 以下配置:Setset-class<util:set/>java.util.TreeSetspring-doc.cn

<util:set id="emails" set-class="java.util.TreeSet">
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
    <value>[email protected]</value>
</util:set>

如果未提供任何属性,则容器将选择一个实现。set-classSetspring-doc.cn

9.1.2. 模式aop

这些标签处理在 Spring 中配置 AOP 的所有内容,包括 Spring 的 拥有基于代理的 AOP 框架以及 Spring 与 AspectJ AOP 框架的集成。 这些标签在标题为 Aspect Oriented Programming with Spring 的章节中全面介绍。aopspring-doc.cn

为了完整起见,要在 schema 中使用标记,您需要具有 Spring XML 配置文件顶部的以下序言( snippet 引用正确的架构,以便命名空间 可供您使用):aopaopspring-doc.cn

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

</beans>

9.1.3. 模式context

这些标记处理与管道相关的配置 — 也就是说,通常不是对最终用户很重要的 bean,而是重要的 bean Spring 中的许多 “grunt” 工作,例如 .以下内容 snippet 引用正确的架构,以便命名空间中的元素 可供您使用:contextApplicationContextBeanfactoryPostProcessorscontextspring-doc.cn

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

    <!-- bean definitions here -->

</beans>
<property-placeholder/>

此元素激活占位符的替换,这些占位符根据 指定的属性文件(作为 Spring 资源位置)。此元素 是一种为您设置 PropertySourcesPlaceholderConfigurer 的便捷机制。如果您需要对特定设置进行更多控制,则可以自己将其显式定义为 bean。${…​}PropertySourcesPlaceholderConfigurerspring-doc.cn

<annotation-config/>

此元素激活 Spring 基础结构以检测 bean 类中的 Comments:spring-doc.cn

或者,您也可以选择为这些注释显式激活个人。BeanPostProcessorsspring-doc.cn

此元素不会激活 Spring 的 @Transactional Comments 的处理; 为此,可以使用 <tx:annotation-driven/> 元素。同样, Spring 的缓存 Comments 也需要显式启用
<component-scan/>

此元素在 基于注释的容器配置 一节中详细介绍。spring-doc.cn

<load-time-weaver/>

这个元素在 Spring 框架中使用 AspectJ 进行加载时编织的部分中有详细介绍。spring-doc.cn

<spring-configured/>

这个元素在 使用 AspectJ 依赖注入 Spring 域对象的 一节中有详细介绍。spring-doc.cn

<mbean-export/>

此元素在 配置基于注释的 MBean 导出 一节中详细介绍。spring-doc.cn

9.1.4. Bean 模式

最后但并非最不重要的一点是,我们在 schema 中有元素。这些元素 自框架诞生之初就一直在Spring。各种元素的示例 在架构中未显示,因为它们已全面介绍 在依赖项和配置中详细介绍(实际上,在那整中)。beansbeansspring-doc.cn

请注意,您可以向 XML 定义添加零个或多个键值对。 使用这些额外的元数据做什么(如果有的话)完全取决于您自己的自定义 logic 的 API 中 (因此,通常只有在您按照所述编写自己的自定义元素时才有用 在标题为 XML 架构创作的附录中)。<bean/>spring-doc.cn

以下示例显示了周围环境中的元素(请注意,如果没有任何逻辑来解释它,元数据实际上是无用的 就目前而言)。<meta/><bean/>spring-doc.cn

<?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="foo" class="x.y.Foo">
        <meta key="cacheName" value="foo"/> (1)
        <property name="name" value="Rick"/>
    </bean>

</beans>
1 这是示例元素meta

在前面的示例中,您可以假设有一些逻辑使用 bean 定义并设置一些使用提供的元数据的缓存基础结构。spring-doc.cn

9.2. XML 架构创作

从 2.0 版本开始, Spring 具有一种将基于 schema 的扩展添加到 用于定义和配置 bean 的基本 Spring XML 格式。本节涵盖 如何编写自己的自定义 XML bean 定义解析器,以及 将此类解析器集成到 Spring IoC 容器中。spring-doc.cn

为了便于编写使用架构感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果你不是 熟悉 Spring 标准附带的当前 XML 配置扩展 Spring 发行版,您应该首先阅读上一节 XML 模式spring-doc.cn

要创建新的 XML 配置扩展:spring-doc.cn

  1. 创作 XML 架构以描述您的自定义元素。spring-doc.cn

  2. 编写自定义实现代码。NamespaceHandlerspring-doc.cn

  3. 对一个或多个实现进行编码 (这是完成真正工作的地方)。BeanDefinitionParserspring-doc.cn

  4. 在 Spring 中注册您的新工件。spring-doc.cn

对于一个统一的示例,我们创建一个 XML 扩展名(自定义 XML 元素),它允许我们配置该类型的对象(从包中)。当我们完成时, 我们将能够定义 bean 类型的定义,如下所示:SimpleDateFormatjava.textSimpleDateFormatspring-doc.cn

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

(我们包括更详细的 示例将在本附录的后面部分进行。第一个简单示例的目的是引导您 通过创建自定义扩展的基本步骤。spring-doc.cn

9.2.1. 编写 Schema

创建用于 Spring 的 IoC 容器的 XML 配置扩展的开头为 编写 XML 架构来描述扩展。在我们的示例中,我们使用以下架构 要配置对象:SimpleDateFormatspring-doc.cn

<!-- myns.xsd (inside package org/springframework/samples/xml) -->

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.mycompany.example/schema/myns"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType"> (1)
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
1 指示的行包含所有可识别标记的扩展基础 (这意味着它们有一个属性,我们可以将其用作 容器)。我们可以使用这个属性,因为我们导入了 Spring 提供的名称空间。idbeans

前面的 schema 允许我们直接在 XML 应用程序上下文文件,如下所示 示例显示:SimpleDateFormat<myns:dateformat/>spring-doc.cn

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

请注意,在我们创建了基础结构类之后,前面的 XML 代码片段是 与以下 XML 代码片段基本相同:spring-doc.cn

<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

前面两个代码片段中的第二个 在容器中创建一个 bean(由 name of type 标识),并设置了几个属性。dateFormatSimpleDateFormatspring-doc.cn

基于 schema 的创建配置格式的方法允许紧密集成 使用具有架构感知 XML 编辑器的 IDE。通过使用正确编写的架构,您可以 可以使用自动完成让用户在多个配置选项之间进行选择 在枚举中定义。

9.2.2. 编写一个NamespaceHandler

除了 schema 之外,我们还需要一个来解析 Spring 在解析配置文件时遇到的这个特定名称空间。对于此示例,应该负责元素的解析。NamespaceHandlerNamespaceHandlermyns:dateformatspring-doc.cn

该界面有三种方法:NamespaceHandlerspring-doc.cn

  • init():允许初始化 和 由 Spring 之前。NamespaceHandlerspring-doc.cn

  • BeanDefinition parse(Element, ParserContext):当 Spring 遇到 顶级元素(不嵌套在 bean 定义或其他命名空间中)。 此方法本身可以注册 Bean 定义,返回 Bean 定义,或同时返回两者。spring-doc.cn

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext):叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。 一个或多个 bean 定义的装饰(例如)与 Spring 支持的范围一起使用。 我们首先突出显示一个简单的示例,不使用装饰,然后 我们在一个更高级的例子中展示 Decoration。spring-doc.cn

尽管您可以为整个 命名空间(因此提供解析命名空间中每个元素的代码), 通常情况下,Spring XML 配置文件中的每个顶级 XML 元素 生成单个 bean 定义(就像我们的例子中,单个元素导致单个 bean 定义)。Spring 具有 支持此方案的便利类的数量。在下面的示例中,我们 使用类:NamespaceHandler<myns:dateformat/>SimpleDateFormatNamespaceHandlerSupportspring-doc.cn

Java
package org.springframework.samples.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}
Kotlin
package org.springframework.samples.xml

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class MyNamespaceHandler : NamespaceHandlerSupport {

    override fun init() {
        registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
    }
}

您可能会注意到,实际上并没有大量的解析逻辑 在这个类中。事实上,该类具有 代表团。它支持注册任意数量的实例,当需要解析其 Namespace。这种清晰的关注点分离让 编排其命名空间中所有自定义元素的解析,而 委托 to 执行 XML 解析的繁重工作。这 表示每个 API 仅包含解析单个 custom 元素,正如我们在下一步中看到的那样。NamespaceHandlerSupportBeanDefinitionParserNamespaceHandlerBeanDefinitionParsersBeanDefinitionParserspring-doc.cn

9.2.3. 使用BeanDefinitionParser

如果遇到 XML,则使用 A 元素,该元素已映射到特定 Bean 定义解析器 (在本例中)。换句话说, is 负责解析架构中定义的一个不同的顶级 XML 元素。在 解析器中,我们可以访问 XML 元素(因此也可以访问它的子元素),以便 我们可以解析自定义 XML 内容,如以下示例所示:BeanDefinitionParserNamespaceHandlerdateformatBeanDefinitionParserspring-doc.cn

Java
package org.springframework.samples.xml;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1)

    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class; (2)
    }

    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArgValue(pattern);

        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }

}
1 我们使用 Spring 提供的 创建单个 .AbstractSingleBeanDefinitionParserBeanDefinition
2 我们为超类提供 single 代表。AbstractSingleBeanDefinitionParserBeanDefinition
Kotlin
package org.springframework.samples.xml

import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element

import java.text.SimpleDateFormat

class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1)

    override fun getBeanClass(element: Element): Class<*>? { (2)
        return SimpleDateFormat::class.java
    }

    override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
        // this will never be null since the schema explicitly requires that a value be supplied
        val pattern = element.getAttribute("pattern")
        bean.addConstructorArgValue(pattern)

        // this however is an optional property
        val lenient = element.getAttribute("lenient")
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
        }
    }
}
1 我们使用 Spring 提供的 创建单个 .AbstractSingleBeanDefinitionParserBeanDefinition
2 我们为超类提供 single 代表。AbstractSingleBeanDefinitionParserBeanDefinition

在这个简单的情况下,这就是我们需要做的全部。我们的 single 的创建由超类处理,因为 是 Bean 定义的唯一标识符的提取和设置。BeanDefinitionAbstractSingleBeanDefinitionParserspring-doc.cn

9.2.4. 注册处理程序和 Schema

编码完成。剩下要做的就是创建 Spring XML 解析基础设施感知我们的自定义元素。为此,我们在两个特殊用途的属性文件中注册了自定义和自定义 XSD 文件。这些 属性文件都放置在应用程序的目录中,并且 例如,可以与二进制类一起在 JAR 文件中分发。Spring XML 解析基础设施通过使用 这些特殊属性文件,其格式将在接下来的两节中详细介绍。namespaceHandlerMETA-INFspring-doc.cn

写作META-INF/spring.handlers

调用的属性文件包含 XML 架构 URI 到 命名空间处理程序类。对于我们的示例,我们需要编写以下内容:spring.handlersspring-doc.cn

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

(该字符是 Java 属性格式中的有效分隔符,因此 URI 中的字符需要使用反斜杠进行转义。::spring-doc.cn

键值对的第一部分(键)是与您的自定义关联的 URI 命名空间扩展名,并且需要与自定义 XSD 架构中指定的属性值完全匹配。targetNamespacespring-doc.cn

编写 'META-INF/spring.schemas'

调用的属性文件包含 XML 架构位置的映射 (与架构声明一起引用,在使用架构作为一部分的 XML 文件中 )添加到 Classpath 资源中。此文件是必需的 以防止 Spring 绝对必须使用 require 用于检索架构文件的 Internet 访问。如果您在此 properties 文件,Spring 在 Classpath 上搜索模式(在本例中,在包中)。 以下代码片段显示了我们需要为自定义架构添加的行:spring.schemasxsi:schemaLocationEntityResolvermyns.xsdorg.springframework.samples.xmlspring-doc.cn

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(请记住,字符必须转义。:spring-doc.cn

我们鼓励您同时部署 XSD 文件 类路径上的 and 类。NamespaceHandlerBeanDefinitionParserspring-doc.cn

9.2.5. 在 Spring XML 配置中使用自定义扩展

使用您自己实现的自定义扩展与使用 Spring 提供的“自定义”扩展之一。以下内容 example 使用前面步骤中开发的自定义元素 在 Spring XML 配置文件中:<dateformat/>spring-doc.cn

<?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:myns="http://www.mycompany.example/schema/myns"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1)

    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- as an inner bean -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>

</beans>
1 我们的定制 bean。

9.2.6. 更详细的例子

本节提供了一些更详细的自定义 XML 扩展示例。spring-doc.cn

在自定义元素中嵌套自定义元素

本节中介绍的示例显示了如何编写所需的各种工件 满足以下配置的目标:spring-doc.cn

<?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:foo="http://www.foo.example/schema/component"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">

    <foo:component id="bionic-family" name="Bionic-1">
        <foo:component name="Mother-1">
            <foo:component name="Karate-1"/>
            <foo:component name="Sport-1"/>
        </foo:component>
        <foo:component name="Rock-1"/>
    </foo:component>

</beans>

前面的配置将自定义扩展相互嵌套。类 )实际上由 Element 配置的是类(如下一个示例所示)。请注意该类如何不公开 setter 方法。这使得它变得困难(或者说是不可能的) 使用 setter 注入为类配置 Bean 定义。 下面的清单显示了该类:<foo:component/>ComponentComponentcomponentsComponentComponentspring-doc.cn

Java
package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

    private String name;
    private List<Component> components = new ArrayList<Component> ();

    // mmm, there is no setter method for the 'components'
    public void addComponent(Component component) {
        this.components.add(component);
    }

    public List<Component> getComponents() {
        return components;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Kotlin
package com.foo

import java.util.ArrayList

class Component {

    var name: String? = null
    private val components = ArrayList<Component>()

    // mmm, there is no setter method for the 'components'
    fun addComponent(component: Component) {
        this.components.add(component)
    }

    fun getComponents(): List<Component> {
        return components
    }
}

此问题的典型解决方案是创建一个自定义,该自定义将 setter 属性。下面的清单显示了这样的自定义:FactoryBeancomponentsFactoryBeanspring-doc.cn

Java
package com.foo;

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

public class ComponentFactoryBean implements FactoryBean<Component> {

    private Component parent;
    private List<Component> children;

    public void setParent(Component parent) {
        this.parent = parent;
    }

    public void setChildren(List<Component> children) {
        this.children = children;
    }

    public Component getObject() throws Exception {
        if (this.children != null && this.children.size() > 0) {
            for (Component child : children) {
                this.parent.addComponent(child);
            }
        }
        return this.parent;
    }

    public Class<Component> getObjectType() {
        return Component.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component

class ComponentFactoryBean : FactoryBean<Component> {

    private var parent: Component? = null
    private var children: List<Component>? = null

    fun setParent(parent: Component) {
        this.parent = parent
    }

    fun setChildren(children: List<Component>) {
        this.children = children
    }

    override fun getObject(): Component? {
        if (this.children != null && this.children!!.isNotEmpty()) {
            for (child in children!!) {
                this.parent!!.addComponent(child)
            }
        }
        return this.parent
    }

    override fun getObjectType(): Class<Component>? {
        return Component::class.java
    }

    override fun isSingleton(): Boolean {
        return true
    }
}

这工作得很好,但它向最终用户公开了大量的 Spring 管道。我们是什么 要做的是编写一个自定义扩展,隐藏所有这些 Spring 管道。 如果我们坚持前面描述的步骤,我们就会从 通过创建 XSD 架构来定义自定义标签的结构,如下所示 列表显示:spring-doc.cn

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/component"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/component"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:element name="component">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="component"/>
            </xsd:choice>
            <xsd:attribute name="id" type="xsd:ID"/>
            <xsd:attribute name="name" use="required" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

再次按照前面描述的过程, 然后我们创建一个自定义 :NamespaceHandlerspring-doc.cn

Java
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class ComponentNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
    }
}

接下来是自定义 .请记住,我们正在创建 a 描述 .以下内容 清单 显示了我们的自定义实现:BeanDefinitionParserBeanDefinitionComponentFactoryBeanBeanDefinitionParserspring-doc.cn

Java
package com.foo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.List;

public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {

    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        return parseComponentElement(element);
    }

    private static AbstractBeanDefinition parseComponentElement(Element element) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
        factory.addPropertyValue("parent", parseComponent(element));

        List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
        if (childElements != null && childElements.size() > 0) {
            parseChildComponents(childElements, factory);
        }

        return factory.getBeanDefinition();
    }

    private static BeanDefinition parseComponent(Element element) {
        BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
        component.addPropertyValue("name", element.getAttribute("name"));
        return component.getBeanDefinition();
    }

    private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
        ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element

import java.util.List

class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {

    override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
        return parseComponentElement(element)
    }

    private fun parseComponentElement(element: Element): AbstractBeanDefinition {
        val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
        factory.addPropertyValue("parent", parseComponent(element))

        val childElements = DomUtils.getChildElementsByTagName(element, "component")
        if (childElements != null && childElements.size > 0) {
            parseChildComponents(childElements, factory)
        }

        return factory.getBeanDefinition()
    }

    private fun parseComponent(element: Element): BeanDefinition {
        val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
        component.addPropertyValue("name", element.getAttribute("name"))
        return component.beanDefinition
    }

    private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
        val children = ManagedList<BeanDefinition>(childElements.size)
        for (element in childElements) {
            children.add(parseComponentElement(element))
        }
        factory.addPropertyValue("children", children)
    }
}

最后,各种工件需要注册到 Spring XML 基础设施中。 通过修改 AND 文件,如下所示:META-INF/spring.handlersMETA-INF/spring.schemasspring-doc.cn

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
“Normal” 元素上的自定义属性

编写自己的自定义解析器和相关工件并不难。然而 有时这不是正确的做法。考虑一个场景,您需要 将元数据添加到现有的 Bean 定义中。在这种情况下,您当然 不想编写自己的整个自定义扩展。相反,你只是 希望向现有的 Bean 定义元素添加一个附加属性。spring-doc.cn

再举一个例子,假设你为 service 对象(它不知道)访问集群 JCache,并且您希望确保 命名的 JCache 实例在周围的集群中急切地启动。 下面的清单显示了这样的定义:spring-doc.cn

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>

然后,我们可以在解析 attribute 时创建另一个。然后初始化 为我们命名的 JCache。我们还可以修改 existing for ,以便它依赖于这个新的 JCache 初始化。以下列表显示了我们的 :BeanDefinition'jcache:cache-name'BeanDefinitionBeanDefinition'checkingAccountService'BeanDefinitionJCacheInitializerspring-doc.cn

Java
package com.foo;

public class JCacheInitializer {

    private String name;

    public JCacheInitializer(String name) {
        this.name = name;
    }

    public void initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}
Kotlin
package com.foo

class JCacheInitializer(private val name: String) {

    fun initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}

现在我们可以转到自定义扩展。首先,我们需要编写 描述 custom 属性的 XSD 架构,如下所示:spring-doc.cn

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/jcache"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/jcache"
        elementFormDefault="qualified">

    <xsd:attribute name="cache-name" type="xsd:string"/>

</xsd:schema>

接下来,我们需要创建关联的 ,如下所示:NamespaceHandlerspring-doc.cn

Java
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
    }

}
Kotlin
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class JCacheNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
                JCacheInitializingBeanDefinitionDecorator())
    }

}

接下来,我们需要创建解析器。请注意,在本例中,因为我们要解析 一个 XML 属性,我们编写 a 而不是 . 下面的清单显示了我们的实现:BeanDefinitionDecoratorBeanDefinitionParserBeanDefinitionDecoratorspring-doc.cn

Java
package com.foo;

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
            ParserContext ctx) {
        String initializerBeanName = registerJCacheInitializer(source, ctx);
        createDependencyOnJCacheInitializer(holder, initializerBeanName);
        return holder;
    }

    private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
            String initializerBeanName) {
        AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
        String[] dependsOn = definition.getDependsOn();
        if (dependsOn == null) {
            dependsOn = new String[]{initializerBeanName};
        } else {
            List dependencies = new ArrayList(Arrays.asList(dependsOn));
            dependencies.add(initializerBeanName);
            dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
        }
        definition.setDependsOn(dependsOn);
    }

    private String registerJCacheInitializer(Node source, ParserContext ctx) {
        String cacheName = ((Attr) source).getValue();
        String beanName = cacheName + "-initializer";
        if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
            BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
            initializer.addConstructorArg(cacheName);
            ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
        }
        return beanName;
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node

import java.util.ArrayList

class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {

    override fun decorate(source: Node, holder: BeanDefinitionHolder,
                        ctx: ParserContext): BeanDefinitionHolder {
        val initializerBeanName = registerJCacheInitializer(source, ctx)
        createDependencyOnJCacheInitializer(holder, initializerBeanName)
        return holder
    }

    private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
                                                    initializerBeanName: String) {
        val definition = holder.beanDefinition as AbstractBeanDefinition
        var dependsOn = definition.dependsOn
        dependsOn = if (dependsOn == null) {
            arrayOf(initializerBeanName)
        } else {
            val dependencies = ArrayList(listOf(*dependsOn))
            dependencies.add(initializerBeanName)
            dependencies.toTypedArray()
        }
        definition.setDependsOn(*dependsOn)
    }

    private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
        val cacheName = (source as Attr).value
        val beanName = "$cacheName-initializer"
        if (!ctx.registry.containsBeanDefinition(beanName)) {
            val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
            initializer.addConstructorArg(cacheName)
            ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
        }
        return beanName
    }
}

最后,我们需要向 Spring XML 基础架构注册各种工件 通过修改 AND 文件,如下所示:META-INF/spring.handlersMETA-INF/spring.schemasspring-doc.cn

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd