核心

1. IoC 容器

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

1.1. Spring IoC 容器和 Bean 简介

本章介绍了控制反转的 Spring Framework 实现 (IoC)原则。IoC 也称为依赖注入 (DI)。这是一个过程,通过这个过程 对象仅通过以下方式定义其依赖关系(即它们使用的其他对象) 构造函数参数、工厂方法的参数或在 object 实例。容器 然后在创建 bean 时注入这些依赖项。这个过程从根本上说是 bean 本身的逆(因此得名 Inversion of Control) 使用 direct 控制其依赖项的实例化或位置 类或机制(如服务定位器模式)的构造。spring-doc.cadn.net.cn

org.springframework.beansorg.springframework.context套餐是基础 用于 Spring Framework 的 IoC 容器。这BeanFactory接口提供了一种高级配置机制,能够管理任何类型的 对象。ApplicationContextBeanFactory.它补充说:spring-doc.cadn.net.cn

简而言之,BeanFactory提供配置框架和基本功能, 和ApplicationContext添加了更多特定于企业的功能。这ApplicationContextBeanFactory并且专门使用 在本章中对 Spring 的 IoC 容器的描述中。有关使用 这BeanFactory而不是ApplicationContext,请参阅涵盖BeanFactory应用程序接口.spring-doc.cadn.net.cn

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

1.2. 容器概述

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

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

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

下图显示了 Spring 工作原理的高级视图。您的应用程序类 与配置元数据相结合,以便在ApplicationContext是 创建并初始化,则您拥有完全配置和可执行的系统,或者 应用。spring-doc.cadn.net.cn

容器魔法
图 1.Spring IoC 容器

1.2.1. 配置元数据

如上图所示,Spring IoC 容器使用 配置元数据。此配置元数据表示您作为 应用程序开发人员,告诉 Spring 容器实例化、配置和汇编 应用程序中的对象。spring-doc.cadn.net.cn

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

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

有关将其他形式的元数据与 Spring 容器一起使用的信息,请参阅:spring-doc.cadn.net.cn

Spring 配置至少由一个 Bean 组成,通常不止一个 Bean 容器必须管理的定义。基于 XML 的配置元数据配置了这些 beans 作为<bean/>顶级元素<beans/>元素。Java 配置通常使用@Bean-annotated 方法中的@Configuration类。spring-doc.cadn.net.cn

这些 Bean 定义对应于构成应用程序的实际对象。 通常,定义服务层对象、数据访问对象 (DAO)、表示 支柱等对象Action实例、基础设施对象,例如 HibernateSessionFactories, JMSQueues,依此类推。通常,不配置 细粒度域对象,因为通常是负责 的 DAO 和业务逻辑来创建和加载域对象。但是,您可以使用 Spring 与 AspectJ 的集成以配置已在外部创建的对象 IoC 容器的控制。请参阅使用 AspectJ dependency-inject 域对象spring-doc.cadn.net.cn

以下示例显示了基于 XML 的配置元数据的基本结构:spring-doc.cadn.net.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 id属性是标识单个 Bean 定义的字符串。
2 class属性定义 bean 的类型并使用完全限定的 类名。

的值id属性是指协作对象。的 XML 此示例中未显示引用协作对象。有关详细信息,请参阅依赖项。spring-doc.cadn.net.cn

1.2.2. 实例化容器

一个或多个位置路径 供应给ApplicationContext构造函数是资源字符串,允许 容器加载来自各种外部资源的配置元数据,例如 作为本地文件系统,JavaCLASSPATH,依此类推。spring-doc.cadn.net.cn

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

了解了 Spring 的 IoC 容器后,您可能想了解更多有关 Spring 的Resource抽象(如参考资料中所述),它提供了一个方便的 从 URI 语法中定义的位置读取 InputStream 的机制。特别Resource路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。spring-doc.cadn.net.cn

以下示例显示了服务层对象(services.xml)配置文件:spring-doc.cadn.net.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.xml文件:spring-doc.cadn.net.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>

在前面的示例中,服务层由PetStoreServiceImpl类 以及两个类型的数据访问对象JpaAccountDaoJpaItemDao(基于 在 JPA 对象关系映射标准上)。这property name元素引用 javaBean 属性的名称,以及ref元素引用另一个 bean 的名称 定义。这种联系idref元素表示 协作对象。有关配置对象依赖项的详细信息,请参阅依赖项spring-doc.cadn.net.cn

编写基于 XML 的配置元数据

让 Bean 定义跨越多个 XML 文件会很有用。通常,每个人 XML 配置文件表示架构中的逻辑层或模块。spring-doc.cadn.net.cn

您可以使用应用程序上下文构造函数从所有这些构造函数加载 Bean 定义 XML 片段。此构造函数采用多个Resource位置,如上一节所示。或者,使用一个或多个 出现<import/>元素从另一个文件加载 Bean 定义,或者 文件。以下示例显示了如何执行此作:spring-doc.cadn.net.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 定义是从三个文件加载的:services.xml,messageSource.xmlthemeSource.xml.所有位置路径都是 相对于执行导入的定义文件,因此services.xml必须位于 与导入的文件相同的目录或类路径位置,而messageSource.xmlthemeSource.xml必须位于resources位置低于 导入文件的位置。如您所见,前导斜杠被忽略。但是,鉴于 这些路径是相对的,最好不要使用斜杠。这 要导入的文件的内容,包括顶层<beans/>元素,必须 是有效的 XML bean 定义,根据 Spring Schema。spring-doc.cadn.net.cn

可以使用 相对“../“ 路径。这样做会创建对当前文件之外的文件的依赖关系 应用。特别是,不建议将此引用用于classpath:URL(用于 例classpath:../services.xml),其中运行时解析过程选择 “nearest”类路径根目录,然后查看其父目录。类路径 配置更改可能会导致选择不同的、不正确的目录。spring-doc.cadn.net.cn

您始终可以使用完全限定的资源位置而不是相对路径:对于 例file:C:/config/services.xmlclasspath:/config/services.xml.但是,是 意识到您正在将应用程序的配置耦合到特定的绝对值 地点。通常最好为这种绝对值保持间接 locations — 例如,通过针对 JVM 解析的“${...}”占位符 运行时的系统属性。spring-doc.cadn.net.cn

命名空间本身提供了导入指令功能。进一步 除了普通 bean 定义之外的配置功能,还可以在选择中使用 Spring 提供的 XML 命名空间——例如,contextutil命名空间。spring-doc.cadn.net.cn

Groovy Bean 定义 DSL

作为外部化配置元数据的进一步示例,bean 定义还可以 在 Spring 的 Groovy Bean Definition DSL 中表示,如 Grails 框架中所知。 通常,此类配置位于“.groovy”文件中,其结构如 以下示例:spring-doc.cadn.net.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 配置命名空间。它还允许导入 XMLbean 定义文件importBeans命令。spring-doc.cadn.net.cn

1.2.3. 使用容器

ApplicationContext是能够维护的高级工厂的接口不同 bean 及其依赖项的注册表。通过使用T getBean(String name, Class<T> requiredType),您可以检索 Bean 的实例。spring-doc.cadn.net.cn

ApplicationContext允许您读取 Bean 定义并访问它们,如下所示示例所示:spring-doc.cadn.net.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 配置,引导看起来非常相似。它有一个不同的上下文实现类,它是 Groovy 感知的(但也理解 XML bean 定义)。以下示例显示了 Groovy 配置:spring-doc.cadn.net.cn

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

最灵活的变体是GenericApplicationContext与阅读器结合使用 委托 — 例如,使用XmlBeanDefinitionReader对于 XML 文件,如下所示 示例显示:spring-doc.cadn.net.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()

您还可以使用GroovyBeanDefinitionReader对于 Groovy 文件,如下所示 示例显示:spring-doc.cadn.net.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()

您可以在同一个ApplicationContext, 从不同的配置源读取 Bean 定义。spring-doc.cadn.net.cn

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

1.3. Bean 概述

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

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

  • 包限定的类名:通常是 bean 正在定义。spring-doc.cadn.net.cn

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

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

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

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

表 1.bean 定义
属性 解释在...

spring-doc.cadn.net.cn

实例化 Beanspring-doc.cadn.net.cn

名称spring-doc.cadn.net.cn

命名 Beanspring-doc.cadn.net.cn

范围spring-doc.cadn.net.cn

Bean 作用域spring-doc.cadn.net.cn

构造函数参数spring-doc.cadn.net.cn

依赖注入spring-doc.cadn.net.cn

性能spring-doc.cadn.net.cn

依赖注入spring-doc.cadn.net.cn

自动接线模式spring-doc.cadn.net.cn

自动布线协作者spring-doc.cadn.net.cn

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

延迟初始化的 Beanspring-doc.cadn.net.cn

初始化方法spring-doc.cadn.net.cn

初始化回调spring-doc.cadn.net.cn

销毁方法spring-doc.cadn.net.cn

销毁回调spring-doc.cadn.net.cn

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

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

1.3.1. 命名 Bean

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

在基于 XML 的配置元数据中,您可以使用id属性,则name属性,或 两者都指定 bean 标识符。这id属性允许您指定 恰好一个 ID。通常,这些名称是字母数字(“myBean”, 'someService' 等),但它们也可以包含特殊字符。如果你想 为 bean 引入其他别名,您也可以在name属性,用逗号 (,)、分号 () 或空格。作为 历史注释,在 Spring 3.1 之前的版本中,;id属性是 定义为xsd:IDtype,它限制了可能的字符。从 3.1 开始, 它被定义为xsd:string类型。请注意,beanid独特性依然 由容器强制执行,但不再由 XML 解析器强制执行。spring-doc.cadn.net.cn

您无需提供nameid对于一颗豆子。如果您不提供nameid显式地,容器为该 Bean 生成一个唯一的名称。然而 如果您想按名称引用该 bean,请使用ref元素或 服务定位器样式查找,必须提供名称。 不提供名称的动机与使用 inner 有关 豆自动布线合作者spring-doc.cadn.net.cn

Bean 命名约定

约定是将标准 Java 约定用于实例字段名称,当 命名豆子。也就是说,豆子名称以小写字母开头,并且用驼峰式大小写 从那里开始。此类名称的示例包括accountManager,accountService,userDao,loginController,依此类推。spring-doc.cadn.net.cn

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

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

在 Bean 定义本身中,您可以使用 最多一个名称的组合,由id属性和任意数量的其他 名称中的name属性。这些名称可以是同一 Bean 的等效别名 并且在某些情况下很有用,例如让应用程序中的每个组件 使用特定于该组件的 Bean 名称来引用公共依赖关系 本身。spring-doc.cadn.net.cn

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

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

在这种情况下,名为fromName也可能, 使用此别名定义后,称为toName.spring-doc.cadn.net.cn

例如,子系统 A 的配置元数据可以通过 名称subsystemA-dataSource.子系统 B 的配置元数据可以引用 一个 DataSource,名称为subsystemB-dataSource.在编写主应用程序时 使用这两个子系统时,主应用程序通过 名称myApp-dataSource.要使所有三个名称都引用同一个对象,您可以 将以下别名定义添加到配置元数据:spring-doc.cadn.net.cn

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

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

Java配置

如果您使用 Java配置,则@Bean注释可用于提供别名。 看使用@Bean注解了解详情。spring-doc.cadn.net.cn

1.3.2. 实例化 Bean

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

如果使用基于 XML 的配置元数据,请指定对象的类型(或类) 即在class属性的<bean/>元素。这class属性(在内部是一个Class属性BeanDefinitioninstance)通常是强制性的。(有关异常,请参阅使用实例工厂方法实例化Bean 定义继承。 您可以使用Class属性,采用以下两种方式之一:spring-doc.cadn.net.cn

  • 通常,要指定在容器 本身通过反射性地调用其构造函数来直接创建 bean,在某种程度上 等效于 Java 代码,其中new算子。spring-doc.cadn.net.cn

  • 要指定包含static工厂方法,即 调用以创建对象,在不太常见的情况下,容器调用staticfactory 方法来创建 bean。返回的对象类型 从staticfactory 方法可以是同一类或另一个类 完全上课。spring-doc.cadn.net.cn

嵌套类名

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

例如,如果您有一个名为SomeThingcom.examplepackage,以及 这SomeThing类有一个static嵌套类调用OtherThing,它们可以是 用美元符号 () 或点 ($.).因此,class属性 bean 定义将是com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing.spring-doc.cadn.net.cn

使用构造函数实例化

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

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

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

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

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

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

使用静态工厂方法实例化

在定义使用静态工厂方法创建的 bean 时,请使用class属性来指定包含staticfactory 方法和属性 叫factory-method以指定工厂方法本身的名称。你应该是 能够调用此方法(使用可选参数,如下所述)并返回 对象,随后将其视为通过构造函数创建的。 这种 bean 定义的一个用法是调用static工厂。spring-doc.cadn.net.cn

以下 bean 定义指定将通过调用 工厂方法。定义未指定返回对象的类型(类), 而是包含工厂方法的类。在此示例中,createInstance()方法必须是static方法。以下示例演示如何 指定工厂方法:spring-doc.cadn.net.cn

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

以下示例显示了将与前面的 bean 定义一起使用的类:spring-doc.cadn.net.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()
        @JvmStatic
        fun createInstance() = clientService
    }
}

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

使用实例工厂方法实例化

与通过静态factory 方法实例化类似,使用实例工厂方法实例化会调用非静态容器中现有 bean 的方法来创建新 bean。要使用此机制,请将class属性为空,并且在factory-bean属性 指定当前(或父级或祖先)容器中包含要调用以创建对象的实例方法。设置factory 方法本身使用factory-method属性。 以下示例显示了如何配置这样的 bean:spring-doc.cadn.net.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.cadn.net.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.cadn.net.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.cadn.net.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.cadn.net.cn

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

确定特定 Bean 的运行时类型并非易事。中的指定类Bean 元数据定义只是一个初始类引用,可能会将与声明的工厂方法组合在一起,或者作为FactoryBean类,这可能会导致不同的运行时类型,或者在实例级factory 方法(通过指定的factory-beanname 代替)。此外,AOP 代理可能会使用目标 bean 的实际类型(仅其实现的接口)的有限公开。spring-doc.cadn.net.cn

了解特定 Bean 的实际运行时类型的推荐方法是 一个BeanFactory.getType调用指定的 bean 名称。这会考虑上述所有cases 并返回对象类型BeanFactory.getBeancall 是将返回相同的 bean 名称。spring-doc.cadn.net.cn

1.4. 依赖项

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

1.4.1. 依赖注入

依赖注入 (DI) 是一个过程,对象仅通过构造函数参数定义其依赖关系(即它们使用的其他对象),工厂方法的参数,或在对象实例上设置的属性它是从工厂方法构造或返回的。然后,容器在创建 bean 时注入这些依赖关系。这个过程从根本上说是 bean 本身的反向(因此名称,控制反转)控制实例化或通过使用类的直接构造自行定位其依赖项或服务定位器模式。spring-doc.cadn.net.cn

使用 DI 原理,代码更简洁,当对象被提供它们的依赖项。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易进行测试,特别是当依赖项依赖于接口或抽象基类时,这允许在单元测试中使用存根或模拟实现。spring-doc.cadn.net.cn

基于构造函数的依赖注入

基于构造函数的 DI 是通过容器调用具有多个参数来完成的,每个参数代表一个依赖项。调用static工厂方法使用特定的参数来构造 bean 几乎是等价的,并且这个讨论将参数视为构造函数和static工厂方法类似。 这 以下示例显示了一个只能使用构造函数注入依赖项的类 注射:spring-doc.cadn.net.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.cadn.net.cn

构造函数参数解析

构造函数参数解析匹配通过使用参数的类型进行。如果没有Bean 定义的构造函数参数中存在潜在的歧义,则在 Bean 定义中定义构造函数参数的顺序是当 Bean 是被实例化时,这些参数将提供给适当的构造函数。考虑以下类:spring-doc.cadn.net.cn

Java
package x.y;

public class ThingOne {

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

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)

假设ThingTwoThingThree类不通过继承相关,没有 存在潜在的歧义。因此,以下配置工作正常,而您不会 需要在<constructor-arg/>元素。spring-doc.cadn.net.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 与前面的示例)。当使用简单类型时,例如<value>true</value>,Spring 无法确定值的类型,因此无法匹配 按类型在没有帮助的情况下。考虑以下类:spring-doc.cadn.net.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
)
构造函数参数类型匹配

在前面的方案中,容器可以将类型匹配与简单类型一起使用,如果 您可以使用type属性 如以下示例所示:spring-doc.cadn.net.cn

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

您可以使用index属性显式指定构造函数参数的索引, 如以下示例所示:spring-doc.cadn.net.cn

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

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

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

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

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

请记住,要使此功能开箱即用,您的代码必须使用 debug 标志,以便 Spring 可以从构造函数中查找参数名称。 如果您不能或不想使用调试标志编译代码,则可以使用@ConstructorProperties JDK 注释来显式命名构造函数参数。示例类将 然后必须看如下:spring-doc.cadn.net.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 方法来完成的 调用无参数构造函数或无参数构造函数后的 beanstaticfactory 方法设置为 实例化你的 bean。spring-doc.cadn.net.cn

以下示例显示了一个只能使用 pur 塞特注射。此类是传统的 Java。它是一个没有依赖关系的 POJO 在容器特定接口、基类或注释上。spring-doc.cadn.net.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...
}

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

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

由于您可以混合使用基于构造函数和基于 setter 的 DI,因此这是一个很好的经验法则 将构造函数用于强制依赖项和 setter 方法或配置方法 用于可选依赖项。请注意,在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项; 但是,最好使用具有参数编程验证的构造函数注入。spring-doc.cadn.net.cn

Spring 团队通常提倡构造函数注入,因为它可以让你实现 应用程序组件作为不可变对象,并确保所需的依赖关系 不是null.此外,构造函数注入的组件始终返回给客户端 (调用)处于完全初始化状态的代码。顺便说一句,大量的构造函数 arguments 是一种不好的代码气味,暗示该类可能有太多 责任,并应进行重构,以更好地解决适当的关注点分离问题。spring-doc.cadn.net.cn

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

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

依赖关系解析过程

容器执行 bean 依赖关系解析,如下所示:spring-doc.cadn.net.cn

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

  • 对于每个 bean,其依赖关系以属性、构造函数的形式表示 参数,或静态工厂方法的参数(如果您使用它而不是 法式构造函数)。当 bean 是 实际创建。spring-doc.cadn.net.cn

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

  • 作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如int,long,String,boolean,依此类推。spring-doc.cadn.net.cn

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

循环依赖关系

如果主要使用构造函数注入,则可能会创建不可解析的 循环依赖方案。spring-doc.cadn.net.cn

例如:类 A 需要通过构造函数注入来获取类 B 的实例,并且 类 B 需要通过构造函数注入来获取类 A 的实例。如果配置 要相互注入的类 A 和 B 的 bean,Spring IoC 容器 在运行时检测到此循环引用,并抛出一个BeanCurrentlyInCreationException.spring-doc.cadn.net.cn

一种可能的解决方案是编辑一些类的源代码,以配置 setter 而不是构造函数。或者,避免构造函数注入并使用 仅限入孵器注射。也就是说,虽然不推荐,但您可以配置 带有 setter 注入的循环依赖关系。spring-doc.cadn.net.cn

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

您通常可以相信 Spring 会做正确的事情。它检测配置问题, 例如对不存在的 bean 和循环依赖项的引用,在容器 加载时间。Spring 设置属性并尽可能晚地解析依赖关系,当 bean 实际上是被创造的。这意味着已加载的 Spring 容器 如果存在 创建该对象或其依赖项之一时出现问题——例如,bean 抛出 由于属性缺失或无效而导致的异常。这可能会延迟 某些配置问题的可见性是原因ApplicationContext实现者 默认预实例化单例 Bean。以一些前期时间和内存为代价 在实际需要之前创建这些 bean,您会发现配置问题 当ApplicationContext创建,而不是以后创建。您仍然可以覆盖此默认值 行为,以便单例 bean 延迟初始化,而不是急切地初始化 预实例化。spring-doc.cadn.net.cn

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

依赖注入示例

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 Spring XML 配置文件的一部分指定了一些 bean 定义,如下所示:spring-doc.cadn.net.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"/>

以下示例显示了相应的ExampleBean类:spring-doc.cadn.net.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.cadn.net.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"/>

以下示例显示了相应的ExampleBean类:spring-doc.cadn.net.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 定义中指定的构造函数参数用作 的构造函数ExampleBean.spring-doc.cadn.net.cn

现在考虑这个例子的一个变体,其中 Spring 不是使用构造函数,而是 被告知要调用staticfactory 方法返回对象的实例:spring-doc.cadn.net.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"/>

以下示例显示了相应的ExampleBean类:spring-doc.cadn.net.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.
        @JvmStatic
        fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
            val eb = ExampleBean (...)
            // some other operations...
            return eb
        }
    }
}

参数static工厂方法由<constructor-arg/>元素 与实际使用构造函数完全相同。类的类型是 由工厂方法返回的类不必与 包含staticfactory 方法(尽管在本例中是)。实例 (非静态)工厂方法可以以基本相同的方式使用(旁白 从使用factory-bean属性而不是class属性),所以我们 这里不要讨论这些细节。spring-doc.cadn.net.cn

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

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

直值(基元、字符串等)

value属性的<property/>元素指定属性或构造函数 参数作为人类可读的字符串表示形式。Spring 的转换服务用于转换这些 来自String属性或参数的实际类型。 以下示例显示了设置的各种值:spring-doc.cadn.net.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 命名空间来更简洁 XML 配置:spring-doc.cadn.net.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.cadn.net.cn

您还可以配置java.util.Properties实例,如下所示:spring-doc.cadn.net.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 容器将<value/>元素转换为java.util.Properties使用 JavaBeansPropertyEditor机制。这 是一个不错的捷径,也是 Spring 团队确实喜欢使用 嵌套的<value/>元素在value属性样式。spring-doc.cadn.net.cn

idref元素

idref元素只是一种传递id(字符串值 - 不是 a 引用) 到<constructor-arg/><property/>元素。以下示例演示如何使用它:spring-doc.cadn.net.cn

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

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

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

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

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

第一种形式比第二种形式更可取,因为使用idref标记让 容器在部署时验证引用的命名 bean 实际上 存在。在第二个变体中,不对传递的值执行验证 到targetName属性的client豆。拼写错误仅被发现(大多数 可能致命的结果)当clientbean 实际上是实例化的。如果clientbean 是一个原型 bean,这个拼写错误和由此产生的异常 可能只有在部署容器很久之后才会被发现。spring-doc.cadn.net.cn

local属性idref4.0 Bean 中不再支持 XSD,因为它不提供比常规bean更多参考。改变 您现有的idref local引用idref bean升级到 4.0 架构时。

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

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

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

通过bean属性的<ref/>标签是最 通用形式,并允许在同一容器中创建对任何 bean 的引用,或者 父容器,无论它是否在同一个 XML 文件中。的值bean属性可能与id属性或相同 作为name属性。以下示例 展示了如何使用ref元素:spring-doc.cadn.net.cn

<ref bean="someBean"/>

通过parent属性创建对 bean 的引用 即在当前容器的父容器中。的值parent属性可以与id属性或 值name属性。目标 Bean 必须位于 当前容器的父容器。您应该主要使用此 bean 引用变体 当您有一个容器层次结构并且想要将现有 Bean 包装在父 Bean 中时 容器,其代理与父 Bean 具有相同的名称。以下一对 listings 展示了如何使用parent属性:spring-doc.cadn.net.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>
local属性ref4.0 Bean 中不再支持 XSD,因为它不提供比常规bean更多参考。改变 您现有的ref local引用ref bean升级到 4.0 架构时。
内豆

一个<bean/>元素<property/><constructor-arg/>元素定义了一个 inner bean,如以下示例所示:spring-doc.cadn.net.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 或名称。如果指定,则容器 不使用此类值作为标识符。容器还忽略了scope标记 on creation,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建 豆。无法独立访问内部 bean 或将它们注入 协作 Bean 以外的 Bean。spring-doc.cadn.net.cn

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

收集

<list/>,<set/>,<map/><props/>元素设置属性 和 Java 的论点Collection类型List,Set,MapProperties, 分别。以下示例显示了如何使用它们:spring-doc.cadn.net.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>

映射键或值的值,或设置值,也可以是 以下元素:spring-doc.cadn.net.cn

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

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

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

以下示例演示了集合合并:spring-doc.cadn.net.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>

请注意merge=true属性<props/>元素的adminEmails属性的childbean 定义。当childbean 已解析 并由容器实例化,则生成的实例具有adminEmails Properties包含合并子项的adminEmails集合与父级的adminEmails收集。以下列表 显示结果:spring-doc.cadn.net.cn

孩子Properties集合的值集从 父母<props/>,而孩子的值supportvalue 覆盖 父集合。spring-doc.cadn.net.cn

此合并行为类似于<list/>,<map/><set/>集合类型。在具体情况下<list/>元素,语义 与List集合类型(即ordered值集合)被维护。父级的值位于所有子列表的 值。在Map,SetProperties集合类型,无排序 存在。因此,对于基础的集合类型,没有排序语义有效 关联的Map,SetProperties容器的实现类型 内部使用。spring-doc.cadn.net.cn

集合合并的局限性

您不能合并不同的集合类型(例如MapList).如果你 尝试这样做,适当的Exception被抛出。这merge属性必须是 在较低的继承子定义上指定。指定merge属性 父集合定义是冗余的,不会导致所需的合并。spring-doc.cadn.net.cn

强类型集合

由于 Java 对泛型类型的支持,您可以使用强类型集合。 也就是说,可以声明一个Collection类型,使其只能包含 (例如)String元素。如果您使用 Spring 依赖注入一个 强类型Collection变成 bean,你可以利用 Spring 的 类型转换支持,以便强类型的元素Collection实例在添加到Collection. 以下 Java 类和 bean 定义显示了如何执行此作:spring-doc.cadn.net.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>

accounts属性的something豆子是注射用的,仿制药 有关强类型的元素类型的信息Map<String, Float>是 可通过反思获得。因此,Spring 的类型转换基础设施识别 各种值元素作为类型Float,字符串值 (9.99,2.753.99) 转换为实际的Float类型。spring-doc.cadn.net.cn

空字符串值和空字符串值

Spring 将属性等的空参数视为空Strings.这 以下基于 XML 的配置元数据代码段将email属性到空的String值(“”)。spring-doc.cadn.net.cn

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

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

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

<null/>元素句柄null值。以下列表显示了一个示例:spring-doc.cadn.net.cn

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

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

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

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

Spring 支持带有命名空间的可扩展配置格式, 这些基于 XML 模式定义。这beans配置格式在 本章在 XML Schema 文档中定义。但是,没有定义 p 命名空间 在 XSD 文件中,并且仅存在于 Spring 的核心中。spring-doc.cadn.net.cn

以下示例显示了两个 XML 片段(第一个使用 标准 XML 格式,第二个使用 p-namespace),解析为相同的结果:spring-doc.cadn.net.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>

该示例显示了 p 命名空间中名为email在 bean 定义中。 这告诉 Spring 包含一个属性声明。如前所述, p-namespace 没有模式定义,因此您可以设置属性的名称 到属性名称。spring-doc.cadn.net.cn

下一个示例包括另外两个 bean 定义,它们都引用了 另一个 bean:spring-doc.cadn.net.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定义使用<property name="spouse" ref="jane"/>从 bean 创建引用john到豆jane,第二个 bean 定义使用p:spouse-ref="jane"作为 属性来做完全相同的事情。在这种情况下,spouse是属性名称, 而-refpart 表示这不是一个直接值,而是一个 引用另一个 bean。spring-doc.cadn.net.cn

p 命名空间不如标准 XML 格式灵活。例如,格式 用于声明属性引用与结尾为Ref,而 标准 XML 格式则不然。我们建议您谨慎选择方法,并 将此信息传达给您的团队成员,以避免生成使用 同时进行三种方法。
带有 c 命名空间的 XML 快捷方式

类似于带有 p 命名空间的 XML 快捷方式,即 Spring 中引入的 c 命名空间 3.1,允许内联属性来配置构造函数参数,而不是 然后嵌套constructor-arg元素。spring-doc.cadn.net.cn

以下示例使用c:命名空间来执行与基于构造函数的依赖注入相同的作:spring-doc.cadn.net.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>

c:命名空间使用与p:一(尾随-ref为 bean 引用)用于按名称设置构造函数参数。同样地 它需要在 XML 文件中声明,即使它未在 XSD 模式中定义 (它存在于 Spring 核心中)。spring-doc.cadn.net.cn

对于构造函数参数名称不可用的极少数情况(通常如果 字节码是在没有调试信息的情况下编译的),您可以使用回退到 参数索引,如下所示:spring-doc.cadn.net.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 允许)。 相应的索引表示法也可用于_<constructor-arg>元素,但 不常用,因为简单的声明顺序通常就足够了。

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

复合属性名称

设置 bean 属性时,可以使用复合或嵌套属性名称,只要 除最终属性名称外,路径的所有组件都不是null.考虑一下 以下 bean 定义:spring-doc.cadn.net.cn

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

somethingbean 有一个fred属性,其中具有bob属性,其中具有sammy属性,并且最终sammy属性设置为123.为了 这要工作,fred属性somethingbob属性fred莫 是null在 bean 构建之后。否则,一个NullPointerException被抛出。spring-doc.cadn.net.cn

1.4.3. 使用depends-on

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

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

要表达对多个 Bean 的依赖关系,请提供 Bean 名称列表作为 这depends-on属性(逗号、空格和分号有效 分隔符):spring-doc.cadn.net.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" />
depends-on属性可以指定初始化时依赖项和, 在仅单例 Bean 的情况下,对应的 销毁时间依赖。定义depends-on关系 在给定的 bean 本身被销毁之前,首先销毁给定的 bean。 因此depends-on还可以控制关机顺序。

1.4.4. 延迟初始化的 Bean

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

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

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

当上述配置被ApplicationContextlazy豆 当ApplicationContext开始 而not.lazybean 被急切地预实例化。spring-doc.cadn.net.cn

但是,当延迟初始化的 Bean 是单例 Bean 的依赖项时,即 not lazy-initialized,则ApplicationContext在 startup,因为它必须满足单例的依赖关系。延迟初始化的 bean 被注入到其他未延迟初始化的单例 Bean 中。spring-doc.cadn.net.cn

您还可以使用default-lazy-init属性<beans/>元素,如以下示例所示:spring-doc.cadn.net.cn

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

1.4.5. 自动布线协作者

Spring 容器可以自动连接协作 Bean 之间的关系。您可以 让 Spring 通过以下方式自动解析您的 bean 的协作者(其他 bean)。 检查ApplicationContext.自动布线具有以下内容 优势:spring-doc.cadn.net.cn

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

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

使用基于 XML 的配置元数据(请参阅依赖项注入)时,您可以 可以使用autowire属性的<bean/>元素。自动布线功能有四种模式。指定自动布线 每个 bean,因此可以选择要自动连接哪些。下表描述了 四种自动接线模式:spring-doc.cadn.net.cn

表 2.自动接线模式
模式 解释

nospring-doc.cadn.net.cn

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

byNamespring-doc.cadn.net.cn

按属性名称自动布线。Spring 寻找与 需要自动连接的属性。例如,如果 Bean 定义设置为 autowire by name,它包含一个master属性(即,它有一个setMaster(..)方法),Spring 查找名为master和用途 it 来设置属性。spring-doc.cadn.net.cn

byTypespring-doc.cadn.net.cn

如果该属性类型的 bean 恰好存在于 容器。如果存在多个异常,则会抛出致命异常,这表示 您不得使用byType自动布线。如果没有匹配 beans,则什么也没发生(属性未设置)。spring-doc.cadn.net.cn

constructorspring-doc.cadn.net.cn

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

byTypeconstructorautowiring 模式下,您可以连接阵列和 类型化集合。在这种情况下,容器内的所有自动连接候选者 匹配预期类型以满足依赖关系。您可以自动布线 强类型Map如果预期的键类型为String.自动接线Map实例的值由与预期类型匹配的所有 Bean 实例组成,并且Map实例的键包含相应的 bean 名称。spring-doc.cadn.net.cn

自动接线的局限性和缺点

当自动布线在整个项目中一致使用时,效果最佳。如果自动接线是 通常不使用,仅使用它来连接一个或 两个 bean 定义。spring-doc.cadn.net.cn

考虑自动布线的局限性和缺点:spring-doc.cadn.net.cn

  • 显式依赖项propertyconstructor-arg设置始终覆盖 自动接线。您不能自动连接简单的属性,例如基元、StringsClasses(以及此类简单属性的数组)。此限制是 设计。spring-doc.cadn.net.cn

  • 自动布线不如显式布线精确。虽然,如上表所述, Spring 小心翼翼地避免猜测,以防出现可能出现意外的歧义 结果。Spring 托管对象之间的关系不再 明确记录。spring-doc.cadn.net.cn

  • 可能无法从以下位置生成文档的工具提供接线信息 弹簧容器。spring-doc.cadn.net.cn

  • 容器中的多个 bean 定义可能与 setter 方法或构造函数参数。对于数组、集合或Map实例,这不一定是问题。但是,对于依赖项 期望单个值,则不会任意解决这种歧义。如果没有唯一的 bean definition 可用,则引发异常。spring-doc.cadn.net.cn

在后一种情况下,您有多种选择:spring-doc.cadn.net.cn

从自动布线中排除 Bean

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

autowire-candidate属性设计为仅影响基于类型的自动布线。 它不会影响按名称的显式引用,即使 指定的 bean 未标记为自动配线候选。因此,自动接线 但是,如果名称匹配,则按名称会注入 bean。

您还可以根据与 Bean 名称的模式匹配来限制自动连线候选者。这 顶层<beans/>元素接受其default-autowire-candidates属性。例如,要限制自动配线候选状态 到名称以Repository,提供*Repository.自 提供多个模式,在逗号分隔的列表中定义它们。显式值truefalse对于 bean 定义的autowire-candidate属性始终采用 优先。对于此类 bean,模式匹配规则不适用。spring-doc.cadn.net.cn

这些技术对于您永远不想注入到其他 Bean 中的 Bean 很有用 通过自动布线的豆子。这并不意味着排除的 Bean 本身不能由 使用自动布线。相反,Bean 本身不是自动连接其他 Bean 的候选者。spring-doc.cadn.net.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.cadn.net.cn

一个解决方案是放弃一些控制倒置。你可以制作 bean A 通过实现ApplicationContextAware接口 和制作一个getBean("B")调用容器要求 (a 通常为 new) bean B 实例。以下示例 显示了这种方法:spring-doc.cadn.net.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.cadn.net.cn

您可以在此博客文章中阅读有关方法注入动机的更多信息。spring-doc.cadn.net.cn

查找方法注入

查找方法注入是容器覆盖容器管理的 bean 上的方法的能力,并在 容器。 查找通常涉及原型 bean,如上一节中描述的场景中。Spring 框架通过使用 CGLIB 库中的字节码生成来实现此方法注入,以动态生成覆盖该方法的子类。spring-doc.cadn.net.cn

  • 为了使这种动态子类化工作,Spring bean 容器子类不能是final,并且要重写的方法不能是final也。spring-doc.cadn.net.cn

  • 对具有abstract方法要求您将类自己子类化,并提供abstract方法。spring-doc.cadn.net.cn

  • 组件扫描也需要具体方法,这需要具体 要上课。spring-doc.cadn.net.cn

  • 另一个关键限制是查找方法不适用于工厂方法和 特别是不与@Bean配置类中的方法,因为在这种情况下, 容器不负责创建实例,因此无法创建 运行时动态生成的子类。spring-doc.cadn.net.cn

CommandManager类,则 Spring 容器动态覆盖createCommand()方法。这CommandManagerclass 没有任何 Spring 依赖项,因为 重新设计的示例显示:spring-doc.cadn.net.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
}

在包含要注入的方法的客户端类中(CommandManager在这个 case),要注入的方法需要以下形式的签名:spring-doc.cadn.net.cn

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

如果方法是abstract,动态生成的子类实现该方法。 否则,动态生成的子类将覆盖 原始类。请考虑以下示例:spring-doc.cadn.net.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>

标识为commandManager调用自己的createCommand()方法 每当它需要一个新的实例时myCommand豆。部署时必须小心 这myCommandbean 作为原型,如果这确实是需要的。如果是 单示例,则与myCommand每次返回 bean。spring-doc.cadn.net.cn

或者,在基于注释的组件模型中,您可以声明查找 方法通过@Lookup注释,如以下示例所示:spring-doc.cadn.net.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 针对 查找方法的声明返回类型:spring-doc.cadn.net.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
}

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

访问不同作用域的目标 Bean 的另一种方法是ObjectFactory/ Provider注射点。请参阅作为依赖项的作用域 Beanspring-doc.cadn.net.cn

您可能还会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.configpackage)有用。spring-doc.cadn.net.cn

任意方法替换

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

对于基于 XML 的配置元数据,您可以使用replaced-method元素设置为 将现有方法实现替换为另一个已部署 bean 的方法实现。考虑 以下类,它有一个名为computeValue我们想要覆盖的:spring-doc.cadn.net.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.MethodReplacerinterface 提供了新的方法定义,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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"/>

您可以使用一个或多个<arg-type/>元素中的<replaced-method/>元素来指示被覆盖的方法的方法签名。签名 因为参数只有在方法重载且多个变体时才需要 存在于类中。为方便起见,参数的类型字符串可以是 子字符串。例如,以下全部匹配java.lang.String:spring-doc.cadn.net.cn

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的参数 选择,这个快捷方式可以节省大量输入,让你只输入 与参数类型匹配的最短字符串。spring-doc.cadn.net.cn

1.5. Bean 作用域

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

您不仅可以控制各种依赖关系和配置值,这些依赖关系和配置值 插入到从特定 bean 定义创建的对象中,但同时也要控制 从特定 Bean 定义创建的对象的范围。这种方法是 强大而灵活,因为您可以选择创建对象的范围 通过配置,而不必在 Java 的对象范围内烘焙 班级级别。可以将 Bean 定义为部署在多个作用域之一中。 Spring Framework 支持六个作用域,其中四个仅在 您使用 Web 感知ApplicationContext.您还可以创建自定义范围。spring-doc.cadn.net.cn

支持的范围如下表所示:spring-doc.cadn.net.cn

表 3.Bean 作用域
范围 描述

单身 人士spring-doc.cadn.net.cn

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

原型spring-doc.cadn.net.cn

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

请求spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是 每个 HTTP 请求都有自己的 Bean 实例,这些实例是在单个 Bean 的后面创建的 定义。仅在 Web 感知 Spring 的上下文中有效ApplicationContext.spring-doc.cadn.net.cn

会期spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为 HTTP 的生命周期Session.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

应用spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为ServletContext.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

网络套接字spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为WebSocket.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

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

1.5.1. 单例作用域

仅管理单例 Bean 的一个共享实例,并且对 Bean 的所有请求 与该 Bean 定义匹配的一个或多个 ID 会导致该特定 Bean 实例。spring-doc.cadn.net.cn

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

单身 人士

Spring 的单例 Bean 概念与 四人帮 (GoF) 模式书。GoF 单例对 对象,以便每个类只创建一个特定类的实例 ClassLoader 的 ClassLoader 中。Spring 单例的范围最好描述为每个容器 和每颗豆子。这意味着,如果您在 单个 Spring 容器,Spring 容器创建一个且仅一个实例 由该 bean 定义定义的类。单例作用域是默认作用域 在Spring。要在 XML 中将 Bean 定义为单例,您可以定义一个 Bean,如 以下示例:spring-doc.cadn.net.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 中,或者您通过getBean()方法调用 容器。通常,您应该对所有有状态 bean 使用原型作用域,并且 无状态 bean 的单例作用域。spring-doc.cadn.net.cn

下图说明了 Spring 原型作用域:spring-doc.cadn.net.cn

原型

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

以下示例将 bean 定义为 XML 中的原型:spring-doc.cadn.net.cn

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

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

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

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

当您使用依赖于原型 Bean 的单例范围 Bean 时,请注意 依赖关系在实例化时解析。因此,如果您依赖注入一个 原型作用域的 Bean 转换为单例作用域的 Bean,则实例化新的原型 Bean 然后依赖注入到单例 Bean 中。原型实例是唯一的 实例,该实例曾经提供给单例作用域的 bean。spring-doc.cadn.net.cn

但是,假设您希望单例作用域的 bean 获取 原型作用域的 bean 在运行时重复。您不能依赖注入 原型作用域的 bean 到您的单例 bean 中,因为该注入仅发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要一个新的原型 Bean 实例,请位于 运行时,请参阅方法注入spring-doc.cadn.net.cn

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

request,session,applicationwebsocket范围仅可用 如果您使用 Web 感知的 SpringApplicationContext实现(例如XmlWebApplicationContext).如果将这些作用域与常规 Spring IoC 容器一起使用, 例如ClassPathXmlApplicationContextIllegalStateException抱怨 抛出一个未知的 bean 作用域。spring-doc.cadn.net.cn

初始 Web 配置

支持在request,session,applicationwebsocket级别(Web 范围的 bean),一些次要的初始配置是 required 在定义 bean 之前。(不需要此初始设置 对于标准示波器:singletonprototype.)spring-doc.cadn.net.cn

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

如果您在 Spring Web MVC 中访问作用域 bean,则实际上,在请求中 由弹簧加工DispatcherServlet,无需特殊设置。DispatcherServlet已经暴露了所有相关状态。spring-doc.cadn.net.cn

如果您使用 Servlet 2.5 Web 容器,则在 Spring 的DispatcherServlet(例如,当使用 JSF 或 Struts 时),您需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener. 对于 Servlet 3.0+,这可以通过使用WebApplicationInitializer接口。或者,对于较旧的容器,将以下声明添加到 您的 Web 应用程序的web.xml文件:spring-doc.cadn.net.cn

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

或者,如果您的侦听器设置有问题,请考虑使用 Spring 的RequestContextFilter.过滤器映射取决于周围的网络 应用程序配置,因此您必须根据需要更改它。以下列表 显示 Web 应用程序的过滤器部分:spring-doc.cadn.net.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,RequestContextListenerRequestContextFilter都做到了 同样的事情,即将 HTTP 请求对象绑定到Thread那就是服务 那个请求。这使得请求和会话范围的 Bean 进一步可用 呼叫链的下游。spring-doc.cadn.net.cn

请求范围

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个LoginActionbean 通过使用loginAction每个 HTTP 请求的 bean 定义。也就是说,loginActionbean 的范围为 HTTP 请求级别。您可以更改内部 根据需要创建的实例的状态,因为其他实例 从相同的loginActionbean 定义在状态中看不到这些变化。 它们特定于个人要求。当请求完成处理时, 范围限定为请求的 bean 将被丢弃。spring-doc.cadn.net.cn

当使用注释驱动的组件或 Java 配置时,@RequestScope注解 可用于将组件分配给request范围。以下示例演示了如何 为此:spring-doc.cadn.net.cn

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

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个UserPreferencesbean 通过使用userPreferences单个 HTTP 生命周期的 bean 定义Session.在其他 单词,userPreferencesbean 的有效作用域为 HTTPSession水平。如 使用请求范围的 Bean,您可以更改实例的内部状态,即 随心所欲地创建,知道其他 HTTPSession实例也是 使用从同一userPreferencesbean 定义看不到这些 状态更改,因为它们特定于单个 HTTPSession.当 HTTPSession最终被丢弃,则作用域限定为该特定 HTTP 的 BeanSession也被丢弃了。spring-doc.cadn.net.cn

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

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

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个AppPreferencesbean 通过使用appPreferences为整个 Web 应用程序定义一次。也就是说,appPreferencesbean 的作用域为ServletContext级别并存储为常规ServletContext属性。这有点类似于 Spring 单例 bean,但是 在两个重要方面有所不同: 它是单例 perServletContext,而不是每个弹簧ApplicationContext(在任何给定的 Web 应用程序中可能有多个), 它实际上是公开的,因此可见为ServletContext属性。spring-doc.cadn.net.cn

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

Java
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
Kotlin
@ApplicationScope
@Component
class AppPreferences {
    // ...
}
WebSocket 作用域

WebSocket 作用域与 WebSocket 会话的生命周期相关联,并适用于 STOMP over WebSocket 应用程序,请参阅 WebSocket 范围了解更多详细信息。spring-doc.cadn.net.cn

作用域 Bean 作为依赖项

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

您还可以使用<aop:scoped-proxy/>在作用域为singleton, 引用然后通过可序列化的中间代理 因此能够在反序列化时重新获得目标单例 Bean。spring-doc.cadn.net.cn

声明时<aop:scoped-proxy/>针对作用域的 Beanprototype,每个方法 调用共享代理会导致创建一个新的目标实例,该目标实例的 然后呼叫正在被转接。spring-doc.cadn.net.cn

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

作为扩展变体,您可以声明ObjectProvider<MyTargetBean>哪个交付 几个额外的访问变体,包括getIfAvailablegetIfUnique.spring-doc.cadn.net.cn

其 JSR-330 变体称为Provider并与Provider<MyTargetBean>声明和相应的get()调用每次检索尝试。 有关 JSR-330 整体的更多详细信息,请参阅此处spring-doc.cadn.net.cn

以下示例中的配置只有一行,但重要的是 了解背后的“为什么”和“如何”:spring-doc.cadn.net.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 定义代理的行。

要创建此类代理,请插入一个子代理<aop:scoped-proxy/>元素转换为作用域 bean 定义(请参阅选择要创建的代理类型基于 XML 模式的配置)。 为什么 bean 的定义作用域为request,session和自定义范围 级别需要<aop:scoped-proxy/>元素? 考虑以下单例 bean 定义,并将其与 您需要为上述范围定义什么(请注意,以下内容userPreferencesbean 定义目前是不完整的):spring-doc.cadn.net.cn

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

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

在前面的示例中,单例 Bean (userManager) 注入引用 到 HTTPSession-作用域 bean (userPreferences).这里的突出点是userManagerbean 是一个单例:它每 容器及其依赖项(在本例中只有一个,即userPreferencesbean)是 也只注射过一次。这意味着userManagerbean 仅在 一模一样userPreferences对象(即最初注入它的对象)。spring-doc.cadn.net.cn

这不是将寿命较短的作用域 bean 注入 较长寿命的作用域 Bean(例如,注入 HTTPSession-范围协作 bean 作为单例 bean 的依赖项)。相反,您需要一个userManager对象,并且,在 HTTP 的生命周期内Session,您需要一个userPreferences对象 特定于 HTTPSession.因此,容器创建一个对象,该对象 公开与UserPreferences类(理想情况下是 对象,即UserPreferencesinstance),它可以获取实数UserPreferences对象(HTTP 请求、Session,等等 第四)。容器将此代理对象注入到userManagerbean,即 不知道这个UserPreferencesreference 是代理。在此示例中,当UserManager实例调用依赖注入的方法UserPreferences对象,它实际上是在调用代理上的方法。然后代理获取真实的UserPreferences对象来自(在本例中)HTTPSession并委托 方法调用到检索到的实数UserPreferences对象。spring-doc.cadn.net.cn

因此,在注入时需要以下(正确且完整的)配置request-session-scopedbean 转换为协作对象,如以下示例所示 显示:spring-doc.cadn.net.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 容器为标记为 这<aop:scoped-proxy/>元素,则创建基于 CGLIB 的类代理。spring-doc.cadn.net.cn

CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,不会委托给实际作用域的目标对象。spring-doc.cadn.net.cn

或者,您可以配置 Spring 容器以创建标准 JDK 此类作用域 Bean 的基于接口的代理,通过指定false对于 这proxy-target-class属性的<aop:scoped-proxy/>元素。使用 JDK 基于接口的代理意味着您不需要在 应用程序类路径来影响此类代理。但是,这也意味着 作用域 Bean 必须至少实现一个接口,并且所有协作者 注入作用域 Bean 的 Bean 必须通过其 接口。以下示例显示了基于接口的代理:spring-doc.cadn.net.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.cadn.net.cn

1.5.5. 自定义作用域

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

创建自定义作用域

要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope接口,在此 部分。有关如何实现您自己的范围的想法,请参阅ScopeSpring Framework 本身提供的实现和Scopejavadoc, 它更详细地解释了您需要实现的方法。spring-doc.cadn.net.cn

Scope接口有四种方法从作用域中获取对象,从 范围,让它们被摧毁。spring-doc.cadn.net.cn

例如,会话作用域实现返回会话作用域的 bean(如果它 不存在,则该方法在将 bean 绑定到 会议以备将来参考)。以下方法从 基础范围:spring-doc.cadn.net.cn

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

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

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

以下方法注册一个回调,当它 destroyed 或当作用域中的指定对象被销毁时:spring-doc.cadn.net.cn

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

有关销毁回调的更多信息,请参阅 javadoc 或 Spring 作用域实现。spring-doc.cadn.net.cn

以下方法获取基础作用域的对话标识符:spring-doc.cadn.net.cn

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

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

使用自定义作用域

编写并测试一个或多个自定义Scope实现,您需要使 Spring 容器知道你的新作用域。以下方法是中心 注册新Scope使用 Spring 容器:spring-doc.cadn.net.cn

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

此方法在ConfigurableBeanFactory接口,该接口可用 通过BeanFactory大部分混凝土的属性ApplicationContextSpring 附带的实现。spring-doc.cadn.net.cn

第一个参数registerScope(..)method 是与 一个范围。Spring 容器本身中此类名称的示例是singletonprototype.第二个参数registerScope(..)method 是一个实际实例 的Scope您希望注册和使用的实现。spring-doc.cadn.net.cn

假设你编写了自定义Scope实现,然后如图所示注册 在下一个示例中。spring-doc.cadn.net.cn

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

然后,您可以创建符合自定义范围规则的 Bean 定义Scope如下:spring-doc.cadn.net.cn

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

使用自定义Scope实施,您不仅限于程序化注册 范围。您还可以执行Scope注册,通过使用CustomScopeConfigurer类,如以下示例所示:spring-doc.cadn.net.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>
当您放置<aop:scoped-proxy/><bean>声明FactoryBean实现时,作用域的是工厂 bean 本身,而不是对象 返回自getObject().

1.6. 自定义 Bean 的性质

Spring Framework 提供了许多接口,您可以使用它来自定义性质 豆子的。本节按如下方式对它们进行分组:spring-doc.cadn.net.cn

1.6.1. 生命周期回调

要与容器对 Bean 生命周期的管理进行交互,您可以实现 SpringInitializingBeanDisposableBean接口。容器调用afterPropertiesSet()对于前者和destroy()让后者让豆子 在初始化和销毁 Bean 时执行某些作。spring-doc.cadn.net.cn

The JSR-250@PostConstruct@PreDestroy注释通常被认为是最好的 在现代 Spring 应用程序中接收生命周期回调的练习。使用这些 注释意味着您的 bean 未耦合到特定于 Spring 的接口。 有关详细信息,请参阅@PostConstruct@PreDestroy.spring-doc.cadn.net.cn

如果您不想使用 JSR-250 注释,但仍想删除 耦合,考虑init-methoddestroy-methodbean 定义元数据。spring-doc.cadn.net.cn

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

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

本节介绍了生命周期回调接口。spring-doc.cadn.net.cn

初始化回调

org.springframework.beans.factory.InitializingBean接口允许 bean 在容器在 豆。这InitializingBeaninterface 指定了单个方法:spring-doc.cadn.net.cn

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它 不必要地将代码耦合到 Spring。或者,我们建议使用 这@PostConstruct注释或 指定 POJO 初始化方法。对于基于 XML 的配置元数据, 您可以使用init-method属性来指定具有 void 的方法的名称 无参数签名。在 Java 配置中,您可以使用initMethod属性@Bean.请参阅接收生命周期回调。请考虑以下示例:spring-doc.cadn.net.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.cadn.net.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.cadn.net.cn

请注意@PostConstruct并且通常执行初始化方法 在容器的单例创建锁中。仅考虑 bean 实例 作为完全初始化的,并准备好在从@PostConstruct方法。此类单独的初始化方法仅用于 用于验证配置状态并可能准备一些数据结构 基于给定的配置,但没有外部 Bean 访问的进一步活动。 否则存在初始化死锁的风险。spring-doc.cadn.net.cn

对于要触发昂贵的初始化后活动的方案, 例如,异步数据库准备步骤,您的 bean 应该实现SmartInitializingSingleton.afterSingletonsInstantiated()或依赖上下文 刷新事件:实现ApplicationListener<ContextRefreshedEvent>或 声明其注释等效@EventListener(ContextRefreshedEvent.class). 这些变体出现在所有常规单例初始化之后,因此 在任何单例创建锁之外。spring-doc.cadn.net.cn

或者,您可以实现(Smart)Lifecycle接口并集成 容器的整体生命周期管理,包括自动启动机制、 销毁前停止步骤,以及潜在的停止/重新启动回调(见下文)。spring-doc.cadn.net.cn

销毁回调

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

void destroy() throws Exception;

我们建议您不要使用DisposableBeancallback 接口,因为它 不必要地将代码耦合到 Spring。或者,我们建议使用 这@PreDestroy注释或 指定 Bean 定义支持的通用方法。使用基于 XML 的 配置元数据,您可以使用destroy-method属性<bean/>. 在 Java 配置中,您可以使用destroyMethod属性@Bean.请参阅接收生命周期回调。考虑以下定义:spring-doc.cadn.net.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.cadn.net.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.cadn.net.cn

您可以分配destroy-method属性<bean>元素 a 特殊(inferred)value,它指示 Spring 自动检测公共closeshutdown方法。(任何实现java.lang.AutoCloseablejava.io.Closeable因此会匹配。您还可以 设置这个特别的(inferred)default-destroy-method属性<beans>元素将此行为应用于整组 bean(参见默认初始化和销毁方法)。请注意,这是 默认行为@BeanJava 配置类中的方法。

对于延长的关机阶段,您可以实现Lifecycle接口和接收 在调用任何单例 Bean 的 destroy 方法之前的提前停止信号。 您也可以实现SmartLifecycle对于有时间限制的停止步骤,其中容器 将等待所有此类停止处理完成,然后再继续销毁方法。spring-doc.cadn.net.cn

默认初始化和销毁方法

当您编写初始化和销毁方法回调时,不使用 弹簧专用InitializingBeanDisposableBean回调接口,您 通常使用以下名称编写方法init(),initialize(),dispose(), 等等。理想情况下,此类生命周期回调方法的名称在各个领域都是标准化的 一个项目,以便所有开发人员使用相同的方法名称并确保一致性。spring-doc.cadn.net.cn

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

假设初始化回调方法名为init()和你的毁灭 回调方法命名为destroy().然后,您的类类似于 以下示例:spring-doc.cadn.net.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.cadn.net.cn

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

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

</beans>

的存在default-init-method属性<beans/>元素 属性导致 Spring IoC 容器识别一个名为init在豆子上 class 作为初始化方法回调。创建和组装 Bean 时,如果 bean 类有这样的方法,它在适当的时候被调用。spring-doc.cadn.net.cn

您可以使用default-destroy-method属性<beans/>元素。spring-doc.cadn.net.cn

现有 bean 类已经具有以差异命名的回调方法 使用约定时,您可以通过指定(即在 XML 中)的 方法 name 的init-methoddestroy-method属性<bean/>本身。spring-doc.cadn.net.cn

Spring 容器保证调用配置的初始化回调 立即提供所有依赖项的 bean。因此,初始化 callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等 第四个尚未应用于 bean。首先完全创建目标 Bean,并且 然后应用 AOP 代理(例如)及其拦截器链。如果目标 bean 和代理是单独定义的,你的代码甚至可以与原始 target bean,绕过代理。因此,应用 拦截器到init方法,因为这样做会耦合 将 bean 定位到其代理或拦截器,并在代码时留下奇怪的语义 直接与原始目标 Bean 交互。spring-doc.cadn.net.cn

组合生命周期机制

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

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

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

  1. @PostConstructspring-doc.cadn.net.cn

  2. afterPropertiesSet()InitializingBean回调接口spring-doc.cadn.net.cn

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

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

  1. @PreDestroyspring-doc.cadn.net.cn

  2. destroy()DisposableBean回调接口spring-doc.cadn.net.cn

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

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 Spring 托管的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收启动和停止信号(例如,对于停止/重新启动 运行时的场景),它会将这些调用级联到Lifecycle实现 在这种背景下定义。它通过委托给LifecycleProcessor显示 在以下列表中:spring-doc.cadn.net.cn

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

请注意,LifecycleProcessor本身就是Lifecycle接口。它还添加了另外两种方法来对正在刷新的上下文做出反应 并关闭。spring-doc.cadn.net.cn

请注意,常规org.springframework.context.Lifecycle界面是普通的 显式启动和停止通知的合约,并不意味着自动启动 在上下文刷新时。用于对自动启动的细粒度控制和优雅 停止特定 Bean(包括启动和停止阶段),请考虑实现 扩展的org.springframework.context.SmartLifecycle界面。spring-doc.cadn.net.cn

另外,请注意,不能保证在销毁之前会收到停止通知。 在定期关闭时,所有Lifecyclebean 首先收到停止通知,然后 常规销毁回调正在传播中。但是,在热刷新期间 上下文的生存期或停止的刷新尝试时,仅调用 destroy 方法。spring-doc.cadn.net.cn

启动和关闭调用的顺序可能很重要。如果“依赖” 关系存在于任意两个对象之间,则依赖端在其之后开始 依赖关系,并且它在依赖关系之前停止。然而,有时,直接 依赖关系未知。您可能只知道某种类型的对象应该启动 在另一种类型的对象之前。在这些情况下,SmartLifecycle接口定义 另一个选项,即getPhase()在其超级接口上定义的方法,Phased.以下列表显示了Phased接口:spring-doc.cadn.net.cn

public interface Phased {

    int getPhase();
}

以下列表显示了SmartLifecycle接口:spring-doc.cadn.net.cn

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,相位最低的对象首先启动。停止时, 遵循相反的顺序。因此,实现SmartLifecycle和 谁的getPhase()方法返回Integer.MIN_VALUE将是最早开始的 最后一个停下来的。在频谱的另一端,相位值Integer.MAX_VALUE将指示对象应最后启动并停止 首先(可能是因为它依赖于其他进程的运行)。在考虑 相位值,同样重要的是要知道任何“正常”的默认相位Lifecycle未实现的对象SmartLifecycle0.因此,任何 负相位值表示对象应在这些标准之前启动 组件(并在它们之后停止)。对于任何正相位值,情况正相关。spring-doc.cadn.net.cn

SmartLifecycle接受回调。任何 实现必须调用该回调的run()方法,然后是该实现的 关闭过程完成。这在必要时启用异步关闭,因为 默认实现的LifecycleProcessor接口DefaultLifecycleProcessor,等待对象组的超时值 在每个阶段中调用该回调。默认的每阶段超时为 30 秒。 您可以通过定义名为lifecycleProcessor在上下文中。如果只想修改超时, 定义以下内容就足够了:spring-doc.cadn.net.cn

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

如前所述,LifecycleProcessor接口定义了 刷新和关闭上下文。后者推动关机 处理好像stop()已被显式调用,但当上下文为 关闭。另一方面,“refresh”回调启用了另一个功能SmartLifecycle豆。刷新上下文时(在所有对象都已刷新后) 实例化并初始化),调用该回调。此时, 默认生命周期处理器检查每个SmartLifecycle对象的isAutoStartup()方法。如果true,则该对象是 从那时开始,而不是等待显式调用上下文的 or 自己start()方法(与上下文刷新不同,上下文启动不会发生 自动用于标准上下文实现)。这phasevalue 和任何 “依赖”关系确定启动顺序,如前所述。spring-doc.cadn.net.cn

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

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

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

要注册关机挂钩,请调用registerShutdownHook()方法是 在ConfigurableApplicationContext接口,如以下示例所示:spring-doc.cadn.net.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

ApplicationContext创建一个对象实例,该实例实现了org.springframework.context.ApplicationContextAware接口,则提供实例 并参考了这一点ApplicationContext.以下列表显示了定义 的ApplicationContextAware接口:spring-doc.cadn.net.cn

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean 可以以编程方式作ApplicationContext创造了他们, 通过ApplicationContext接口或通过将引用转换为已知的 该接口的子类(例如ConfigurableApplicationContext,这会暴露 附加功能)。一种用途是以编程方式检索其他 bean。 有时此功能很有用。但是,一般来说,您应该避免它,因为 它将代码耦合到 Spring,并且不遵循控制反转样式, 其中协作者作为属性提供给 bean。的其他方法ApplicationContext提供对文件资源的访问、发布应用程序事件、 并访问MessageSource.这些附加功能在的附加功能ApplicationContext.spring-doc.cadn.net.cn

自动布线是获取对ApplicationContext.传统的 constructorbyType自动接线模式 (如自动布线协作器中所述)可以提供类型ApplicationContext对于构造函数参数或 setter 方法参数, 分别。为了获得更大的灵活性,包括自动布线字段和 多种参数方法,使用基于注释的自动布线功能。如果你这样做, 这ApplicationContext自动连接到字段、构造函数参数或方法 参数,该参数需要ApplicationContext如果字段、构造函数或 有问题的方法携带@Autowired注解。有关更多信息,请参阅@Autowired.spring-doc.cadn.net.cn

ApplicationContext创建一个类,实现org.springframework.beans.factory.BeanNameAware接口,该类提供了 对其关联对象定义中定义的名称的引用。以下列表 显示了 BeanNameAware 接口的定义:spring-doc.cadn.net.cn

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回调在普通 bean 属性的填充之后调用,但在 初始化回调,例如InitializingBean.afterPropertiesSet()或自定义 init-方法。spring-doc.cadn.net.cn

1.6.3. 其他Aware接口

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

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

ApplicationContextAwarespring-doc.cadn.net.cn

声明ApplicationContext.spring-doc.cadn.net.cn

ApplicationContextAwareBeanNameAwarespring-doc.cadn.net.cn

ApplicationEventPublisherAwarespring-doc.cadn.net.cn

封闭的事件发布者ApplicationContext.spring-doc.cadn.net.cn

的附加功能ApplicationContextspring-doc.cadn.net.cn

BeanClassLoaderAwarespring-doc.cadn.net.cn

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

实例化 Beanspring-doc.cadn.net.cn

BeanFactoryAwarespring-doc.cadn.net.cn

声明BeanFactory.spring-doc.cadn.net.cn

BeanFactory应用程序接口spring-doc.cadn.net.cn

BeanNameAwarespring-doc.cadn.net.cn

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

ApplicationContextAwareBeanNameAwarespring-doc.cadn.net.cn

LoadTimeWeaverAwarespring-doc.cadn.net.cn

定义了用于在加载时处理类定义的织布器。spring-doc.cadn.net.cn

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

MessageSourceAwarespring-doc.cadn.net.cn

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

的附加功能ApplicationContextspring-doc.cadn.net.cn

NotificationPublisherAwarespring-doc.cadn.net.cn

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

通知spring-doc.cadn.net.cn

ResourceLoaderAwarespring-doc.cadn.net.cn

配置了用于对资源的低级访问的加载程序。spring-doc.cadn.net.cn

资源spring-doc.cadn.net.cn

ServletConfigAwarespring-doc.cadn.net.cn

当前ServletConfig容器运行。仅在 Web 感知 Spring 中有效ApplicationContext.spring-doc.cadn.net.cn

弹簧 MVCspring-doc.cadn.net.cn

ServletContextAwarespring-doc.cadn.net.cn

当前ServletContext容器运行。仅在 Web 感知 Spring 中有效ApplicationContext.spring-doc.cadn.net.cn

弹簧 MVCspring-doc.cadn.net.cn

再次请注意,使用这些接口会将您的代码绑定到 Spring API,而不是 遵循控制反转样式。因此,我们建议将它们用于基础设施 需要以编程方式访问容器的 Bean。spring-doc.cadn.net.cn

1.7. Bean 定义继承

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

如果您使用ApplicationContext接口,子 bean 定义由ChildBeanDefinition类。大多数用户不工作 在这个层面上和他们在一起。相反,他们在类中以声明方式配置 Bean 定义 例如ClassPathXmlApplicationContext.使用基于 XML 的配置时 metadata,您可以使用parent属性 将父 Bean 指定为此属性的值。以下示例演示了如何 为此:spring-doc.cadn.net.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 类必须是 与父级兼容(即,它必须接受父级的属性值)。spring-doc.cadn.net.cn

子 Bean 定义继承作用域、构造函数参数值、属性值和 方法覆盖父级,并可选择添加新值。任何作用域,初始化 method、destroy 方法或static您指定的出厂方法设置 覆盖相应的父设置。spring-doc.cadn.net.cn

其余设置始终取自子定义:取决于, autowire 模式、依赖检查、单例和延迟初始化。spring-doc.cadn.net.cn

前面的示例通过使用 这abstract属性。如果父定义未指定类,则显式 将父 Bean 定义标记为abstract是必需的,如以下示例所示 显示:spring-doc.cadn.net.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 不能自行实例化,因为它是不完整的,而且它是 也显式标记为abstract.当定义是abstract是的 只能用作纯模板 bean 定义,用作 子定义。尝试使用这样的abstract父 bean 本身,通过引用 将其作为另一个 bean 的 ref 属性或执行显式getBean()使用 父 Bean ID 返回错误。同样,容器的内部preInstantiateSingletons()方法忽略定义为 抽象。spring-doc.cadn.net.cn

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

1.8. 容器扩展点

通常,应用程序开发人员不需要将ApplicationContext实现类。相反,可以通过插入 特殊集成接口的实现。接下来的几节将介绍这些 集成接口。spring-doc.cadn.net.cn

1.8.1. 使用BeanPostProcessor

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

您可以配置多个BeanPostProcessor实例,您可以控制顺序 其中这些BeanPostProcessor实例通过设置order财产。 仅当BeanPostProcessor实现Ordered接口。如果您编写自己的BeanPostProcessor,您应该考虑实现 这Ordered界面也是如此。有关更多详细信息,请参阅BeanPostProcessorOrdered接口。另请参阅注释 上编程 注册BeanPostProcessor实例.spring-doc.cadn.net.cn

BeanPostProcessor实例对 bean(或对象)实例进行作。那是 Spring IoC 容器实例化一个 bean 实例,然后BeanPostProcessor实例完成他们的工作。spring-doc.cadn.net.cn

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

要更改实际的 Bean 定义(即定义 Bean 的蓝图), 您需要改用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor.spring-doc.cadn.net.cn

org.springframework.beans.factory.config.BeanPostProcessor接口由以下内容组成 恰好有两个回调方法。当此类类注册为后处理器时,具有 容器,对于容器创建的每个 Bean 实例, 后处理器在容器之前从容器获取回调 初始化方法(例如InitializingBean.afterPropertiesSet()或任何 宣布init方法),并在任何 bean 初始化回调之后调用。 后处理器可以对 bean 实例执行任何作,包括忽略 回调。bean 后处理器通常检查回调接口, 或者它可以用代理包装 bean。一些 Spring AOP 基础设施类是 作为 bean 后处理器实现,以提供代理包装逻辑。spring-doc.cadn.net.cn

ApplicationContext自动检测在 实现BeanPostProcessor接口。这ApplicationContext将这些 bean 注册为后处理器,以便可以调用它们 后来,在 bean 创建时。Bean 后处理器可以部署在容器中的 与任何其他豆子一样。spring-doc.cadn.net.cn

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

以编程方式注册BeanPostProcessor实例
虽然推荐的方法BeanPostProcessor注册是通过ApplicationContext自动检测(如前所述),您可以注册它们以编程方式针对ConfigurableBeanFactory通过使用addBeanPostProcessor方法。 当您需要在注册之前评估条件逻辑,甚至用于跨层次结构中的上下文复制 Bean 后处理器时,这会很有用。但是请注意,请注意BeanPostProcessor以编程方式添加的实例不尊重 这Ordered接口。 在这里,是注册的顺序决定了顺序的执行。另请注意BeanPostProcessor以编程方式注册的实例始终在通过自动检测注册的实例之前处理,无论任何显式排序。
BeanPostProcessor实例和 AOP 自动代理

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

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

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

以下示例演示如何编写、注册和使用BeanPostProcessor实例 在ApplicationContext.spring-doc.cadn.net.cn

示例:Hello World,BeanPostProcessor-风格

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

以下列表显示了自定义BeanPostProcessor实现类定义:spring-doc.cadn.net.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
    }
}

以下内容beans元素使用InstantiationTracingBeanPostProcessor:spring-doc.cadn.net.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>

请注意InstantiationTracingBeanPostProcessor只是定义的。它不会 甚至有一个名字,而且,因为它是一个 bean,所以它可以像你一样注入依赖 其他豆子。(前面的配置还定义了一个由 Groovy 支持的 bean 脚本。Spring 动态语言支持在标题为 动态语言支持 的章节中进行了详细介绍。spring-doc.cadn.net.cn

以下 Java 应用程序运行上述代码和配置:spring-doc.cadn.net.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.cadn.net.cn

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

将回调接口或注解与自定义BeanPostProcessor实现是扩展 Spring IoC 容器的常用手段。一个例子是 Spring的AutowiredAnnotationBeanPostProcessor— 一个BeanPostProcessor实现 附带 Spring 分布和自动连接注释字段、setter 方法、 和任意配置方法。spring-doc.cadn.net.cn

1.8.2. 使用BeanFactoryPostProcessor

我们看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor.的语义 此接口类似于BeanPostProcessor,有一个专业 差异:BeanFactoryPostProcessor对 Bean 配置元数据进行作。 也就是说,Spring IoC 容器允许BeanFactoryPostProcessor阅读 配置元数据,并可能在容器实例化之前对其进行更改 除BeanFactoryPostProcessor实例。spring-doc.cadn.net.cn

您可以配置多个BeanFactoryPostProcessor实例,您可以控制 这些BeanFactoryPostProcessor实例通过设置order财产。 但是,只有在BeanFactoryPostProcessor实现Ordered接口。如果您编写自己的BeanFactoryPostProcessor,你应该 考虑实现Ordered界面也是如此。请参阅BeanFactoryPostProcessorOrdered接口了解更多详细信息。spring-doc.cadn.net.cn

如果要更改实际的 bean 实例(即创建的对象 )中,则需要改为BeanPostProcessor(前面在使用BeanPostProcessor).虽然这在技术上是可行的 在BeanFactoryPostProcessor(例如,通过使用BeanFactory.getBean()),这样做会导致 bean 实例化过早,违反 标准容器生命周期。这可能会导致负面副作用,例如绕过 豆后处理。spring-doc.cadn.net.cn

BeanFactoryPostProcessor实例按容器限定范围。这仅相关 如果使用容器层次结构。如果定义BeanFactoryPostProcessor合二为一 container,则仅应用于该容器中的 bean 定义。Bean 定义 在一个容器中不会被后处理BeanFactoryPostProcessor实例 容器,即使两个容器都是同一层次结构的一部分。spring-doc.cadn.net.cn

当 bean 工厂后处理器在ApplicationContext,以便将更改应用于 定义容器。Spring 包括一些预定义的 bean 工厂 后处理器,例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer.您还可以使用自定义BeanFactoryPostProcessor- 例如,注册自定义属性编辑器。spring-doc.cadn.net.cn

ApplicationContext自动检测部署到其中的任何 bean 实现BeanFactoryPostProcessor接口。它使用这些豆子作为豆子工厂 后处理器,在适当的时间。您可以将这些后处理器 Bean 部署为 你会喜欢任何其他豆子。spring-doc.cadn.net.cn

BeanPostProcessors 时,您通常不想配置BeanFactoryPostProcessors 用于延迟初始化。如果没有其他 bean 引用Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,并且Bean(Factory)PostProcessor即使您将default-lazy-init属性设置为true在声明您的<beans />元素。
示例:类名替换PropertySourcesPlaceholderConfigurer

您可以使用PropertySourcesPlaceholderConfigurer外部化属性值使用标准 Java 从单独文件中的 Bean 定义Properties格式。 这样做使部署应用程序的人员能够自定义特定于环境的属性,例如数据库 URL 和密码,而不会有修改容器的一个或多个主 XML 定义文件。spring-doc.cadn.net.cn

考虑以下基于 XML 的配置元数据片段,其中DataSourcewith 占位符值被定义:spring-doc.cadn.net.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>

该示例显示从外部Properties文件。在运行时, 一个PropertySourcesPlaceholderConfigurer应用于替换某些 DataSource 的属性。要替换的值被指定为 形式${property-name},它遵循 Ant 和 log4j 以及 JSP EL 风格。spring-doc.cadn.net.cn

实际值来自标准 Java 中的另一个文件Properties格式:spring-doc.cadn.net.cn

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

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

使用context命名空间,您可以配置属性占位符 具有专用配置元素。您可以将一个或多个位置作为 逗号分隔的列表location属性,如以下示例所示:spring-doc.cadn.net.cn

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

PropertySourcesPlaceholderConfigurer不仅在Properties您指定的文件。默认情况下,如果在指定的属性文件中找不到属性, 它针对 Spring 进行检查Environment属性和常规 JavaSystem性能。spring-doc.cadn.net.cn

您可以使用PropertySourcesPlaceholderConfigurer替换类名,其中 有时,当您必须在运行时选择特定的实现类时,它很有用。 以下示例显示了如何执行此作:spring-doc.cadn.net.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 的解析 在即将创建时失败,即在preInstantiateSingletons()阶段ApplicationContext对于非 lazy-init bean。spring-doc.cadn.net.cn

示例:该PropertyOverrideConfigurer

PropertyOverrideConfigurer,另一个 bean 工厂后处理器,类似于PropertySourcesPlaceholderConfigurer,但与后者不同的是,最初的定义 可以有 bean 属性的默认值或根本没有值。如果覆盖Propertiesfile 没有某个 bean 属性的条目,默认 上下文定义。spring-doc.cadn.net.cn

请注意,bean 定义不知道被覆盖,因此它没有 从 XML 定义文件中可以立即看出覆盖配置器正在被 使用。如果出现多个PropertyOverrideConfigurer定义不同 值,由于覆盖机制,最后一个优先。spring-doc.cadn.net.cn

属性文件配置文件行采用以下格式:spring-doc.cadn.net.cn

beanName.property=value

以下列表显示了格式的示例:spring-doc.cadn.net.cn

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

此示例文件可以与包含名为dataSource那有driverClassNameurl性能。spring-doc.cadn.net.cn

还支持复合属性名称,只要路径的每个组件 除了被覆盖的最终属性已经是非 null(大概是初始化的 由构造函数)。在以下示例中,sammy属性的bob属性的fred属性的tom豆 设置为标量值123:spring-doc.cadn.net.cn

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

使用context命名空间在 Spring 2.5 中引入,可以配置 属性覆盖为专用配置元素,如以下示例所示:spring-doc.cadn.net.cn

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

1.8.3. 使用FactoryBean

您可以实现org.springframework.beans.factory.FactoryBean接口 本身就是工厂。spring-doc.cadn.net.cn

FactoryBean接口是 Spring IoC 容器的 实例化逻辑。如果您有复杂的初始化代码,最好用 Java 而不是(可能)冗长的 XML,您可以创建自己的 XMLFactoryBean,在该类中编写复杂的初始化,然后将 习惯FactoryBean放入容器中。spring-doc.cadn.net.cn

FactoryBean<T>interface 提供了三种方法:spring-doc.cadn.net.cn

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

  • boolean isSingleton():返回true如果这FactoryBean返回单例或false否则。此方法的默认实现返回true.spring-doc.cadn.net.cn

  • Class<?> getObjectType():返回getObject()方法 或null如果事先不知道类型。spring-doc.cadn.net.cn

FactoryBean概念和接口在 Spring 中的许多地方都使用了 框架。超过 50 个实现FactoryBean与 Spring 一起发布的接口 本身。spring-doc.cadn.net.cn

当您需要向容器索取实际的FactoryBean实例本身,而不是 它产生的 bean,前缀 bean 的id当 & 符号 () 时 调用&getBean()方法ApplicationContext.所以,就给定而言FactoryBean使用idmyBean调用getBean("myBean")在容器上返回 的乘积FactoryBean,而调用getBean("&myBean")返回FactoryBean实例本身。spring-doc.cadn.net.cn

1.9. 基于注释的容器配置

对于配置 Spring,注释是否比 XML 更好?

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

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

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

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

与往常一样,您可以将后处理器注册为单独的 bean 定义,但它们 也可以通过在基于 XML 的 Spring 中包含以下标签来隐式注册 配置(请注意,包含context命名空间):spring-doc.cadn.net.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.cadn.net.cn

<context:annotation-config/>仅查找同一 定义它的应用程序上下文。这意味着,如果您将<context:annotation-config/>WebApplicationContext对于一个DispatcherServlet, 它只检查@Autowiredbean 在控制器中,而不是您的服务。请参阅 DispatcherServlet 以了解更多信息。spring-doc.cadn.net.cn

1.9.1. @Required

@RequiredCommentation 适用于 Bean 属性 setter 方法,如下所示 例:spring-doc.cadn.net.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
class SimpleMovieLister {

    @set:Required
    lateinit var movieFinder: MovieFinder

    // ...
}

此注释指示受影响的 bean 属性必须填充在 配置时间,通过 Bean 定义中的显式属性值或通过 自动接线。如果受影响的 bean 属性尚未 填充。这允许急切和显式失败,避免NullPointerException实例或类似的东西。我们仍然建议您将断言放入 bean 类本身(例如,进入 init 方法)。这样做会强制执行所需的 引用和值,即使您在容器外部使用类也是如此。spring-doc.cadn.net.cn

RequiredAnnotationBeanPostProcessor必须注册为 bean 才能启用对@Required注解。spring-doc.cadn.net.cn

@Requiredannotation 和RequiredAnnotationBeanPostProcessor正式 从 Spring Framework 5.1 开始被弃用,转而使用构造函数注入 必需设置(或InitializingBean.afterPropertiesSet()或自定义@PostConstruct方法以及 bean 属性 setter 方法)。spring-doc.cadn.net.cn

1.9.2. 使用@Autowired

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

您可以应用@Autowired注释到构造函数,如以下示例所示:spring-doc.cadn.net.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 开始,一个@Autowired此类构造函数上的注释不再 如果目标 bean 一开始只定义了一个构造函数,则必需。但是,如果 有多个构造函数可用,并且至少没有主/默认构造函数 其中一个构造函数必须使用@Autowired为了指示 容器使用哪一个。有关详细信息,请参阅有关构造函数解析的讨论。spring-doc.cadn.net.cn

您还可以应用@Autowired对传统setter方法进行注解, 如以下示例所示:spring-doc.cadn.net.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
Kotlin
class SimpleMovieLister {

    @set:Autowired
    lateinit var movieFinder: MovieFinder

    // ...

}

您还可以将注释应用于具有任意名称和多个 参数,如以下示例所示:spring-doc.cadn.net.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
    }

    // ...
}

您可以申请@Autowired添加到字段中,甚至可以将其与构造函数混合,如 以下示例显示:spring-doc.cadn.net.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

    // ...
}

确保您的目标组件(例如MovieCatalogCustomerPreferenceDao) 由您用于@Autowired-注释 注入点。否则,注入可能会因运行时出现“未找到类型匹配”错误而失败。spring-doc.cadn.net.cn

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

您还可以指示 Spring 从ApplicationContext通过添加@Autowired注释到字段或方法需要该类型的数组,如以下示例所示:spring-doc.cadn.net.cn

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalogs: Array<MovieCatalog>

    // ...
}

这同样适用于类型化集合,如以下示例所示:spring-doc.cadn.net.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 可以实现org.springframework.core.Ordered接口或使用 这@Order或标准@Priority注释,如果您希望数组或列表中的项目按特定顺序排序。否则,它们的顺序遵循注册容器中相应目标 bean 定义的顺序。spring-doc.cadn.net.cn

您可以声明@Order目标类级别的注释和@Bean方法 可能用于单个 Bean 定义(如果有多个定义 使用相同的 bean 类)。@Order值可能会影响进样点的优先级, 但请注意,它们不会影响单例启动顺序,这是一个 由依赖关系和@DependsOn声明。spring-doc.cadn.net.cn

请注意,标准javax.annotation.Priority注释在@Beanlevel,因为它不能在方法上声明。其语义可以建模 通过@Order值与@Primary在每种类型的单个 bean 上。spring-doc.cadn.net.cn

甚至打字Map只要预期的键类型为String. 映射值包含预期类型的所有 bean,键包含 相应的 bean 名称,如以下示例所示:spring-doc.cadn.net.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 没有匹配的可用时,自动装配将失败 注射点。对于声明的数组、集合或映射,至少一个 匹配元素。spring-doc.cadn.net.cn

默认行为是将带注释的方法和字段视为指示必需的 依赖。可以更改此行为,如以下示例所示, 使框架能够通过将不满足的注入点标记为 非必需的(即,通过将required属性@Autowiredfalse):spring-doc.cadn.net.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.cadn.net.cn

注入的构造函数和工厂方法参数是一种特例,因为required属性@Autowired由于 Spring 的构造函数,其含义有些不同 可能处理多个构造函数的解析算法。构造 函数 和工厂方法参数默认情况下是必需的,但有一些特殊的 单构造函数场景中的规则,例如多元素注入点(数组、 collections, maps)如果没有匹配的 bean 可用,则解析为空实例。这 允许一种通用的实现模式,其中所有依赖项都可以在 唯一的多参数构造函数 — 例如,声明为单个公共构造函数 没有@Autowired注解。spring-doc.cadn.net.cn

任何给定 bean 类的只有一个构造函数可以声明@Autowired使用required属性设置为true指示构造函数在用作弹簧时要自动连线 豆。因此,如果required属性保留为默认值true, 只能用@Autowired.如果多个构造函数 声明注释,它们都必须声明required=false为了成为 被视为自动布线的候选者(类似于autowire=constructor在 XML 中)。 具有最多依赖项的构造函数,可以通过匹配 将选择 Spring 容器中的 bean。如果没有一个候选人能满意, 然后将使用主/默认构造函数(如果存在)。同样,如果类 声明多个构造函数,但没有一个构造函数使用@Autowired,然后是 将使用主/默认构造函数(如果存在)。如果类仅声明单个 构造函数,即使没有注释,它也会始终被使用。请注意,一个 带注释的构造函数不必是公共的。spring-doc.cadn.net.cn

required属性@Autowired比已弃用的@Requiredsetter 方法的注释。设置required属性设置为false表示 自动布线目的不需要该属性,如果 无法自动接线。@Required另一方面,它更强大,因为它强制执行 属性,如果没有定义值, 引发相应的异常。spring-doc.cadn.net.cn

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

public class SimpleMovieLister {

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

从 Spring Framework 5.0 开始,您还可以使用@Nullable注释(任何类型的 在任何包中 — 例如,javax.annotation.Nullable来自 JSR-305)或只是利用 Kotlin 内置 null-safety 支持:spring-doc.cadn.net.cn

Java
public class SimpleMovieLister {

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

    @Autowired
    var movieFinder: MovieFinder? = null

    // ...
}

您还可以使用@Autowired对于众所周知的可解析接口 依赖:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisherMessageSource.这些接口及其扩展 接口,例如ConfigurableApplicationContextResourcePatternResolver是 自动解析,无需特殊设置。以下示例自动布线 一ApplicationContext对象:spring-doc.cadn.net.cn

Java
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var context: ApplicationContext

    // ...
}

@Autowired,@Inject,@Value@Resource注释由 Spring 处理BeanPostProcessor实现。这意味着您无法应用这些注释 在您自己的BeanPostProcessorBeanFactoryPostProcessor类型(如果有)。 这些类型必须使用 XML 或 Spring 显式“连接”。@Bean方法。spring-doc.cadn.net.cn

1.9.3. 微调基于注释的自动布线@Primary

由于按类型自动布线可能会导致多个候选者,因此通常需要 对选择过程有更多的控制权。实现此目的的一种方法是使用 Spring 的@Primary注解。@Primary表示应该给出一个特定的 bean 当多个 bean 是自动连接到单值的候选对象时的首选项 Dependency。如果候选者中恰好存在一个主 Bean,则它将成为 autowired 值。spring-doc.cadn.net.cn

请考虑以下定义firstMovieCatalog作为 主要MovieCatalog:spring-doc.cadn.net.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 { ... }

    // ...
}

使用上述配置,以下内容MovieRecommender使用firstMovieCatalog:spring-doc.cadn.net.cn

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

相应的 bean 定义如下:spring-doc.cadn.net.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. 使用限定符微调基于 Commentation 的自动配线

@Primary是按类型使用自动布线的有效方法,当一个 可以确定主要候选人。当您需要对选择过程进行更多控制时, 您可以使用 Spring 的@Qualifier注解。您可以关联限定符值 使用特定参数,缩小类型匹配集的范围,以便特定 bean 为每个参数选择。在最简单的情况下,这可以是一个简单的描述值,因为 如以下示例所示:spring-doc.cadn.net.cn

Java
public class MovieRecommender {

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

    // ...
}
Kotlin
class MovieRecommender {

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

    // ...
}

您还可以指定@Qualifier单个构造函数参数的注释或 方法参数,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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 带有main限定符值与构造函数参数连接,该参数 以相同的值限定。
2 带有action限定符值与构造函数参数连接,该参数 以相同的值限定。

对于回退匹配,bean 名称被视为默认限定符值。因此,你 可以使用idmain而不是嵌套限定符元素,前导 以相同的匹配结果。但是,虽然您可以使用此约定来引用 按名称划分的特定豆,@Autowired从根本上讲,是关于类型驱动的注入 可选的语义限定符。这意味着限定符值,即使带有 bean 名称 fallback,在类型匹配集中始终具有缩小的语义。他们没有 在语义上表达对唯一 bean 的引用id.好的限定符值是mainEMEApersistent,表示特定组件的特征 独立于 beanid,如果是匿名 bean,则可以自动生成 定义,例如前面示例中的定义。spring-doc.cadn.net.cn

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

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

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

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

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

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

@Autowired适用于字段、构造函数和多参数方法,允许 在参数级别通过限定符注释缩小范围。相比之下,@Resource仅支持具有单个参数的字段和 Bean 属性 setter 方法。 因此,如果您的注入目标是 构造函数或多参数方法。spring-doc.cadn.net.cn

您可以创建自己的自定义限定符注释。为此,请定义注释和 提供@Qualifier注释,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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 定义的信息。您可以添加<qualifier/>标签作为<bean/>标记,然后指定typevalue以匹配您的自定义限定符注释。该类型与 注解的完全限定类名。或者,如果没有风险,作为一种便利 存在冲突的名称,则可以使用短类名。以下示例 演示了这两种方法:spring-doc.cadn.net.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.cadn.net.cn

在某些情况下,使用不带值的注释可能就足够了。这可以是 当注释具有更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有可用的 Internet 连接时可以搜索。首先,定义 简单注解,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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 定义只需要一个限定符type,如以下示例所示:spring-doc.cadn.net.cn

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

您还可以定义自定义限定符注释,这些注释接受 添加或代替简单的value属性。如果多个属性值 然后在要自动连接的字段或参数上指定,Bean 定义必须匹配 所有这些属性值都被视为自动配线候选值。例如, 请考虑以下注释定义:spring-doc.cadn.net.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)

在这种情况下Format是一个枚举,定义如下:spring-doc.cadn.net.cn

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

要自动连接的字段使用自定义限定符进行注释,并包含值 对于这两个属性:genreformat,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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. 使用泛型作为自动装配限定符

除了@Qualifier注释,可以使用 Java 泛型类型 作为一种隐含的限定形式。例如,假设您有以下内容 配置:spring-doc.cadn.net.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 实现了一个泛型接口(即,Store<String>Store<Integer>),您可以@AutowireStore接口,泛型是 用作限定符,如以下示例所示:spring-doc.cadn.net.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

泛型限定符也适用于自动布线列表,Map实例和数组。这 以下示例自动连接泛型List:spring-doc.cadn.net.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是一个BeanFactoryPostProcessor允许您注册自己的自定义限定符 注释类型,即使它们没有用 Spring 的@Qualifier注解。 以下示例演示如何使用CustomAutowireConfigurer:spring-doc.cadn.net.cn

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

AutowireCandidateResolver通过以下方式确定自动配线候选者:spring-doc.cadn.net.cn

当多个 bean 符合自动连接候选项的条件时,“主”的确定是 如下所示:如果候选者中恰好有一个 bean 定义具有primary属性设置为true,则已选择。spring-doc.cadn.net.cn

1.9.7. 注入@Resource

Spring 还支持使用 JSR-250 进行注入@Resource注解 (javax.annotation.Resource) 在字段或 bean 属性 setter 方法上。 这是 Java EE 中的常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 中 端点。Spring 也支持 Spring 管理的对象的这种模式。spring-doc.cadn.net.cn

@Resource采用 name 属性。默认情况下,Spring 将该值解释为 要注入的 Bean 名称。换句话说,它遵循按名称语义, 如以下示例所示:spring-doc.cadn.net.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 属性名称。以下示例将有 bean 叫movieFinder注入到其 setter 方法中:spring-doc.cadn.net.cn

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    @set:Resource
    private lateinit var movieFinder: MovieFinder

}
注释提供的名称由ApplicationContext其中CommonAnnotationBeanPostProcessor是知道的。 如果将 Spring 的SimpleJndiBeanFactory明确地。但是,我们建议您依赖默认行为和 使用 Spring 的 JNDI 查找功能来保留间接级别。

在排他性情况下@Resource未指定显式名称的用法,以及类似的用法 自@Autowired,@Resource查找主类型匹配项,而不是特定的命名 Bean 并解析众所周知的可解析依赖项:该BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisherMessageSource接口。spring-doc.cadn.net.cn

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

Java
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

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

    @Resource
    private lateinit var customerPreferenceDao: CustomerPreferenceDao


    @Resource
    private lateinit var context: ApplicationContext (1)

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

1.9.8. 使用@Value

@Value通常用于注入外部化属性:spring-doc.cadn.net.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.cadn.net.cn

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

还有以下内容application.properties文件:spring-doc.cadn.net.cn

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于MovieCatalog价值。spring-doc.cadn.net.cn

Spring 提供了默认的宽松嵌入值解析器。它将尝试解决 属性值,如果无法解析,则属性名称(例如${catalog.name}) 将作为值注入。如果您想对不存在的保持严格控制 values,则应声明PropertySourcesPlaceholderConfigurerbean,如下所示 示例显示:spring-doc.cadn.net.cn

Java
@Configuration
public class AppConfig {

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

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

如果无法解析任何占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用类似${}setPlaceholderPrefix,setPlaceholderSuffixsetValueSeparator自定义 占位符。spring-doc.cadn.net.cn

Spring Boot 默认配置一个PropertySourcesPlaceholderConfigurerbean 那 将从application.propertiesapplication.yml文件。

Spring 提供的内置转换器支持允许简单的类型转换(到Integerint例如)自动处理。多个逗号分隔值可以是 自动转换为String数组,无需额外努力。spring-doc.cadn.net.cn

可以提供默认值,如下所示:spring-doc.cadn.net.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)

SpringBeanPostProcessor使用ConversionService在幕后处理 转换String@Value到目标类型。如果你想 为自己的自定义类型提供转换支持,您可以提供自己的ConversionServicebean 实例,如以下示例所示:spring-doc.cadn.net.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())
        }
    }
}

什么时候@Value包含一个SpEL表达该值将是动态的 在运行时计算,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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

CommonAnnotationBeanPostProcessor不仅识别@Resource注解 还有 JSR-250 生命周期注释:javax.annotation.PostConstructjavax.annotation.PreDestroy.在 Spring 2.5 中引入,对这些 Commentations 提供了初始化回销毁回调中描述的生命周期回调机制的替代方案。前提是CommonAnnotationBeanPostProcessor在 Spring 中注册ApplicationContext, 在生命周期的同一点调用带有其中一个注释的方法 作为相应的 Spring 生命周期接口方法或显式声明的回调 方法。在以下示例中,缓存在初始化时预填充,并且 破坏时清除:spring-doc.cadn.net.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.cadn.net.cn

喜欢@Resource@PostConstruct@PreDestroy注释类型是一部分 从 JDK 6 到 8 的标准 Java 库。然而,整个javax.annotationpackage 在 JDK 9 中与核心 Java 模块分离,并最终在 JDK 11.如果需要,将javax.annotation-api工件需要通过 Maven 获得 现在,只需像任何其他库一样添加到应用程序的类路径中。spring-doc.cadn.net.cn

1.10. 类路径扫描和托管组件

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

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

1.10.1.@Component和进一步的刻板印象注释

@Repository注释是任何类的标记,该类履行该角色或存储库的构造型(也称为数据访问对象或 DAO)。其中用途该标记是异常的自动转换,如异常转换中所述。spring-doc.cadn.net.cn

Spring 提供了进一步的构造型注释:@Component,@Service@Controller.@Component是任何 Spring 管理组件的通用构造型。@Repository,@Service@Controller@Component为 更具体的用例(分别在持久性、服务和表示层中)。因此,您可以使用@Component,但是,通过用@Repository,@Service@Controller相反,您的类更适合通过工具进行处理或将与方面相关联。例如,这些刻板印象注释是切入点的理想目标。@Repository,@Service@Controller还可以在 Spring Framework 的未来版本中携带额外的语义。因此,如果你是在使用@Component@Service对于您的服务层,@Service是 显然是更好的选择。同样,如前所述,@Repository已经支持作为持久层中自动异常转换的标记。spring-doc.cadn.net.cn

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

Spring 提供的许多注解都可以用作自己的代码中的元注解。元注解是可以应用于另一个注解的注解。例如,@Service前面提到的注释是用元注释的@Component,如以下示例所示:spring-doc.cadn.net.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.

您还可以组合元注释以创建“组合注释”。例如 这@RestController来自 Spring MVC 的注释由@Controller@ResponseBody.spring-doc.cadn.net.cn

此外,组合的注释可以选择从 meta 注释以允许自定义。当您 只想公开元注释属性的子集。例如,Spring 的@SessionScope注释将作用域名称硬编码为session但仍允许 自定义proxyMode.以下列表显示了SessionScope注解:spring-doc.cadn.net.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
)

然后,您可以使用@SessionScope而不声明proxyMode如下:spring-doc.cadn.net.cn

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

您还可以覆盖proxyMode,如以下示例所示:spring-doc.cadn.net.cn

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

有关更多详细信息,请参阅 Spring Annotation 编程模型 wiki 页面。spring-doc.cadn.net.cn

1.10.3. 自动检测类和注册 Bean 定义

Spring 可以自动检测定型类并注册相应的BeanDefinition实例与ApplicationContext.例如,以下两个类 有资格进行此类自动检测:spring-doc.cadn.net.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给你的@Configuration类,其中basePackages属性 是两个类的通用父包。(或者,您可以指定一个 逗号或分号或空格分隔的列表,其中包含每个类的父包。spring-doc.cadn.net.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.cadn.net.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>.

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

在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。 但是,请确保您的组件类导出在module-info描述 符。如果您希望 Spring 调用类的非公共成员,请将 确保它们已“打开”(也就是说,它们使用opens声明而不是exports声明module-info描述符)。spring-doc.cadn.net.cn

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

您可以禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor通过包含annotation-config属性 值为false.

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

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

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

注释(默认)spring-doc.cadn.net.cn

org.example.SomeAnnotationspring-doc.cadn.net.cn

在目标组件的类型级别存在或元存在的注释。spring-doc.cadn.net.cn

可分配的spring-doc.cadn.net.cn

org.example.SomeClassspring-doc.cadn.net.cn

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

方面Jspring-doc.cadn.net.cn

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

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

正则表达式spring-doc.cadn.net.cn

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

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

习惯spring-doc.cadn.net.cn

org.example.MyTypeFilterspring-doc.cadn.net.cn

的自定义实现org.springframework.core.type.TypeFilter接口。spring-doc.cadn.net.cn

以下示例显示了忽略所有@Repository附注 并改用“存根”存储库:spring-doc.cadn.net.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.cadn.net.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=false在 注释或通过提供use-default-filters="false"作为<component-scan/>元素。这有效地禁用了对类的自动检测 注释或元注释@Component,@Repository,@Service,@Controller,@RestController@Configuration.

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

Spring 组件还可以向容器提供 bean 定义元数据。你可以做 这与相同的@Bean用于定义 Bean 元数据的注释@Configuration带注释的类。以下示例显示了如何执行此作:spring-doc.cadn.net.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 组件,其doWork()方法。但是,它还贡献了一个具有工厂的 bean 定义 方法指的publicInstance().这@Bean注释标识 factory 方法和其他 bean 定义属性,例如通过 这@Qualifier注解。可以指定的其他方法级注释包括@Scope,@Lazy和自定义限定符注释。spring-doc.cadn.net.cn

除了用于组件初始化的作用外,您还可以将@Lazy注塑点上的注释标有@Autowired@Inject.在这种情况下, 它导致注入延迟分辨率代理。然而,这种代理方法 相当有限。用于复杂的懒惰交互,尤其是组合 对于可选依赖项,我们建议ObjectProvider<MyTargetBean>相反。

如前所述,支持自动连接字段和方法,并具有额外的 支持自动布线@Bean方法。以下示例显示了如何执行此作:spring-doc.cadn.net.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)
}

该示例自动连接String方法参数country设置为age另一个名为privateInstance.Spring Expression Language 元素 通过符号定义属性的值#{ <expression> }.为@Value注释,表达式解析器被预配置为在以下情况下查找 bean 名称 解析表达式文本。spring-doc.cadn.net.cn

从 Spring Framework 4.3 开始,您还可以声明类型为InjectionPoint(或其更具体的子类:DependencyDescriptor) 更改为 访问触发当前 Bean 创建的请求注入点。 请注意,这仅适用于 Bean 实例的实际创建,不适用于 注入现有实例。因此,此功能对于 原型范围的 bean。对于其他范围,工厂方法只会看到 触发在给定范围内创建新 Bean 实例的注入点 (例如,触发创建惰性单例 Bean 的依赖项)。 在这种情况下,您可以将提供的注入点元数据与语义联系在一起。 以下示例演示如何使用InjectionPoint:spring-doc.cadn.net.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}")
}

@Bean常规 Spring 组件中的方法的处理方式与其 弹簧内部的对应物@Configuration类。不同的是@Component类不会使用 CGLIB 增强来拦截方法和字段的调用。 CGLIB 代理是调用@Bean方法 在@Configurationclasses 创建对协作对象的 Bean 元数据引用。 此类方法不是使用正常的 Java 语义调用的,而是通过 container 的生命周期管理与代理,以提供 Spring 的常规生命周期管理和代理 bean,即使通过编程调用@Bean方法。 相反,在@Beanplain 中的方法@Componentclass 具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他 约束适用。spring-doc.cadn.net.cn

您可以声明@Bean方法static,允许在没有 将其包含的配置类创建为实例。这使得特别 定义后处理器 Bean(例如,类型BeanFactoryPostProcessorBeanPostProcessor),因为此类 bean 在容器中早期初始化 生命周期,并且应避免在该点触发配置的其他部分。spring-doc.cadn.net.cn

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

Java 语言的可见性@Bean方法不会对 Spring 容器中生成的 bean 定义。您可以自由声明您的 工厂方法,如您认为适合非@Configuration类,也用于静态 任何地方的方法。然而,常规@Bean方法@Configuration课程需要 可覆盖 — 也就是说,它们不得被声明为privatefinal.spring-doc.cadn.net.cn

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

最后,单个类可以容纳多个@Bean相同的方法 bean,作为多种工厂方法的排列,根据可用的情况使用 运行时的依赖关系。这与选择“最贪婪”的算法相同 构造函数或工厂方法:具有 在构建时选择最多数量的可满足依赖项, 类似于容器在多个@Autowired构造 函数。spring-doc.cadn.net.cn

1.10.6. 命名自动检测的组件

当组件在扫描过程中自动检测时,其 bean 名称为 由BeanNameGenerator该扫描仪已知的策略。默认情况下,任何 弹簧构造型注释 (@Component,@Repository,@Service@Controller) 包含名称value从而将该名称提供给 相应的 bean 定义。spring-doc.cadn.net.cn

如果此类注释不包含名称value或对于任何其他检测到的组件(例如自定义过滤器发现的组件),默认 Bean 名称生成器返回不大写的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieListermovieFinderImpl:spring-doc.cadn.net.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-naming 策略,则可以提供自定义的bean-naming 策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描仪时提供完全限定的类名,如以下示例注释和 bean 定义所示。spring-doc.cadn.net.cn

如果您由于多个自动检测到的组件具有相同的非限定类名(即具有相同名称但驻留在不同包中的类)而遇到命名冲突,您可能需要配置一个BeanNameGenerator默认为完全限定的类名。从 Spring Framework 5.2.3 开始,FullyQualifiedAnnotationBeanNameGenerator位于包中org.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>

作为一般规则,每当其他组件可能显式引用它时,请考虑使用注释指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。spring-doc.cadn.net.cn

1.10.7. 为自动检测的组件提供作用域

与一般的 Spring 托管组件一样,默认和最常见的范围autodetected 组件是singleton. 但是,有时您需要一个不同的范围这可以通过@Scope注解。您可以提供 范围,如以下示例所示:spring-doc.cadn.net.cn

Java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
@Scope注释仅在具体 bean 类(对于 Commentted components)或工厂方法(对于@Bean方法)。与 XML bean 相比 定义,没有 bean 定义继承和继承的概念 类级别的层次结构与元数据目的无关。

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

要提供用于范围解析的自定义策略,而不是依赖基于注释的方法,您可以实现ScopeMetadataResolver接口。 请务必包含默认的无参数构造函数。然后,您可以在配置扫描仪时提供完全限定的类名,如以下示例注释和 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>

当使用某些非单例作用域时,可能需要为作用域对象生成代理。推理在作用域 Bean 作为依赖项中进行了描述。为此,组件扫描上提供了作用域代理属性 元素。 三个可能的值是:no,interfacestargetClass. 例如 以下配置将产生标准 JDK 动态代理:spring-doc.cadn.net.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. 提供带有注释的限定符元数据

@Qualifier注释在使用限定符微调基于注释的自动连接中进行了讨论。该部分中的示例演示了@Qualifierannotation 和自定义限定符注释,以便在解析自动连线时提供细粒度控制 候选人。 因为这些示例是基于 XML Bean 定义的,所以限定符metadata 是通过使用qualifiermeta子元素的bean元素。当依赖类路径扫描自动检测组件时,您可以为限定符元数据提供类型级候选类上的注释。以下三个示例演示了这一点 技术:spring-doc.cadn.net.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 {
    // ...
}
与大多数基于注释的替代方案一样,请记住,注释元数据是 绑定到类定义本身,而使用 XML 允许多个 bean 提供其限定符元数据的变体,因为 元数据是按实例而不是按类提供的。

1.10.9. 生成候选组件的索引

虽然类路径扫描速度非常快,但可以提高启动性能 通过在编译时创建静态候选列表来处理大型应用程序。在这个 模式下,所有组件扫描目标的模块都必须使用该机制。spring-doc.cadn.net.cn

您现有的@ComponentScan<context:component-scan/>指令必须保留 unchanged 以请求上下文扫描某些包中的候选者。当ApplicationContext检测到此类索引时,它会自动使用它而不是扫描 类路径。

要生成索引,请向包含 组件扫描指令的目标。以下示例显示 如何使用 Maven 做到这一点:spring-doc.cadn.net.cn

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

在 Gradle 4.5 及更早版本中,应在compileOnly配置,如以下示例所示:spring-doc.cadn.net.cn

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

在 Gradle 4.6 及更高版本中,依赖项应在annotationProcessor配置,如以下示例所示:spring-doc.cadn.net.cn

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

spring-context-indexer工件生成一个META-INF/spring.components文件 包含在 jar 文件中。spring-doc.cadn.net.cn

在 IDE 中使用此模式时,spring-context-indexer必须是 注册为注释处理器,以确保索引在以下情况下是最新的 候选组件已更新。
META-INF/spring.components找到文件 在类路径上。如果索引部分可用于某些库(或用例) 但无法为整个应用程序构建,您可以回退到常规类路径 排列(就好像根本不存在索引一样)通过设置spring.index.ignoretrue,作为 JVM 系统属性或通过SpringProperties机制。

1.11. 使用 JSR 330 标准注释

从 Spring 3.0 开始,Spring 提供了对 JSR-330 标准注解的支持(依赖注入)。这些注解的扫描方式与 Spring 附注。 要使用它们,您需要在类路径中具有相关的 jar。spring-doc.cadn.net.cn

如果您使用 Maven,则javax.injectartifact 在标准 Maven存储库 ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/) 中可用。您可以将以下依赖项添加到文件pom.xml:spring-doc.cadn.net.cn

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

1.11.1. 使用@Inject@Named

而不是@Autowired,您可以使用@javax.inject.Inject如下:spring-doc.cadn.net.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(...)
        // ...
    }
}

@Autowired,您可以使用@Inject在字段级别、方法级别和构造函数参数级别。此外,您可以将注入点声明为Provider,允许按需访问范围较短的 Bean 或延迟访问其他 Bean 通过Provider.get()叫。 以下示例提供了前面示例的变体:spring-doc.cadn.net.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: Provider<MovieFinder>


    fun listMovies() {
        movieFinder.get().findMovies(...)
        // ...
    }
}

如果您想为应注入的依赖项使用限定名称,您应该使用@Named注释,如以下示例所示:spring-doc.cadn.net.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
    }

    // ...
}

@Autowired,@Inject也可以与java.util.Optional@Nullable. 这在这里更适用,因为@Inject没有 一个required属性。 以下一对示例演示如何使用@Inject@Nullable:spring-doc.cadn.net.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.ManagedBean, 如以下示例所示:spring-doc.cadn.net.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而不指定组件的名称。@Named可以以类似的方式使用,如以下示例所示:spring-doc.cadn.net.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

    // ...
}

当您使用@Named@ManagedBean,您可以在 与使用 Spring 注释时的方式完全相同,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
@Component、JSR-330@Named和 JSR-250@ManagedBean注释不可组合。您应该使用 Spring 的刻板模型来构建 自定义组件注释。

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

当您使用标准注释时,您应该知道一些重要的 功能不可用,如下表所示:spring-doc.cadn.net.cn

表 6.弹簧组件模型元素与 JSR-330 变体
Spring javax.inject 中。 javax.inject 限制/注释

@Autowiredspring-doc.cadn.net.cn

@Injectspring-doc.cadn.net.cn

@Inject没有“required”属性。可以与 Java 8 一起使用Optional相反。spring-doc.cadn.net.cn

@Componentspring-doc.cadn.net.cn

@Named / @ManagedBeanspring-doc.cadn.net.cn

JSR-330 不提供可组合模型,仅提供一种识别命名组件的方法。spring-doc.cadn.net.cn

@Scope(“单例”)spring-doc.cadn.net.cn

@Singletonspring-doc.cadn.net.cn

JSR-330 默认范围类似于 Spring 的prototype.然而,为了保持它 与 Spring 的一般默认值一致,在 Spring 中声明的 JSR-330 bean container 是一个singleton默认情况下。为了使用singleton, 您应该使用 Spring 的@Scope注解。javax.inject还提供了@Scope注释。 然而,这个仅用于创建您自己的注释。spring-doc.cadn.net.cn

@Qualifierspring-doc.cadn.net.cn

@Qualifier / @Namedspring-doc.cadn.net.cn

javax.inject.Qualifier只是用于构建自定义限定符的元注释。 混凝土String限定符(如 Spring 的@Qualifier与值)相关联 通过javax.inject.Named.spring-doc.cadn.net.cn

@Valuespring-doc.cadn.net.cn

-spring-doc.cadn.net.cn

没有等效的spring-doc.cadn.net.cn

@Requiredspring-doc.cadn.net.cn

-spring-doc.cadn.net.cn

没有等效的spring-doc.cadn.net.cn

@Lazyspring-doc.cadn.net.cn

-spring-doc.cadn.net.cn

没有等效的spring-doc.cadn.net.cn

对象工厂spring-doc.cadn.net.cn

提供商spring-doc.cadn.net.cn

javax.inject.Provider是 Spring 的ObjectFactory, 只有较短的get()方法名称。它也可以与 Spring的@Autowired或使用未带注释的构造函数和 setter 方法。spring-doc.cadn.net.cn

1.12. 基于Java的容器配置

本节介绍如何使用 Java 代码中的注释来配置 Spring 容器。它包括以下主题:spring-doc.cadn.net.cn

1.12.1. 基本概念:@Bean@Configuration

Spring 的新 Java 配置支持中的核心工件是@Configuration-annotated 类和@Bean-annotated 方法。spring-doc.cadn.net.cn

@Bean注释用于指示方法实例化、配置和 初始化一个要由 Spring IoC 容器管理的新对象。对于那些熟悉的人 与 Spring 的<beans/>XML 配置,则@Bean注释的作用与 这<bean/>元素。您可以使用@Bean-annotated 方法与任何 Spring@Component.但是,它们最常与@Configuration豆。spring-doc.cadn.net.cn

使用@Configuration表示其主要用途是作为 bean 定义的来源。此外@Configuration类让 bean 间 依赖关系通过调用 other@Bean方法。 最简单的@Configuration类内容如下:spring-doc.cadn.net.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public MyServiceImpl myService() {
        return new MyServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun myService(): MyServiceImpl {
        return MyServiceImpl()
    }
}

前面的AppConfigclass 相当于下一个 Spring<beans/>XML:spring-doc.cadn.net.cn

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
全@Configuration与“精简版”@Bean模式?

什么时候@Bean方法在未用@Configuration,它们被称为以“精简”模式处理。Bean 方法 在@Component甚至在普通的旧类中都被认为是“精简版”, 包含类的主要用途不同,并且@Bean方法 在那里是一种奖励。例如,服务组件可能会公开管理视图 通过额外的@Bean方法。 在这种情况下,@Bean方法是一种通用的工厂方法机制。spring-doc.cadn.net.cn

与满@Configuration建兴@Bean方法不能声明 bean 间依赖关系。 相反,它们对包含组件的内部状态进行作,并且可以选择在 他们可能声明的论点。这样的@Bean因此,方法不应调用其他@Bean方法。每个这样的方法实际上只是特定 bean 引用,没有任何特殊的运行时语义。这里的积极副作用是 在运行时无需应用 CGLIB 子类化,因此 类设计术语(即包含类可以是final等等)。spring-doc.cadn.net.cn

在常见场景中,@Bean方法应在@Configuration类 确保始终使用“完整”模式,因此交叉方法引用 重定向到容器的生命周期管理。这可以防止相同的@Bean方法不会通过常规 Java 调用意外调用,这有助于 以减少在“精简版”模式下作时难以追踪的细微错误。spring-doc.cadn.net.cn

@Bean@Configuration以下部分将深入讨论注释。 然而,首先,我们介绍了通过使用 基于 Java 的配置。spring-doc.cadn.net.cn

1.12.2. 使用AnnotationConfigApplicationContext

以下部分记录了 Spring 的AnnotationConfigApplicationContext,在 Spring 中引入 3.0. 这个多功能的ApplicationContext实施不仅能够接受@Configuration类作为输入,但也是普通的@Component类和类 用 JSR-330 元数据进行注释。spring-doc.cadn.net.cn

什么时候@Configuration类作为输入提供,则@Configuration类本身 被注册为 bean 定义,并且所有@Bean类中的方法 也注册为 bean 定义。spring-doc.cadn.net.cn

什么时候@Component和 JSR-330 类,它们被注册为 bean 定义,并且假定 DI 元数据(例如@Autowired@Inject是 必要时在这些类中使用。spring-doc.cadn.net.cn

结构简单

与实例化ClassPathXmlApplicationContext,您可以使用@Configuration类作为输入 实例化AnnotationConfigApplicationContext.这完全允许 Spring 容器的无 XML 用法,如以下示例所示:spring-doc.cadn.net.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()
}

如前所述,AnnotationConfigApplicationContext不仅限于工作 跟@Configuration类。任何@Component或可以提供 JSR-330 注释类 作为构造函数的输入,如以下示例所示:spring-doc.cadn.net.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()
}

前面的示例假设MyServiceImpl,Dependency1Dependency2使用 Spring 依赖注入注解,例如@Autowired.spring-doc.cadn.net.cn

使用register(Class<?>…​)

您可以实例化AnnotationConfigApplicationContext通过使用无参数构造函数 ,然后使用register()方法。这种方法特别有用 以编程方式构建AnnotationConfigApplicationContext.以下内容 示例显示了如何执行此作:spring-doc.cadn.net.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()
}
启用元件扫描scan(String…​)

要启用组件扫描,您可以注释@Configuration类如下:spring-doc.cadn.net.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.cadn.net.cn

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

在前面的示例中,com.acme扫描包以查找任何@Component-带注释的类,这些类被注册为 Spring bean 容器内的定义。AnnotationConfigApplicationContext公开scan(String…​)方法允许相同的组件扫描功能,如 以下示例显示:spring-doc.cadn.net.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>()
}
记住@Configuration类的元注释@Component,因此它们是组件扫描的候选者。在前面的示例中, 假设AppConfigcom.acme包(或任何包 ),在调用scan().后refresh(),其所有@Bean方法在容器中被处理并注册为 bean 定义。
支持 Web 应用程序AnnotationConfigWebApplicationContext

一个WebApplicationContext的变体AnnotationConfigApplicationContext可用 跟AnnotationConfigWebApplicationContext.您可以在以下情况下使用此实现 配置 SpringContextLoaderListenerservlet 监听器,Spring MVCDispatcherServlet,依此类推。以下内容web.xml代码段配置一个典型的 Spring MVC Web 应用程序(请注意使用contextClasscontext-param 和 init-param):spring-doc.cadn.net.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>
对于编程用例,一个GenericWebApplicationContext可以用作 替代AnnotationConfigWebApplicationContext.请参阅GenericWebApplicationContextjavadoc 了解详情。

1.12.3. 使用@Bean注解

@Bean是方法级注释,是 XML 的直接模拟<bean/>元素。 注释支持<bean/>如:spring-doc.cadn.net.cn

您可以使用@Bean注释@Configuration-注释或在@Component-annotated 类。spring-doc.cadn.net.cn

声明 Bean

要声明 bean,您可以使用@Bean注解。你用这个 在ApplicationContext的类型 指定为方法的返回值。默认情况下,bean 名称与 方法名称。以下示例显示了@Bean方法声明:spring-doc.cadn.net.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.cadn.net.cn

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都使一个名为transferServiceApplicationContext,绑定到类型为TransferServiceImpl,作为 以下文字图片显示:spring-doc.cadn.net.cn

transferService -> com.acme.TransferServiceImpl

您还可以使用默认方法来定义 bean。这允许豆子的组成 通过在默认方法上实现具有 bean 定义的接口来进行配置。spring-doc.cadn.net.cn

Java
public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}

您还可以声明您的@Bean方法与接口(或基类) 返回类型,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}

但是,这会将高级类型预测的可见性限制为指定的 接口类型 (TransferService).然后,使用完整类型 (TransferServiceImpl) 只有在实例化受影响的单例 Bean 后,容器才知道。 非惰性单例 Bean 根据其声明顺序进行实例化, 因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间 尝试通过非声明类型(例如@Autowired TransferServiceImpl, 它仅解析一次transferServicebean 已被实例化)。spring-doc.cadn.net.cn

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

一个@Bean-annotated 方法可以有任意数量的参数来描述 构建该 bean 所需的依赖项。例如,如果我们的TransferService需要一个AccountRepository,我们可以使用一个方法实现这种依赖关系 参数,如以下示例所示:spring-doc.cadn.net.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.cadn.net.cn

接收生命周期回传

使用@Bean注解支持常规生命周期回调 并且可以使用@PostConstruct@PreDestroyJSR-250 的注释。有关进一步的信息,请参阅 JSR-250 注释 详。spring-doc.cadn.net.cn

常规的 Spring 生命周期回调完全支持 井。如果 bean 实现InitializingBean,DisposableBeanLifecycle他们 容器调用相应的方法。spring-doc.cadn.net.cn

标准集*Aware接口(例如 BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware 等)也完全受支持。spring-doc.cadn.net.cn

@Bean注解支持指定任意初始化和销毁 回调方法,很像 Spring XML 的init-methoddestroy-method属性 在bean元素,如以下示例所示:spring-doc.cadn.net.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 配置定义的 bean 具有 publiccloseshutdown方法会自动注册销毁回调。如果您有公共closeshutdown方法,并且您不希望在容器 关闭时,您可以添加@Bean(destroyMethod="")添加到您的 Bean 定义以禁用 违约(inferred)模式。spring-doc.cadn.net.cn

默认情况下,您可能希望对使用 JNDI 获取的资源执行此作,因为它的 生命周期在应用程序外部进行管理。特别是,确保始终这样做 对于一个DataSource,因为众所周知,它在 Java EE 应用程序服务器上存在问题。spring-doc.cadn.net.cn

以下示例显示了如何防止DataSource:spring-doc.cadn.net.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
}

此外,与@Bean方法,您通常使用编程 JNDI 查找,通过 使用 Spring 的JndiTemplateJndiLocatorDelegate辅助程序或直接 JNDIInitialContext用法,但不是JndiObjectFactoryBean变体(这将强制 您将返回类型声明为FactoryBeantype 而不是实际目标 类型,使其更难用于其他@Bean方法 打算在此处参考提供的资源)。spring-doc.cadn.net.cn

在以下情况下BeanOne从前面注释的示例中,调用init()方法,如下例所示:spring-doc.cadn.net.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 包括@Scope注释,以便您可以指定 bean 的作用域。spring-doc.cadn.net.cn

使用@Scope注解

您可以指定使用@Bean注释应该有一个 具体范围。您可以使用 Bean 作用域部分中指定的任何标准作用域。spring-doc.cadn.net.cn

默认范围为singleton,但您可以使用@Scope注解 如以下示例所示:spring-doc.cadn.net.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 配置时,这样的代理是<aop:scoped-proxy/>元素。 使用@Scope注释提供等效支持 使用proxyMode属性。默认值为ScopedProxyMode.DEFAULT哪 通常表示不应创建任何作用域代理,除非有不同的默认值 已在组件扫描指令级别进行配置。您可以指定ScopedProxyMode.TARGET_CLASS,ScopedProxyMode.INTERFACESScopedProxyMode.NO.spring-doc.cadn.net.cn

如果您将作用域代理示例从 XML 参考文档(请参阅作用域代理)移植到我们的@Bean使用 Java, 它类似于以下内容:spring-doc.cadn.net.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方法的名称作为 生成的 bean。但是,可以使用name属性 如以下示例所示:spring-doc.cadn.net.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 别名。这name属性的@Beanannotation 为此目的接受 String 数组。以下示例演示如何设置 Bean 的多个别名:spring-doc.cadn.net.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.cadn.net.cn

要将描述添加到@Bean,您可以使用@Description注释,如以下示例所示:spring-doc.cadn.net.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 定义。@Configuration类通过@Bean-注释 方法。调用@Bean方法@Configuration类也可用于定义 bean 间依赖关系。看基本概念:@Bean@Configuration进行一般介绍。spring-doc.cadn.net.cn

注入 Bean 间依赖关系

当 bean 彼此依赖时,表达这种依赖关系就像 让一个 bean 方法调用另一个 bean 方法,如以下示例所示:spring-doc.cadn.net.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()
}

在前面的示例中,beanOne接收对beanTwo通过构造函数 注射。spring-doc.cadn.net.cn

这种声明 Bean 间依赖关系的方法仅在@Bean方法 在@Configuration类。您不能声明 Bean 间依赖关系 通过使用 plain@Component类。
查找方法注入

如前所述,查找方法注入是一个 您应该很少使用的高级功能。在以下情况下,它很有用 单例范围的 Bean 依赖于原型范围的 Bean。为此使用 Java 配置类型为实现此模式提供了一种自然的方法。这 以下示例显示如何使用查找方法注入:spring-doc.cadn.net.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 配置,您可以创建CommandManager哪里 摘要createCommand()方法被覆盖,以便它查找新的 (prototype) 命令对象。以下示例显示了如何执行此作:spring-doc.cadn.net.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 的配置如何在内部工作的更多信息

请考虑以下示例,其中显示了@Beanannotated 方法被调用两次:spring-doc.cadn.net.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()已调用一次clientService1()一旦进入clientService2(). 由于此方法创建了一个ClientDaoImpl并返回它,你会 通常期望有两个实例(每个服务一个)。那肯定是 problemacy:在 Spring 中,实例化的 bean 有一个singleton默认范围。这是 神奇之处:全部@Configuration类在启动时被子类化 跟CGLIB.在子类中,子方法首先检查容器中是否有任何 缓存(作用域)bean,然后调用父方法并创建一个新实例。spring-doc.cadn.net.cn

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

从 Spring 3.2 开始,不再需要将 CGLIB 添加到类路径中,因为 CGLIB 类已重新打包在org.springframework.cglib并直接包含 在弹簧核心 JAR 中。spring-doc.cadn.net.cn

由于 CGLIB 在 启动时间。特别是,配置类不得是最终的。但是,作为 4.3 中,配置类上允许使用任何构造函数,包括使用@Autowired或用于默认注入的单个非默认构造函数声明。spring-doc.cadn.net.cn

如果您希望避免 CGLIB 施加的任何限制,请考虑声明您的@Bean关于非@Configuration类(例如,在普通@Component类)。之间的跨方法调用@Bean方法不会被拦截,因此你有完全依赖于构造函数或方法级别的依赖注入。spring-doc.cadn.net.cn

1.12.5. 编写基于 Java 的配置

Spring 基于 Java 的配置功能允许您编写注释,这可以减少配置的复杂性。spring-doc.cadn.net.cn

使用@Import注解

就像<import/>元素在 Spring XML 文件中使用,以帮助模块化 配置,则@Import注释允许加载@Bean定义来自 另一个配置类,如以下示例所示:spring-doc.cadn.net.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.class什么时候 实例化上下文,仅ConfigB需要显式提供,因为 以下示例显示:spring-doc.cadn.net.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>()
}

这种方法简化了容器实例化,因为只需要处理一个类 与,而不是要求您记住潜在的大量@Configuration施工期间的课程。spring-doc.cadn.net.cn

从 Spring Framework 4.2 开始,@Import还支持对常规组件的引用 类,类似于AnnotationConfigApplicationContext.register方法。 如果您想通过使用一些 配置类作为入口点,以显式定义所有组件。
在导入的@Bean定义

前面的示例有效,但很简单。在大多数实际场景中,bean 具有 跨配置类相互依赖。使用 XML 时,这不是 issue,因为不涉及编译器,你可以声明ref="someBean"并相信 Spring 会在容器初始化期间解决问题。 使用时@Configuration类,Java 编译器会对 配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。spring-doc.cadn.net.cn

幸运的是,解决这个问题很简单。正如我们已经讨论过的, 一个@Bean方法可以具有任意数量的描述 bean 的参数 依赖。考虑以下更真实的场景,其中包含几个@Configuration类,每个类都取决于其他 bean 中声明的 bean:spring-doc.cadn.net.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")
}

还有另一种方法可以达到相同的结果。记住@Configuration类是 最终容器中只有另一个 bean:这意味着他们可以利用@Autowired@Value注射和其他功能与任何其他 Bean 相同。spring-doc.cadn.net.cn

确保以这种方式注入的依赖项仅属于最简单的类型。@Configuration类在上下文初始化期间很早就被处理,并强制依赖 以这种方式注入可能会导致意外的早期初始化。只要有可能,就求助于 基于参数的注入,如前面的示例所示。spring-doc.cadn.net.cn

避免访问@PostConstruct方法相同配置 类。这有效地导致了循环参考,因为非静态@Bean语义方法 需要调用完全初始化的配置类实例。带循环参考 不允许(例如在 Spring Boot 2.6+ 中),这可能会触发BeanCurrentlyInCreationException.spring-doc.cadn.net.cn

另外,要特别小心BeanPostProcessorBeanFactoryPostProcessor定义 通过@Bean.这些通常应该声明为static @Bean方法,而不是触发 实例化其包含的配置类。否则@Autowired@Value可能不会 处理配置类本身,因为可以在AutowiredAnnotationBeanPostProcessor.spring-doc.cadn.net.cn

以下示例显示了如何将一个 Bean 自动连接到另一个 Bean:spring-doc.cadn.net.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")
}
构造函数注入@Configurationclasses 仅从 Spring 开始受支持 框架 4.3.另请注意,无需指定@Autowired如果目标 bean 只定义了一个构造函数。
完全合格的导入 Bean,便于导航

在前面的场景中,使用@Autowired效果很好,提供了所需的 模块化,但确定自动连接 bean 定义的确切声明位置是 还是有些模棱两可。例如,作为开发人员,将ServiceConfig、如何 您确切地知道@Autowired AccountRepositorybean 被声明了吗?它不是 在代码中显式,这可能没问题。请记住,Spring Tools for Eclipse 提供了以下工具: 可以渲染显示所有内容如何连接的图表,这可能就是您所需要的。也 您的 Java IDE 可以轻松找到AccountRepository类型 并快速向您展示@Bean返回该类型的方法。spring-doc.cadn.net.cn

如果这种歧义是不可接受的,并且您希望直接导航 从您的 IDE 中从一个@Configuration类到另一个,请考虑自动连接 配置类本身。以下示例显示了如何执行此作:spring-doc.cadn.net.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())
    }
}

在上述情况下,其中AccountRepository定义是完全明确的。 然而ServiceConfig现在紧密耦合到RepositoryConfig.那就是 权衡。这种紧密耦合可以通过使用基于接口或 抽象类@Configuration类。请考虑以下示例:spring-doc.cadn.net.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")
}

现在ServiceConfig相对于混凝土松耦合DefaultRepositoryConfig,并且内置的 IDE 工具仍然有用:您可以轻松地 获取类型层次结构RepositoryConfig实现。在这个 方式, 导航@Configuration类及其依赖关系没有什么不同 比导航基于接口的代码的通常过程。spring-doc.cadn.net.cn

如果要影响某些 bean 的启动创建顺序,请考虑 将其中一些声明为@Lazy(用于首次访问时而不是启动时创建) 或作为@DependsOn某些其他 bean(确保特定的其他 bean 是 在当前 bean 之前创建,超出了后者的直接依赖关系所暗示的范围)。
有条件地包括@Configuration类或@Bean方法

有条件地启用或禁用完整的@Configuration类 甚至个人@Bean方法,基于一些任意系统状态。一种常见的 例如,使用@Profile注解仅在特定 profile 已在 Spring 中启用Environment(有关详细信息,请参阅 Bean 定义配置文件)。spring-doc.cadn.net.cn

@Profile注释实际上是通过使用更灵活的注释来实现的 叫@Conditional. 这@Conditional注释表示特定的org.springframework.context.annotation.Condition应该是 在咨询之前@Bean已注册。spring-doc.cadn.net.cn

的实现Condition接口提供matches(…​)返回truefalse.例如,以下列表显示了实际的Condition用于@Profile:spring-doc.cadn.net.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
}

请参阅@Conditionaljavadoc 了解更多详情。spring-doc.cadn.net.cn

结合 Java 和 XML 配置

Spring的@Configuration类支持的目标不是 100% 完全替代对于 Spring XML。一些工具,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,您有一个选择:要么以“以 XML 为中心”的方式实例化容器,例如,使用ClassPathXmlApplicationContext,或使用AnnotationConfigApplicationContext@ImportResource注解来导入 XML 根据需要。spring-doc.cadn.net.cn

以 XML 为中心的使用@Configuration

最好从 XML 引导 Spring 容器并包含@Configuration以临时方式进行类。例如,在大型现有代码库中 使用 Spring XML 的,创建起来更容易@Configuration类 根据需要,并从现有 XML 文件中包含它们。在本节后面,我们将介绍 使用选项@Configuration类在这种“以 XML 为中心”的情况下。spring-doc.cadn.net.cn

声明@Configuration类作为普通 Spring<bean/>元素

记住@Configuration类最终是 容器。在本系列示例中,我们创建了一个@Configuration名为AppConfig和 将其包含在system-test-config.xml作为<bean/>定义。因为<context:annotation-config/>打开时,容器会识别@Configuration注释并处理@BeanAppConfig适当地。spring-doc.cadn.net.cn

以下示例显示了 Java 中的普通配置类:spring-doc.cadn.net.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.xml文件:spring-doc.cadn.net.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.properties文件:spring-doc.cadn.net.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>()
    // ...
}
system-test-config.xml文件,则AppConfig <bean/>不声明id元素。虽然这样做是可以接受的,但考虑到没有其他 bean ever 引用它,并且不太可能按名称从容器中显式获取它。 同样,DataSourcebean 仅按类型自动连接,因此显式 beanid不是严格要求的。
使用 <context:component-scan/> 进行拾取@Configuration

因为@Configuration元注释为@Component,@Configuration-注释 类自动成为组件扫描的候选者。使用与 在前面的例子中描述,我们可以重新定义system-test-config.xml以利用组件扫描。 请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>因为<context:component-scan/>启用相同的 功能性。spring-doc.cadn.net.cn

以下示例显示了修改后的system-test-config.xml文件:spring-doc.cadn.net.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

在以下应用中@Configuration类是配置的主要机制 容器,可能仍然需要至少使用一些 XML。在这些 场景,您可以使用@ImportResource并仅定义您需要的 XML。行为 因此实现了“以 Java 为中心”的方法来配置容器,并将 XML 保持在 最低 限度。以下示例(包括一个配置类、一个 XML 文件 定义 bean、属性文件和main类)展示了如何使用 这@ImportResource注释,以实现使用 XML 的“以 Java 为中心”的配置 根据需要:spring-doc.cadn.net.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.cadn.net.cn

配置文件是一组命名的逻辑 Bean 定义,要向 仅当给定的配置文件处于活动状态时才容器。可以将 Bean 分配给配置文件 无论是在 XML 中定义还是使用注释定义。的作用Environment对象替换为 与配置文件的关系在于确定哪些配置文件(如果有)当前处于活动状态, 以及哪些配置文件(如果有)默认应处于活动状态。spring-doc.cadn.net.cn

属性在几乎所有应用中都起着重要作用,并且可能源自 多种来源:属性文件、JVM 系统属性、系统环境 变量, JNDI, servlet 上下文参数, 临时Properties对象Map对象,等等 上。的作用Environmentobject 与属性相关的是提供 用户具有方便的服务界面,用于配置属性源和解析 属性。spring-doc.cadn.net.cn

1.13.1. Bean 定义配置文件

Bean 定义配置文件在核心容器中提供了一种机制,允许 在不同环境中注册不同的 Bean。“环境”这个词, 对不同的用户来说可能意味着不同的事情,此功能可以帮助许多用户 用例,包括:spring-doc.cadn.net.cn

考虑实际应用程序中的第一个用例,它需要DataSource.在测试环境中,配置可能类似于以下内容:spring-doc.cadn.net.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 目录。我们dataSource豆 现在看起来像以下列表:spring-doc.cadn.net.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 用户设计了多种方法 完成此作,通常依赖于系统环境变量的组合 和 XML<import/>包含${placeholder}解析的Tokens 到正确的配置文件路径,具体取决于环境的值 变量。Bean 定义配置文件是一个核心容器功能,它提供了 解决这个问题。spring-doc.cadn.net.cn

如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中则不然。您可以说您想注册一个 情况 A 中 Bean 定义的某些配置文件和 情况 B.我们首先更新我们的配置以反映这种需求。spring-doc.cadn.net.cn

@Profile

@Profile注释允许您指示组件符合注册条件 当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们 可以重写dataSource配置如下:spring-doc.cadn.net.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
    }
}
如前所述,使用@Bean方法,您通常选择使用编程JNDI 查找,通过使用 Spring 的JndiTemplate/JndiLocatorDelegatehelpers 或直接 JNDIInitialContext用法,但未显示JndiObjectFactoryBeanvariant,这将强制您将返回类型声明为FactoryBean类型。

配置文件字符串可以包含一个简单的配置文件名称(例如,production) 或profile 表达式。profile 表达式允许将更复杂的配置文件逻辑表达式(例如,production & us-east). 以下运算符在profile 表达式中支持:spring-doc.cadn.net.cn

不能将 和&|运算符而不使用括号。 例如production & us-east | eu-central不是有效的表达式。它必须表示为production & (us-east | eu-central).

您可以使用@Profile作为元注释,用于创建自定义组合注释。以下示例定义了自定义@Production注释,可用作插入式替换@Profile("production"):spring-doc.cadn.net.cn

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
Kotlin
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果@Configurationclass 标记为@Profile,所有@Bean方法和@Import与该类关联的注释将被绕过,除非一个或多个 指定的配置文件处于活动状态。如果@Component@Configurationclass 被标记为 跟@Profile({"p1", "p2"}),除非 配置文件“P1”或“P2”已激活。如果给定配置文件前缀为 NOT 运算符 (!),仅当配置文件未注册时才会注册带注释的元素 积极。例如,给定@Profile({"p1", "!p2"}),如果配置文件 “p1”处于活动状态,或者配置文件“p2”未处于活动状态。

@Profile也可以在方法级别声明以仅包含一个特定的 bean 配置类的(例如,对于特定 bean 的替代变体),作为 以下示例显示:spring-doc.cadn.net.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 standaloneDataSource方法仅在development轮廓。
2 jndiDataSource方法仅在production轮廓。
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 standaloneDataSource方法仅在development轮廓。
2 jndiDataSource方法仅在production轮廓。

@Profile@Bean方法,可能适用特殊场景:在 重载@Bean相同 Java 方法名称的方法(类似于构造函数 overloading)、a@Profile条件需要在所有 重载方法。如果条件不一致,则只有 重载方法中的第一个声明很重要。因此@Profile能 不用于选择具有特定参数签名的重载方法 另一个。同一 bean 的所有工厂方法之间的解析遵循 Spring 的 创建时的构造函数解析算法。spring-doc.cadn.net.cn

如果要定义具有不同配置文件条件的替代 Bean, 使用指向相同 bean 名称的不同 Java 方法名称,方法是使用@Bean名字 属性,如前面的示例所示。如果参数签名全部 相同(例如,所有变体都有无参数工厂方法),这是唯一的 首先在有效的 Java 类中表示这种排列的方式 (因为特定名称和参数签名只能有一个方法)。spring-doc.cadn.net.cn

XML Bean 定义配置文件

XML 对应项是profile属性的<beans>元素。我们前面的示例 配置可以在两个 XML 文件中重写,如下所示:spring-doc.cadn.net.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.cadn.net.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>

spring-bean.xsd已限制为仅允许 文件中的最后一个。这应该有助于提供灵活性,而不会产生 XML 文件中的杂乱。spring-doc.cadn.net.cn

XML 对应项不支持前面描述的配置文件表达式。有可能, 但是,要使用!算子。也可以应用逻辑 “and”,如以下示例所示:spring-doc.cadn.net.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>

在前面的示例中,dataSource如果同时productionus-east配置文件处于活动状态。spring-doc.cadn.net.cn

激活配置文件

现在我们已经更新了配置,我们仍然需要指示 Spring 哪个 配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到 一个NoSuchBeanDefinitionException抛出,因为容器找不到 名为 Spring beandataSource.spring-doc.cadn.net.cn

可以通过多种方式激活配置文件,但最直接的是 它以编程方式针对EnvironmentAPI 可通过ApplicationContext.以下示例显示了如何执行此作:spring-doc.cadn.net.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()
}

此外,您还可以通过spring.profiles.active属性,可以通过系统环境指定 变量、JVM 系统属性、servlet 上下文参数web.xml,甚至作为 JNDI 中的条目(参见PropertySource抽象化).在集成测试中,活动 可以使用@ActiveProfiles注释中的spring-test模块(请参阅使用环境配置文件进行上下文配置)。spring-doc.cadn.net.cn

请注意,配置文件不是“非此即彼”的命题。您可以激活多个 配置文件。通过编程方式,您可以向setActiveProfiles()方法,该方法接受String…​varargs。以下示例 激活多个配置文件:spring-doc.cadn.net.cn

Java
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")

声明式,spring.profiles.active可以接受以逗号分隔的配置文件名称列表, 如以下示例所示:spring-doc.cadn.net.cn

    -Dspring.profiles.active="profile1,profile2"
默认配置文件

默认配置文件表示默认启用的配置文件。考虑一下 以下示例:spring-doc.cadn.net.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()
    }
}

如果没有处于活动状态的配置文件,则dataSource被创建。你可以看到这个 作为为一个或多个 bean 提供默认定义的一种方式。如果有 配置文件,则默认配置文件不适用。spring-doc.cadn.net.cn

您可以使用以下命令更改默认配置文件的名称setDefaultProfiles()上 这Environment或者,以声明方式,通过使用spring.profiles.default财产。spring-doc.cadn.net.cn

1.13.2.PropertySource抽象化

Spring的Environment抽象通过可配置的 属性源的层次结构。请考虑以下列表:spring-doc.cadn.net.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 是否my-property属性是 为当前环境定义。要回答这个问题,请Environment对象执行 搜索一组PropertySource对象。一个PropertySource是对任何键值对源的简单抽象,并且 Spring的StandardEnvironment配置了两个 PropertySource 对象 — 一个表示 JVM 系统属性集 (System.getProperties())和一个表示系统环境变量集 (System.getenv()).spring-doc.cadn.net.cn

这些默认属性源存在于StandardEnvironment,用于独立使用 应用。StandardServletEnvironment填充了其他默认属性源,包括 servlet config、servlet 上下文参数,以及JndiPropertySource如果 JNDI 可用。

具体来说,当您使用StandardEnvironment,调用env.containsProperty("my-property")如果my-property系统属性或my-property环境变量存在于 运行。spring-doc.cadn.net.cn

执行的搜索是分层的。默认情况下,系统属性优先于 环境变量。因此,如果my-property属性恰好在两个地方都设置了 对env.getProperty("my-property"),则系统属性值“获胜”并返回。 请注意,属性值不会合并 而是完全被前面的条目覆盖。spring-doc.cadn.net.cn

对于一个普通的StandardServletEnvironment,完整的层次结构如下,其中 顶部的最高优先级条目:spring-doc.cadn.net.cn

  1. ServletConfig 参数(如果适用 — 例如,如果DispatcherServlet上下文)spring-doc.cadn.net.cn

  2. ServletContext 参数(web.xml context-param 条目)spring-doc.cadn.net.cn

  3. JNDI 环境变量 (java:comp/env/条目)spring-doc.cadn.net.cn

  4. JVM 系统属性 (-D命令行参数)spring-doc.cadn.net.cn

  5. JVM 系统环境(作系统环境变量)spring-doc.cadn.net.cn

最重要的是,整个机制是可配置的。也许您有一个自定义源 要集成到此搜索中的属性。为此,请实现 并实例化您自己的PropertySource并将其添加到PropertySources对于 当前Environment.以下示例显示了如何执行此作:spring-doc.cadn.net.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())

在前面的代码中,MyPropertySource已以最高优先级添加到 搜索。如果它包含my-property属性,则该属性被检测并返回,有利于 任何my-property属性PropertySource.这MutablePropertySourcesAPI 公开了许多方法,允许精确作 属性来源。spring-doc.cadn.net.cn

1.13.3. 使用@PropertySource

@PropertySource注释提供了一种方便的声明性机制,用于添加PropertySource到 Spring 的Environment.spring-doc.cadn.net.cn

给定一个名为app.properties包含键值对testbean.name=myTestBean, 以下@Configuration类用途@PropertySource以这样一种方式 对testBean.getName()返回myTestBean:spring-doc.cadn.net.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")!!
    }
}

任何${…​}占位符@PropertySource资源位置是 针对已针对 环境,如以下示例所示:spring-doc.cadn.net.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")!!
    }
}

假设my.placeholder已存在于其中一个属性源中 已注册(例如,系统属性或环境变量),占位符为 解析为相应的值。如果没有,那么default/path被使用 作为默认设置。如果未指定默认值并且无法解析属性,则IllegalArgumentException被抛出。spring-doc.cadn.net.cn

@PropertySource根据 Java 8 约定,注释是可重复的。 然而,所有这些@PropertySource注释需要同时声明 级别,可以直接在配置类上,也可以作为 相同的自定义注释。混合直接注释和元注释不是 推荐,因为直接注释有效地覆盖了元注释。

1.13.4. 语句中的占位符解析

从历史上看,元素中占位符的值只能针对 JVM 系统属性或环境变量。现在情况已不再如此。因为 这Environment抽象集成在整个容器中,很容易 通过它路由占位符的解析。这意味着您可以配置 以您喜欢的任何方式解决过程。您可以更改搜索的优先级 系统属性和环境变量或完全删除它们。您还可以添加您的 根据需要,自己的财产来源混合在一起。spring-doc.cadn.net.cn

具体来说,无论customer属性,只要它在Environment:spring-doc.cadn.net.cn

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

1.14. 注册LoadTimeWeaver

LoadTimeWeaverSpring 使用它们来动态转换类 加载到 Java 虚拟机 (JVM) 中。spring-doc.cadn.net.cn

要启用加载时编织,您可以将@EnableLoadTimeWeaving给你的一个@Configuration类,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig

或者,对于 XML 配置,您可以使用context:load-time-weaver元素:spring-doc.cadn.net.cn

<beans>
    <context:load-time-weaver/>
</beans>

一旦为ApplicationContext,其中的任何 beanApplicationContext可以实现LoadTimeWeaverAware,从而接收对加载时间的引用 Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是 JPA 类转换所必需的。 请查阅LocalContainerEntityManagerFactoryBeanjavadoc 了解更多详情。有关 AspectJ 加载时编织的更多信息,请参阅 在 Spring Framework 中使用 AspectJ 进行加载时编织spring-doc.cadn.net.cn

1.15. 的附加功能ApplicationContext

正如本章介绍中所讨论的,该org.springframework.beans.factorypackage 提供了管理和作 bean 的基本功能,包括在 程序化方式。这org.springframework.contextpackage 添加ApplicationContext接口,它扩展了BeanFactory接口,除了扩展其他 接口,以便在更多应用程序中提供附加功能 面向框架的风格。许多人使用ApplicationContext在完全 声明式时尚,甚至不是以编程方式创建它,而是依赖于 支持类,例如ContextLoader自动实例化ApplicationContext作为 Java EE Web 应用程序正常启动过程的一部分。spring-doc.cadn.net.cn

增强BeanFactory功能以更面向框架的风格,上下文 package 还提供以下功能:spring-doc.cadn.net.cn

1.15.1. 使用MessageSource

ApplicationContextinterface 扩展了一个名为MessageSource和 因此,提供国际化(“i18n”)功能。Spring 还提供了HierarchicalMessageSource接口,可以分层解析消息。 这些接口共同为 Spring 效果消息提供了基础 分辨率。在这些接口上定义的方法包括:spring-doc.cadn.net.cn

  • String getMessage(String code, Object[] args, String default, Locale loc):基本 用于从MessageSource.找不到消息时 对于指定的区域设置,将使用默认消息。传入的任何参数都会变成 替换值,使用MessageFormat标准提供的功能 图书馆。spring-doc.cadn.net.cn

  • String getMessage(String code, Object[] args, Locale loc):本质上与 与前一种方法相同,但有一个区别:不能指定默认消息。如果 找不到消息,则NoSuchMessageException被抛出。spring-doc.cadn.net.cn

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):所有属性 在前面的方法中使用的也包装在名为MessageSourceResolvable,您可以将其与此方法一起使用。spring-doc.cadn.net.cn

ApplicationContext加载时,它会自动搜索MessageSourcebean 在上下文中定义。豆子必须有名称messageSource.如果这样的豆子 找到,则对上述方法的所有调用都委托给消息源。如果没有 消息源,则ApplicationContext尝试查找包含 同名的豆子。如果是这样,它会使用该 bean 作为MessageSource.如果ApplicationContext找不到消息的任何源,则为空DelegatingMessageSource被实例化,以便能够接受对 上面定义的方法。spring-doc.cadn.net.cn

Spring 提供了三个MessageSource实现ResourceBundleMessageSource,ReloadableResourceBundleMessageSourceStaticMessageSource.他们都实现了HierarchicalMessageSource为了做嵌套 消息。这StaticMessageSource很少使用,但提供了编程方式 将消息添加到源。以下示例显示ResourceBundleMessageSource:spring-doc.cadn.net.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>

该示例假设您有三个名为format,exceptionswindows在您的类路径中定义。任何解析消息的请求都是以 JDK 标准方式处理的,通过ResourceBundle对象。对于 本示例的目的是,假设上述两个资源包文件的内容 如下:spring-doc.cadn.net.cn

    # in format.properties
    message=Alligators rock!
    # in exceptions.properties
    argument.required=The {0} argument is required.

下一个示例显示了一个程序,用于运行MessageSource功能性。 请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口。spring-doc.cadn.net.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.cadn.net.cn

Alligators rock!

总而言之,MessageSource在名为beans.xml哪 存在于类路径的根目录中。这messageSourcebean 定义是指 通过其basenames财产。三个文件 将列表中传递给basenames属性作为文件存在于您的根目录下 classpath 并调用format.properties,exceptions.propertieswindows.properties分别。spring-doc.cadn.net.cn

下一个示例显示传递给消息查找的参数。这些论点是 转换为String对象并插入到查找消息中的占位符中。spring-doc.cadn.net.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.cadn.net.cn

The userDao argument is required.

关于国际化(“i18n”),Spring 的各种MessageSource实现遵循与标准 JDK 相同的区域设置解析和回退规则ResourceBundle.简而言之,继续这个例子messageSource定义 以前,如果您想解决针对英国(en-GB) locale,您 将创建名为format_en_GB.properties,exceptions_en_GB.propertieswindows_en_GB.properties分别。spring-doc.cadn.net.cn

通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 resolved 是手动指定的:spring-doc.cadn.net.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.cadn.net.cn

Ebagum lad, the 'userDao' argument is required, I say, required.

您还可以使用MessageSourceAware接口来获取对任何MessageSource这已经定义了。在ApplicationContext实现MessageSourceAware接口注入了 应用程序上下文的MessageSource创建和配置 Bean 时。spring-doc.cadn.net.cn

因为Spring的MessageSource基于 Java 的ResourceBundle,它不会合并 捆绑包,但仅使用找到的第一个捆绑包。 具有相同基本名称的后续消息包将被忽略。
作为替代方案ResourceBundleMessageSource,Spring 提供了一个ReloadableResourceBundleMessageSource类。此变体支持相同的捆绑包 文件格式,但比基于标准 JDK 的 JDK 更灵活ResourceBundleMessageSource实现。特别是,它允许阅读 来自任何 Spring 资源位置(不仅来自类路径)的文件,并支持热 重新加载捆绑属性文件(同时在两者之间有效地缓存它们)。 请参阅ReloadableResourceBundleMessageSourcejavadoc 了解详情。

1.15.2. 标准和自定义事件

事件处理ApplicationContext通过ApplicationEventclass 和ApplicationListener接口。如果实现ApplicationListener接口被部署到上下文中,每次ApplicationEvent发布到ApplicationContext,则通知该 bean。 从本质上讲,这是标准的 Observer 设计模式。spring-doc.cadn.net.cn

从 Spring 4.2 开始,活动基础设施得到了显着改进,并提供了 基于注释的模型以及 能够发布任何任意事件(即不一定 从ApplicationEvent).当这样的对象被发布时,我们将其包装在 为您举办的活动。

下表描述了 Spring 提供的标准事件:spring-doc.cadn.net.cn

表7.内置事件
事件 解释

ContextRefreshedEventspring-doc.cadn.net.cn

发布时ApplicationContext初始化或刷新(例如,通过 使用refresh()方法ConfigurableApplicationContext接口)。 这里,“初始化”意味着加载所有 bean,检测到后处理器 bean 和 activated,则单例被预先实例化,并且ApplicationContext对象是 准备使用。只要上下文尚未关闭,就可以触发刷新 多次,前提是选择的ApplicationContext其实支持这样的 “热”刷新。例如XmlWebApplicationContext支持热刷新,但GenericApplicationContext不。spring-doc.cadn.net.cn

ContextStartedEventspring-doc.cadn.net.cn

发布时ApplicationContext通过使用start()方法ConfigurableApplicationContext接口。这里,“started”意味着所有Lifecyclebean 接收显式启动信号。通常,此信号用于重新启动 bean 在显式停止后,但它也可用于启动尚未停止的组件 配置为 autostart(例如,尚未启动的组件 初始化)。spring-doc.cadn.net.cn

ContextStoppedEventspring-doc.cadn.net.cn

发布时ApplicationContext通过使用stop()方法ConfigurableApplicationContext接口。这里,“停止”意味着所有Lifecyclebean 接收显式停止信号。停止的上下文可以通过start()叫。spring-doc.cadn.net.cn

ContextClosedEventspring-doc.cadn.net.cn

发布时ApplicationContext正在使用close()方法 在ConfigurableApplicationContext接口或通过 JVM 关闭钩子。这里 “closed”表示所有单例 bean 都将被销毁。关闭上下文后, 它已达到其生命周期,无法刷新或重新启动。spring-doc.cadn.net.cn

RequestHandledEventspring-doc.cadn.net.cn

一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。这 请求完成后发布事件。此活动只适用于 使用 Spring 的DispatcherServlet.spring-doc.cadn.net.cn

ServletRequestHandledEventspring-doc.cadn.net.cn

的子类RequestHandledEvent添加特定于 Servlet 的上下文信息。spring-doc.cadn.net.cn

您还可以创建和发布自己的自定义事件。以下示例显示了 简单类,扩展 Spring 的ApplicationEvent基类:spring-doc.cadn.net.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)

发布自定义ApplicationEvent,调用publishEvent()方法ApplicationEventPublisher.通常,这是通过创建一个实现ApplicationEventPublisherAware并将其注册为 Spring bean。以下内容 示例显示了这样的类:spring-doc.cadn.net.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 容器检测到EmailService实现ApplicationEventPublisherAware并自动调用setApplicationEventPublisher().实际上,传入的参数是 Spring 容器本身。您正在通过应用程序上下文的ApplicationEventPublisher接口。spring-doc.cadn.net.cn

接收自定义ApplicationEvent,您可以创建一个类来实现ApplicationListener并将其注册为 Spring bean。以下示例 显示这样的类:spring-doc.cadn.net.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 notificationAddress: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意ApplicationListener通常参数化为 自定义事件 (BlockedListEvent在前面的示例中)。这意味着onApplicationEvent()方法可以保持类型安全,避免任何向下转换的需要。 您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,事件 侦听器同步接收事件。这意味着publishEvent()方法 块,直到所有侦听器都处理完事件。这样做的一个好处 同步和单线程方法是,当侦听器收到事件时,它 如果事务上下文是 可用。如果需要其他事件发布策略,请参阅 javadoc 对于Spring的ApplicationEventMulticaster接口 和SimpleApplicationEventMulticaster配置选项的实现。spring-doc.cadn.net.cn

以下示例显示了用于注册和配置每个 上述类:spring-doc.cadn.net.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>

将它们放在一起,当sendEmail()方法emailServicebean 是 如果有任何应阻止的电子邮件,则调用 a 自定义事件BlockedListEvent已发布。这blockedListNotifierbean 被注册为ApplicationListener并接收BlockedListEvent,此时它可以 通知有关各方。spring-doc.cadn.net.cn

Spring 的事件机制专为 Spring Bean 之间的简单通信而设计 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring Integration 项目提供了 完全支持构建轻量级、面向模式、事件驱动的构建 建立在众所周知的 Spring 编程模型之上的架构。
基于注释的事件侦听器

您可以使用@EventListener注解。这BlockedListNotifier可以重写如下:spring-doc.cadn.net.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...
    }
}

方法签名再次声明它侦听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的监听器接口。 只要实际事件类型,还可以通过泛型缩小事件类型 解析其实现层次结构中的泛型参数。spring-doc.cadn.net.cn

如果你的方法应该监听多个事件,或者如果你想用 参数,也可以在注解本身上指定事件类型。这 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
Kotlin
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}

还可以使用condition属性 定义SpEL表达,应匹配 实际调用特定事件的方法。spring-doc.cadn.net.cn

以下示例显示了如何重写通知程序,仅在content属性等于my-event:spring-doc.cadn.net.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...
}

SpELexpression 根据专用上下文进行计算。下表列出了 提供给上下文的项,以便您可以将它们用于条件事件处理:spring-doc.cadn.net.cn

表8.事件 SpEL 可用元数据
名称 位置 描述 示例

事件spring-doc.cadn.net.cn

root 对象spring-doc.cadn.net.cn

实际的ApplicationEvent.spring-doc.cadn.net.cn

#root.eventeventspring-doc.cadn.net.cn

参数数组spring-doc.cadn.net.cn

root 对象spring-doc.cadn.net.cn

用于调用该方法的参数(作为对象数组)。spring-doc.cadn.net.cn

#root.argsargs; args[0]访问第一个参数等。spring-doc.cadn.net.cn

参数名称spring-doc.cadn.net.cn

评估背景spring-doc.cadn.net.cn

任何方法参数的名称。如果由于某种原因,名称不可用 (例如,因为编译后的字节码中没有调试信息),单个 参数也可以使用#a<#arg>语法,其中<#arg>代表 参数索引(从 0 开始)。spring-doc.cadn.net.cn

#blEvent#a0(您也可以使用#p0#p<#arg>参数表示法作为别名)spring-doc.cadn.net.cn

请注意#root.event允许您访问基础事件,即使您的方法 签名实际上是指已发布的任意对象。spring-doc.cadn.net.cn

如果您需要在处理另一个事件后发布事件,您可以将 method signature 返回应发布的事件,如以下示例所示:spring-doc.cadn.net.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...
}
异步侦听器不支持此功能。

handleBlockedListEvent()方法发布一个新的ListUpdateEvent对于每个BlockedListEvent它处理的。如果需要发布多个事件,可以返回 一个Collection或事件数组。spring-doc.cadn.net.cn

异步侦听器

如果您希望特定监听器异步处理事件,您可以重用定期@Async支持. 以下示例显示了如何执行此作:spring-doc.cadn.net.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.cadn.net.cn

对侦听器进行排序

如果您需要在另一个侦听器之前调用一个侦听器,则可以将@OrderComments 添加到方法声明中,如以下示例所示:spring-doc.cadn.net.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...
}
通用事件

您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>哪里T是创建的实际实体的类型。例如,您 可以创建以下监听器定义以仅接收EntityCreatedEvent对于一个Person:spring-doc.cadn.net.cn

Java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Kotlin
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}

由于类型擦除,这仅在触发的事件解析泛型 事件侦听器过滤的参数(即,类似于class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }).spring-doc.cadn.net.cn

在某些情况下,如果所有事件都遵循相同的内容,这可能会变得非常乏味 结构(与前面示例中的事件一样)。在这种情况下, 您可以实现ResolvableTypeProvider引导框架超越运行时 环境提供。以下事件演示了如何执行此作:spring-doc.cadn.net.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 的Resource抽象,如参考资料中所述。spring-doc.cadn.net.cn

应用程序上下文是ResourceLoader,可用于加载Resource对象。 一个Resource本质上是 JDK 的功能更丰富的版本java.net.URL类。 事实上,的实现Resource包装java.net.URL哪里 适当。一个Resource几乎可以从任何位置获取低级资源 透明方式,包括从类路径、文件系统位置、任何地方 使用标准 URL 和一些其他变体进行描述。如果资源位置 string 是一个没有任何特殊前缀的简单路径,这些资源来自 is 特定且适合实际的应用程序上下文类型。spring-doc.cadn.net.cn

您可以配置部署到应用程序上下文中的 bean 以实现特殊的 回调接口,ResourceLoaderAware,自动回调 初始化时间,应用程序上下文本身作为ResourceLoader. 您还可以公开类型Resource,用于访问静态资源。 它们像任何其他属性一样被注入其中。您可以指定这些Resource属性为简单String路径,并依赖于从这些文本自动转换 字符串到实际Resource对象。spring-doc.cadn.net.cn

提供给ApplicationContext构造函数实际上是 资源字符串,并且以简单的形式根据特定的 上下文实现。例如ClassPathXmlApplicationContext对待简单的 location path 作为类路径位置。您还可以使用位置路径(资源字符串) 使用特殊前缀强制从类路径或 URL 加载定义, 无论实际上下文类型如何。spring-doc.cadn.net.cn

1.15.4. 应用程序启动跟踪

ApplicationContext管理 Spring 应用程序的生命周期,并提供丰富的 围绕组件的编程模型。因此,复杂的应用程序可以同样具有 复杂的组件图和启动阶段。spring-doc.cadn.net.cn

使用特定指标跟踪应用程序启动步骤有助于了解在哪里 在启动阶段花费了时间,但它也可以用作更好的一种方式 了解整个上下文生命周期。spring-doc.cadn.net.cn

AbstractApplicationContext(及其子类)使用ApplicationStartup,它收集StartupStep有关各个启动阶段的数据:spring-doc.cadn.net.cn

下面是AnnotationConfigApplicationContext:spring-doc.cadn.net.cn

Java
// create a startup step and start recording
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
Kotlin
// create a startup step and start recording
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages)
// end the current step
scanPackages.end()

应用程序上下文已通过多个步骤进行检测。 记录后,可以使用特定工具收集、显示和分析这些启动步骤。 有关现有启动步骤的完整列表,您可以查看专门的附录部分spring-doc.cadn.net.cn

默认值ApplicationStartup实现是一种无作变体,可最大限度地减少开销。 这意味着默认情况下,在应用程序启动期间不会收集任何指标。 Spring Framework 附带了一个用于使用 Java Flight Recorder 跟踪启动步骤的实现:FlightRecorderApplicationStartup.要使用此变体,您必须配置它的实例 到ApplicationContext一旦创建。spring-doc.cadn.net.cn

开发人员还可以使用ApplicationStartup基础设施(如果他们提供自己的)AbstractApplicationContext子类,或者如果他们希望收集更精确的数据。spring-doc.cadn.net.cn

ApplicationStartup仅在应用程序启动期间使用 核心容器;这绝不是 Java 分析器或 像 Micrometer 这样的指标库。

开始收集自定义StartupStep,组件可以获取ApplicationStartup实例直接从应用程序上下文中实现,使其组件实现ApplicationStartupAware, 或要求ApplicationStartup键入任何注射点。spring-doc.cadn.net.cn

开发人员不应使用"spring.*"命名空间。 此命名空间保留供内部 Spring 使用,可能会发生变化。

1.15.5. Web 应用程序的便捷 ApplicationContext 实例化

您可以创建ApplicationContext实例,例如,使用ContextLoader.当然,你也可以创建ApplicationContext实例 以编程方式使用ApplicationContext实现。spring-doc.cadn.net.cn

您可以注册一个ApplicationContext通过使用ContextLoaderListener,作为 以下示例显示:spring-doc.cadn.net.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>

监听器检查contextConfigLocation参数。如果参数没有 exist,则监听器使用/WEB-INF/applicationContext.xml作为默认设置。当 参数确实存在,监听器将String通过使用预定义的 分隔符(逗号、分号和空格),并将这些值用作其中的位置 搜索应用程序上下文。还支持 Ant 样式路径模式。 示例包括/WEB-INF/*Context.xml(对于名称以Context.xml并且驻留在WEB-INF目录)和/WEB-INF/**/*Context.xml(对于WEB-INF).spring-doc.cadn.net.cn

1.15.6. 部署 SpringApplicationContext作为 Java EE RAR 文件

可以部署 SpringApplicationContext作为 RAR 文件,将 上下文及其在 Java EE RAR 部署中所需的所有 bean 类和库 JAR 单位。这相当于引导一个独立的ApplicationContext(仅托管在 Java EE 环境中)能够访问 Java EE 服务器设施。RAR 部署是部署无头 WAR 文件场景的更自然的替代方案——实际上,一个没有任何 HTTP 入口点的 WAR 文件,仅用于引导 SpringApplicationContext在 Java EE 环境中。spring-doc.cadn.net.cn

RAR 部署非常适合不需要 HTTP 入口点但 相反,仅由消息端点和计划作业组成。在这样的上下文中,bean 可以 使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBCDataSource实例和 JMSConnectionFactory实例,也可以注册 平台的 JMX 服务器——全部通过 Spring 的标准事务管理和 JNDI 和 JMX 支持设施。应用程序组件也可以与应用程序交互 服务器的 JCAWorkManager通过 Spring 的TaskExecutor抽象化。spring-doc.cadn.net.cn

请参阅SpringContextResourceAdapterclass 用于 RAR 部署中涉及的配置详细信息。spring-doc.cadn.net.cn

对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:spring-doc.cadn.net.cn

  1. 包 所有应用程序类都转换为 RAR 文件(这是一个标准 JAR 文件,具有不同的 文件扩展名)。spring-doc.cadn.net.cn

  2. 将所有必需的库 JAR 添加到 RAR 存档的根目录中。spring-doc.cadn.net.cn

  3. 添加一个META-INF/ra.xml部署描述符(如javadoc 的SpringContextResourceAdapter) 和相应的 Spring XML bean 定义文件(通常META-INF/applicationContext.xml).spring-doc.cadn.net.cn

  4. 将生成的 RAR 文件放入您的 应用程序服务器的部署目录。spring-doc.cadn.net.cn

这种 RAR 部署单元通常是独立的。它们不会暴露组件 对外界,甚至对同一应用程序的其他模块也不行。与 基于 RARApplicationContext通常通过与其共享的 JMS 目标发生 其他模块。基于 RAR 的ApplicationContext例如,还可以安排一些作业 或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步 从外部访问,它可以(例如)导出 RMI 端点,可以使用 由同一台机器上的其他应用程序模块。

1.16.BeanFactory应用程序接口

BeanFactoryAPI 为 Spring 的 IoC 功能提供了底层基础。 其具体合约主要用于与 Spring 的其他部分集成,以及 相关的第三方框架及其DefaultListableBeanFactory实现 是上级的关键代表GenericApplicationContext容器。spring-doc.cadn.net.cn

BeanFactory和相关接口(例如BeanFactoryAware,InitializingBean,DisposableBean)是其他框架组件的重要集成点。 由于不需要任何注释甚至反射,它们可以非常高效地实现 容器与其组件之间的交互。应用程序级 Bean 可能 使用相同的回调接口,但通常更喜欢声明性依赖 注入,通过注释或编程配置。spring-doc.cadn.net.cn

请注意,核心BeanFactoryAPI 级别及其DefaultListableBeanFactory实现不要对配置格式或任何 要使用的组件注释。所有这些风格都是通过扩展而来的 (例如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor) 和 对共享进行作BeanDefinition对象作为核心元数据表示形式。 这就是使 Spring 容器如此灵活和可扩展的本质。spring-doc.cadn.net.cn

1.16.1.BeanFactoryApplicationContext?

本节解释BeanFactoryApplicationContext容器级别及其对引导的影响。spring-doc.cadn.net.cn

您应该使用ApplicationContext除非你有充分的理由不这样做,否则使用GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。这些是主要入口指向 Spring 的核心容器,用于所有常见目的:加载配置文件,触发类路径扫描,以编程方式注册 Bean 定义和带注释的类,以及(从 5.0 开始)注册功能 Bean 定义。spring-doc.cadn.net.cn

因为ApplicationContext包括BeanFactory是的 通常推荐在普通BeanFactory,但已满的场景除外 需要控制 bean 处理。在ApplicationContext(例如GenericApplicationContext实现),检测到几种 bean 按约定(即按 Bean 名称或按 Bean 类型 - 特别是后处理器), 虽然平原DefaultListableBeanFactory对任何特殊的豆子都不可知论。spring-doc.cadn.net.cn

对于许多扩展容器功能,例如注解处理和 AOP 代理, 这BeanPostProcessor扩展点是必不可少的。 如果您只使用普通DefaultListableBeanFactory,此类后处理器不会 默认情况下被检测并激活。这种情况可能会令人困惑,因为 您的 bean 配置实际上没有任何问题。相反,在这种情况下, 容器需要通过其他设置完全引导。spring-doc.cadn.net.cn

下表列出了BeanFactoryApplicationContext接口和实现。spring-doc.cadn.net.cn

表 9.功能矩阵
特征 BeanFactory ApplicationContext

Bean 实例化/布线spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

集成生命周期管理spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

自动BeanPostProcessor注册spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

自动BeanFactoryPostProcessor注册spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

方便MessageSource访问(用于国际化)spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

内置ApplicationEvent发布机制spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

DefaultListableBeanFactory, 您需要以编程方式调用addBeanPostProcessor,如以下示例所示:spring-doc.cadn.net.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

要应用BeanFactoryPostProcessor到平原DefaultListableBeanFactory, 您需要调用其postProcessBeanFactory方法,如以下示例所示:spring-doc.cadn.net.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)

在这两种情况下,显式注册步骤都很不方便,即 为什么各种ApplicationContext变体优于普通DefaultListableBeanFactory在 Spring-backed 应用程序中,尤其是当 依靠BeanFactoryPostProcessorBeanPostProcessor扩展实例 典型企业设置中的容器功能。spring-doc.cadn.net.cn

AnnotationConfigApplicationContext具有所有常见的注释后处理器 注册,并可能在 通过配置注释覆盖,例如@EnableTransactionManagement. 在 Spring 基于注释的配置模型的抽象级别, Bean 后处理器的概念仅仅成为一个内部容器细节。spring-doc.cadn.net.cn

2. 资源

本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:spring-doc.cadn.net.cn

2.1. 简介

Java 的标准java.net.URL各种 URL 前缀的类和标准处理程序, 不幸的是,还不足以让所有人获得低级资源。为 例如,没有标准化的URL可用于访问 需要从类路径或相对于ServletContext.虽然可以为专用处理程序注册新的处理程序URLprefixes(类似于前缀的现有处理程序,例如http:),这通常是 相当复杂,并且URL界面仍然缺乏一些理想的功能, 例如检查所指向资源是否存在的方法。spring-doc.cadn.net.cn

2.2.Resource接口

Spring的Resource界面位于org.springframework.core.io.package是 旨在成为一个更强大的接口,用于抽象对低级资源的访问。这 以下列表概述了Resource接口。请参阅Resourcejavadoc 了解更多详情。spring-doc.cadn.net.cn

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}

作为Resource接口显示时,它扩展了InputStreamSource接口。以下列表显示了InputStreamSource接口:spring-doc.cadn.net.cn

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

来自Resource接口是:spring-doc.cadn.net.cn

  • getInputStream():查找并打开资源,返回InputStream为 从资源中读取。预计每个调用都会返回一个新的InputStream.调用方负责关闭流。spring-doc.cadn.net.cn

  • exists():返回一个boolean指示此资源是否实际存在于 物理形式。spring-doc.cadn.net.cn

  • isOpen():返回一个boolean指示此资源是否表示句柄 有一条开放的溪流。如果trueInputStream不能多次读取,并且 必须只读取一次,然后关闭以避免资源泄漏。返回false为 所有常用资源实现,但InputStreamResource.spring-doc.cadn.net.cn

  • getDescription():返回此资源的描述,用于处理错误使用资源时的输出。这通常是完全限定的文件名或资源的实际 URL。spring-doc.cadn.net.cn

其他方法可以让您获得实际的URLFile对象表示资源(如果底层实现兼容并支持功能)。spring-doc.cadn.net.cn

的一些实现Resource接口还实现了扩展的WritableResource接口 支持写入它的资源。spring-doc.cadn.net.cn

Spring 本身使用Resource抽象,作为参数类型许多方法签名。某些 Spring API 中的其他方法(例如构造函数到ApplicationContext实现)采取一个String以朴素或简单的形式用于创建Resource适合 该上下文实现,或者通过Stringpath,让 调用方指定特定的Resource必须创建和使用实现。spring-doc.cadn.net.cn

虽然Resourceinterface 在 Spring 中被大量使用,而 Spring 实际上是 在您自己的代码中单独用作通用实用程序类非常方便,以便访问 资源,即使您的代码不知道或关心 Spring 的任何其他部分。 虽然这将您的代码耦合到 Spring,但它实际上只将其耦合到这一小集 实用程序类,它可以作为更强大的替代品URL并且可以是 被认为等同于您为此目的使用的任何其他库。spring-doc.cadn.net.cn

Resource抽象不会取代功能。它把它包装在哪里 可能。例如,一个UrlResource包装一个 URL 并使用URL做它的 工作。

2.3. 内置Resource实现

Spring 包括几个内置的Resource实现:spring-doc.cadn.net.cn

如需完整列表ResourceSpring 中可用的实现,请参阅 “所有已知实现类”部分Resourcejavadoc 的文档。spring-doc.cadn.net.cn

2.3.1.UrlResource

UrlResource包装一个java.net.URL并且可用于访问任何对象通常可以通过 URL 访问,例如文件、HTTPS 目标、FTP 目标和 别人。 所有 URL 都有一个标准化的String表示形式,以便适当的标准化前缀用于指示一种 URL 类型与另一种 URL 类型。这包括file:用于访问文件系统路径,https:用于通过HTTPS 协议访问资源,ftp:用于通过 FTP 等访问资源。spring-doc.cadn.net.cn

一个UrlResource是由 Java 代码通过显式使用UrlResource构造 函数 但通常在调用 API 方法时隐式创建,该方法采用String参数表示路径。对于后一种情况,JavaBeansPropertyEditor最终决定哪种类型的Resource创造。如果路径字符串包含 已知(即属性编辑器)前缀(例如classpath:),它会创建一个 适当的专业Resource对于该前缀。但是,如果它无法识别 前缀,它假设该字符串是标准 URL 字符串,并创建UrlResource.spring-doc.cadn.net.cn

2.3.2.ClassPathResource

此类表示应从类路径获取的资源。它使用线程上下文类加载器、给定类加载器或给定类加载资源。spring-doc.cadn.net.cn

Resource实现支持解析为java.io.File如果类路径资源驻留在文件系统中,但不适用于驻留在jar 中的类路径资源,并且尚未扩展(通过 servlet 引擎或任何环境)到文件系统。为了解决这个问题,各种Resource实现始终支持resolution 作为java.net.URL.spring-doc.cadn.net.cn

一个ClassPathResource是由 Java 代码通过显式使用ClassPathResource构造函数,但通常在调用 API 方法时隐式创建,该方法采用String参数表示路径。对于后一种情况,JavaBeansPropertyEditor识别特殊前缀,classpath:,在字符串路径上,并且创建一个ClassPathResource在这种情况下。spring-doc.cadn.net.cn

2.3.3.FileSystemResource

这是一个Resource实现java.io.File处理。 它还支持java.nio.file.Pathhandles,应用 Spring 的标准基于字符串的路径转换,但通过java.nio.file.Files应用程序接口。 对于纯java.nio.path.Path基于支持使用PathResource相反。FileSystemResource支持分辨率作为File并作为URL.spring-doc.cadn.net.cn

2.3.4.PathResource

这是一个Resource实现java.nio.file.Path句柄,执行所有作和转换,通过Path应用程序接口。 它支持分辨率作为File和 作为URL并且还实现了扩展的WritableResource接口。PathResource实际上是一种纯粹的java.nio.path.Path基于替代FileSystemResource跟 不同createRelative行为。spring-doc.cadn.net.cn

2.3.5.ServletContextResource

这是一个Resource实现ServletContext解释相关 Web 应用程序根目录中的相对路径的资源。spring-doc.cadn.net.cn

它始终支持流访问和 URL 访问,但允许java.io.File仅访问权限 当 Web 应用程序存档被扩展并且资源物理上位于 文件系统。无论它是否被扩展并在文件系统上或被访问 直接从 JAR 或其他地方(如数据库(可以想象))实际上是 依赖于 Servlet 容器。spring-doc.cadn.net.cn

2.3.6.InputStreamResource

InputStreamResource是一个Resource给定的实现InputStream.它 仅当没有特定的Resource实施适用。在 特别,更喜欢ByteArrayResource或任何基于文件的Resource尽可能实现。spring-doc.cadn.net.cn

与其他Resource实现,这是 已打开的资源。因此,它返回trueisOpen().如果出现以下情况,请勿使用它 您需要将资源描述符保留在某个位置,或者如果您需要读取流 多次。spring-doc.cadn.net.cn

2.3.7.ByteArrayResource

这是一个Resource给定字节数组的实现。它创建了一个ByteArrayInputStream对于给定的字节数组。spring-doc.cadn.net.cn

它对于从任何给定的字节数组加载内容非常有用,而无需诉诸 一次性使用InputStreamResource.spring-doc.cadn.net.cn

2.4.ResourceLoader接口

ResourceLoader接口旨在由可以返回的对象实现 (即负载)Resource实例。以下列表显示了ResourceLoader接口定义:spring-doc.cadn.net.cn

public interface ResourceLoader {

    Resource getResource(String location);

    ClassLoader getClassLoader();
}

所有应用程序上下文都实现了ResourceLoader接口。因此,所有 应用程序上下文可用于获取Resource实例。spring-doc.cadn.net.cn

当您调用getResource()在特定应用程序上下文和位置路径上 specified 没有特定的前缀,则返回一个Resource类型,即 适合该特定应用程序上下文。例如,假设以下情况 代码片段针对ClassPathXmlApplicationContext实例:spring-doc.cadn.net.cn

Java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("some/resource/path/myTemplate.txt")

针对ClassPathXmlApplicationContext,则该代码返回ClassPathResource.如果 相同的方法针对FileSystemXmlApplicationContext例如,它会 返回一个FileSystemResource.对于一个WebApplicationContext,它将返回一个ServletContextResource.它同样会为每个上下文返回适当的对象。spring-doc.cadn.net.cn

因此,您可以以适合特定应用程序的方式加载资源 上下文。spring-doc.cadn.net.cn

另一方面,你也可以强制ClassPathResource无论 应用程序上下文类型,通过指定特殊的classpath:前缀,如下所示 示例显示:spring-doc.cadn.net.cn

Java
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")

同样,您可以强制UrlResource通过指定任何标准来使用java.net.URL前缀。以下示例使用filehttps前缀:spring-doc.cadn.net.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")

下表总结了转换策略String对象设置为Resource对象:spring-doc.cadn.net.cn

表10.资源字符串
前缀 示例 解释

类路径:spring-doc.cadn.net.cn

classpath:com/myapp/config.xmlspring-doc.cadn.net.cn

从类路径加载。spring-doc.cadn.net.cn

文件:spring-doc.cadn.net.cn

file:///data/config.xmlspring-doc.cadn.net.cn

加载为URL从文件系统。也可以看看FileSystemResource警告.spring-doc.cadn.net.cn

https:spring-doc.cadn.net.cn

https://myserver/logo.pngspring-doc.cadn.net.cn

加载为URL.spring-doc.cadn.net.cn

(无)spring-doc.cadn.net.cn

/data/config.xmlspring-doc.cadn.net.cn

取决于基础ApplicationContext.spring-doc.cadn.net.cn

2.5.ResourcePatternResolver接口

ResourcePatternResolver接口是ResourceLoader接口 它定义了解决位置模式的策略(例如,Ant 样式的路径 pattern) 变成Resource对象。spring-doc.cadn.net.cn

public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}

如上所示,该接口还定义了一个特殊的classpath*:资源前缀 对于类路径中的所有匹配资源。请注意,资源位置为 在这种情况下,预计是一条没有占位符的路径——例如,classpath*:/config/beans.xml.JAR 文件或类路径中的不同目录可以 包含具有相同路径和相同名称的多个文件。有关更多详细信息,请参阅应用程序上下文构造函数资源路径中的通配符及其小节 在通配符支持上,使用classpath*:资源前缀。spring-doc.cadn.net.cn

一个传入的ResourceLoader(例如,通过ResourceLoaderAware语义)是否可以检查 它也实现了这个扩展接口。spring-doc.cadn.net.cn

PathMatchingResourcePatternResolver是一个可用的独立实现 在ApplicationContext并且也被ResourceArrayPropertyEditor为 填充Resource[]bean 属性。PathMatchingResourcePatternResolver能够 将指定的资源位置路径解析为一个或多个匹配的Resource对象。 源路径可以是与目标具有一对一映射的简单路径Resource,或者可以包含特殊的classpath*:前缀和/或内部 Ant 样式正则表达式(使用 Spring 的org.springframework.util.AntPathMatcher实用程序)。后者都有效 通配符。spring-doc.cadn.net.cn

默认值ResourceLoader在任何标准中ApplicationContext实际上是一个实例 之PathMatchingResourcePatternResolver它实现了ResourcePatternResolver接口。对于ApplicationContext实例本身,这也 实现ResourcePatternResolver接口,并将委托给默认的PathMatchingResourcePatternResolver.spring-doc.cadn.net.cn

2.6.ResourceLoaderAware接口

ResourceLoaderAwareinterface 是一个特殊的回调接口,用于识别 组件,这些组件希望提供ResourceLoader参考。以下列表 显示了ResourceLoaderAware接口:spring-doc.cadn.net.cn

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当类实现ResourceLoaderAware并部署到应用程序上下文中 (作为 Spring 管理的 bean),它被识别为ResourceLoaderAware通过应用程序 上下文。然后,应用程序上下文调用setResourceLoader(ResourceLoader), 将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现 这ResourceLoader接口)。spring-doc.cadn.net.cn

由于ApplicationContext是一个ResourceLoader,则 bean 还可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来 加载资源。但是,一般来说,最好使用专门的ResourceLoader界面,如果这就是您所需要的。代码将仅与资源加载耦合 接口(可以被认为是一个实用程序接口),而不是整个 SpringApplicationContext接口。spring-doc.cadn.net.cn

在应用组件中,您还可以依赖于ResourceLoader如 实现ResourceLoaderAware接口。传统的 constructorbyType自动布线模式(如自动布线协作者中所述) 能够提供ResourceLoader对于构造函数参数或 setter 方法参数。为了获得更大的灵活性(包括能够 autowire 字段和多个参数方法),请考虑使用基于注释的 自动接线功能。在这种情况下,ResourceLoader自动连接到字段中, 构造函数参数或方法参数,该参数需要ResourceLoader类型为 long 由于相关字段、构造函数或方法携带@Autowired注解。 有关更多信息,请参阅@Autowired.spring-doc.cadn.net.cn

加载一个或多个Resource包含通配符的资源路径的对象 或利用特殊的classpath*:resource 前缀,请考虑使用ResourcePatternResolver自动连接到您的 应用程序组件而不是ResourceLoader.

2.7. 资源作为依赖项

如果 bean 本身要通过某种方式确定和提供资源路径 动态进程的 bean 使用ResourceLoaderResourcePatternResolver接口来加载资源。例如,考虑负载 某种模板的模板,其中所需的特定资源取决于 用户的角色。如果资源是静态的,那么消除使用ResourceLoader接口(或ResourcePatternResolverinterface)完全,则具有 bean 公开了Resource它需要的属性,并期望它们被注入其中。spring-doc.cadn.net.cn

注入这些属性变得微不足道的是,所有应用程序上下文 注册并使用特殊的 JavaBeansPropertyEditor,可以转换String路径 自Resource对象。例如,以下内容MyBean类有一个template类型的属性Resource.spring-doc.cadn.net.cn

Java
package example;

public class MyBean {

    private Resource template;

    public setTemplate(Resource template) {
        this.template = template;
    }

    // ...
}
Kotlin
class MyBean(var template: Resource)

在 XML 配置文件中,template属性可以使用简单的 string,如以下示例所示:spring-doc.cadn.net.cn

<bean id="myBean" class="example.MyBean">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

请注意,资源路径没有前缀。因此,由于应用程序上下文 本身将用作ResourceLoader,则资源通过ClassPathResource一个FileSystemResourceServletContextResource根据 应用程序上下文的确切类型。spring-doc.cadn.net.cn

如果您需要强制执行特定的Resourcetype 时,可以使用前缀。这 以下两个示例演示如何强制ClassPathResourceUrlResource(这 后者用于访问文件系统中的文件):spring-doc.cadn.net.cn

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

如果MyBean类被重构以用于注释驱动的配置,则 路径到myTemplate.txt可以存储在名为template.path——例如, 在提供给 Spring 的属性文件中Environment(参见环境抽象)。然后可以通过@Value使用属性占位符进行注释(请参阅@Value).Spring会 将模板路径的值检索为字符串,并将特殊PropertyEditor将 将字符串转换为Resource对象要注入到MyBean构造 函数。 以下示例演示了如何实现此目的。spring-doc.cadn.net.cn

Java
@Component
public class MyBean {

    private final Resource template;

    public MyBean(@Value("${template.path}") Resource template) {
        this.template = template;
    }

    // ...
}
Kotlin
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)

如果我们想支持在多个 类路径中的位置——例如,在类路径中的多个 jar 中——我们可以 使用特殊的classpath*:前缀和通配符来定义templates.pathkey 作为classpath*:/config/templates/*.txt.如果我们重新定义MyBeanclass 如下所示, Spring 会将模板路径模式转换为Resource对象 可以注入MyBean构造 函数。spring-doc.cadn.net.cn

Java
@Component
public class MyBean {

    private final Resource[] templates;

    public MyBean(@Value("${templates.path}") Resource[] templates) {
        this.templates = templates;
    }

    // ...
}
Kotlin
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])

2.8. 应用程序上下文和资源路径

本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 与 XML 一起使用、如何使用通配符和其他详细信息。spring-doc.cadn.net.cn

2.8.1. 构建应用程序上下文

应用程序上下文构造函数(通常适用于特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。spring-doc.cadn.net.cn

当这样的位置路径没有前缀时,特定的Resource类型构建自 该路径并用于加载 bean 定义取决于并且适用于 具体应用上下文。例如,请考虑以下示例,该示例创建了ClassPathXmlApplicationContext:spring-doc.cadn.net.cn

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")

Bean 定义是从类路径加载的,因为ClassPathResource是 使用。但是,请考虑以下示例,该示例会创建FileSystemXmlApplicationContext:spring-doc.cadn.net.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")

现在,Bean 定义从文件系统位置加载(在本例中,相对于 当前工作目录)。spring-doc.cadn.net.cn

请注意,使用特殊的classpath前缀或标准 URL 前缀 位置路径覆盖默认类型Resource创建以加载 bean 定义。请考虑以下示例:spring-doc.cadn.net.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")

FileSystemXmlApplicationContext从类路径加载 Bean 定义。 但是,它仍然是一个FileSystemXmlApplicationContext.如果它随后用作ResourceLoader,任何无前缀的路径仍被视为文件系统路径。spring-doc.cadn.net.cn

构建ClassPathXmlApplicationContext实例 — 快捷方式

ClassPathXmlApplicationContext公开了许多构造函数以启用 方便的实例化。基本思想是你只能提供一个字符串数组 仅包含 XML 文件本身的文件名(不包含前导路径 信息),并提供Class.这ClassPathXmlApplicationContext然后派生 来自提供的类的路径信息。spring-doc.cadn.net.cn

考虑以下目录布局:spring-doc.cadn.net.cn

com/
  example/
    services.xml
    repositories.xml
    MessengerService.class

以下示例显示了如何ClassPathXmlApplicationContext实例由 在名为services.xmlrepositories.xml(这些在 classpath)可以实例化:spring-doc.cadn.net.cn

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
Kotlin
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)

请参阅ClassPathXmlApplicationContextjavadoc 以获取有关各种构造函数的详细信息。spring-doc.cadn.net.cn

2.8.2. 应用程序上下文构造函数资源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径(如 前面显示),每个目标都有一对一的映射Resource或 或者,可以包含特殊的classpath*:前缀或内部 Ant 样式模式(通过使用 Spring 的PathMatcher实用程序)。后者都有效 通配符。spring-doc.cadn.net.cn

此机制的一个用途是当您需要执行组件样式的应用程序汇编时。 都 组件可以将上下文定义片段发布到已知的位置路径,并且,当使用前缀为classpath*:,则自动拾取所有组件片段。spring-doc.cadn.net.cn

请注意,此通配符特定于在应用程序上下文中使用资源路径构造函数(或当您使用PathMatcher实用程序类层次结构)并且是 在施工时解决。它与Resource类型本身。 您不能使用classpath*:前缀来构造实际的Resource如 资源一次仅指向一个资源。spring-doc.cadn.net.cn

Ant式图案

路径位置可以包含 Ant 样式模式,如以下示例所示:spring-doc.cadn.net.cn

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

当路径位置包含 Ant 样式模式时,解析器会遵循更复杂的模式 尝试解析通配符的过程。它会产生一个Resource对于通往 最后一个非通配符段,并从中获取 URL。如果此 URL 不是jar:URL 或 特定于容器的变体(例如zip:在 WebLogic 中,wsjar在 WebSphere 中,依此类推), 一个java.io.File从中获取,并用于通过遍历 文件系统。对于 jar URL,解析器要么获得java.net.JarURLConnection或手动解析 jar URL,然后遍历 用于解析通配符的 jar 文件的内容。spring-doc.cadn.net.cn

对便携性的影响

如果指定的路径已经是fileURL(隐式的,因为基ResourceLoader是文件系统之一或显式),通配符保证 以完全便携的方式工作。spring-doc.cadn.net.cn

如果指定的路径是classpath位置,解析器必须获取最后一个 非通配符路径段 URL,方法是将Classloader.getResource()叫。既然如此 只是路径的一个节点(不是末尾的文件),它实际上是未定义的(在ClassLoaderjavadoc)在这种情况下返回的 URL 类型。在实践中, 它始终是一个java.io.File表示目录(其中 ClassPath 资源 解析为文件系统位置)或某种 jar URL(其中 classpath 资源 解析为 jar 位置)。尽管如此,此作仍存在可移植性问题。spring-doc.cadn.net.cn

如果为最后一个非通配符段获取了jar URL,则解析器必须能够 获取一个java.net.JarURLConnection或手动解析 jar URL,以便能够 遍历 jar 的内容并解析通配符。这在大多数环境中确实有效 但在其他方面失败,我们强烈建议资源的通配符解析 来自 jar 在依赖它之前,请在您的特定环境中进行彻底测试。spring-doc.cadn.net.cn

classpath*:前缀

在构造基于 XML 的应用程序上下文时,位置字符串可以使用 特殊classpath*:prefix,如以下示例所示:spring-doc.cadn.net.cn

Java
ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")

此特殊前缀指定与给定名称匹配的所有类路径资源 必须获取(在内部,这基本上是通过调用ClassLoader.getResources(…​)),然后合并形成最终应用程序 上下文定义。spring-doc.cadn.net.cn

通配符类路径依赖于getResources()基础的方法ClassLoader.由于现在大多数应用程序服务器都提供自己的ClassLoader实现时,行为可能会有所不同,尤其是在处理 jar 文件时。一个 简单的测试,检查是否classpath*works 是使用ClassLoader从中加载文件 在类路径上的 jar 中:getClass().getClassLoader().getResources("<someFileInsideTheJar>").尝试此测试 具有相同名称但位于两个不同位置的文件,例如,文件 具有相同的名称和相同的路径,但在类路径上的不同 jar 中。如果 返回不适当的结果,请检查应用程序服务器文档中的设置 这可能会影响ClassLoader行为。

您还可以将classpath*:前缀加上PathMatcher模式 位置路径的其余部分(例如classpath*:META-INF/*-beans.xml).在这个 case 中,解决策略相当简单:AClassLoader.getResources()call 是 用于最后一个非通配符路径段,以获取 类加载器层次结构,然后,在每个资源中,相同的PathMatcher分辨率 前面描述的策略用于通配符子路径。spring-doc.cadn.net.cn

与通配符相关的其他说明

请注意classpath*:,当与Ant风格的图案结合使用时,仅有效 在模式启动之前至少有一个根目录,除非实际的 目标文件驻留在文件系统中。这意味着诸如classpath*:*.xml可能不会从 jar 文件的根目录中检索文件,而只能从 jar 文件的根目录中检索文件 来自扩展目录的根目录。spring-doc.cadn.net.cn

Spring 检索类路径条目的能力源自 JDK 的ClassLoader.getResources()方法,它仅返回 空字符串(表示要搜索的潜在根)。Spring 评估URLClassLoader运行时配置和java.class.pathjar 文件中的清单 同样,但这并不能保证会导致可移植行为。spring-doc.cadn.net.cn

扫描类路径包需要存在相应的目录 类路径中的条目。当您使用 Ant 构建 JAR 时,请不要激活files-onlyJAR 任务的开关。此外,基于安全性,类路径目录可能不会被公开 某些环境中的策略 — 例如,JDK 1.7.0_45 上的独立应用程序 和更高(这需要在清单中设置“Trusted-Library”。参见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。spring-doc.cadn.net.cn

在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。 这里也强烈建议将资源放入专用目录, 避免上述搜索 JAR 文件根级别的可移植性问题。spring-doc.cadn.net.cn

Ant式图案classpath:不保证资源找到匹配项 resources 如果要搜索的根包在多个类路径位置可用。 请考虑以下资源位置示例:spring-doc.cadn.net.cn

com/mycompany/package1/service-context.xml

现在考虑一个 Ant 风格的路径,有人可能会使用它来尝试查找该文件:spring-doc.cadn.net.cn

classpath:com/mycompany/**/service-context.xml

此类资源可能仅存在于类路径中的一个位置,但当路径(例如 前面的示例用于尝试解决它,解析器在(第一个) 返回的 URL 由getResource("com/mycompany");.如果此基础包节点存在于 倍数ClassLoaderlocations,则所需的资源可能不存在于第一个 位置找到。因此,在这种情况下,您应该更喜欢使用classpath*:使用 相同的 Ant 样式模式,它搜索包含com.mycompany基本包:classpath*:com/mycompany/**/service-context.xml.spring-doc.cadn.net.cn

2.8.3.FileSystemResource警告

一个FileSystemResource未附加到FileSystemApplicationContext(那 是,当FileSystemApplicationContext不是实际的ResourceLoader) 款待 绝对路径和相对路径,如您所期望的那样。相对路径相对于 当前工作目录,而绝对路径相对于 文件系统。spring-doc.cadn.net.cn

但是,出于向后兼容性(历史)原因,当FileSystemApplicationContextResourceLoader.这FileSystemApplicationContext全部附着的力FileSystemResource实例 将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。 在实践中,这意味着以下示例是等效的:spring-doc.cadn.net.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.cadn.net.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")

在实践中,如果您需要真正的绝对文件系统路径,则应避免使用 绝对路径与FileSystemResourceFileSystemXmlApplicationContext和 强制使用UrlResource通过使用file:URL 前缀。以下示例 展示如何做到这一点:spring-doc.cadn.net.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 提供了一个Validator既基本又非常可用的契约 在应用程序的每一层中。spring-doc.cadn.net.cn

数据绑定对于让用户输入动态绑定到域非常有用 应用程序模型(或用于处理用户输入的任何对象)。Spring 提供了恰如其分的名称DataBinder就是为了做到这一点。这ValidatorDataBinder构成validationpackage,主要用于但不用于 仅限于 Web 层。spring-doc.cadn.net.cn

BeanWrapper是 Spring Framework 中的一个基本概念,并且被大量使用 的地方。但是,您可能不需要使用BeanWrapper径直。但是,由于这是参考文档,我们认为一些解释 可能是有序的。我们解释BeanWrapper在本章中,因为,如果你是 要使用它,在尝试将数据绑定到对象时,您很可能会这样做。spring-doc.cadn.net.cn

Spring的DataBinder和较低级别的BeanWrapper两者都使用PropertyEditorSupport实现来分析和格式化属性值。这PropertyEditorPropertyEditorSupport类型是 JavaBeans 规范的一部分,也是 在本章中解释。Spring 3 引入了一个core.convert提供 通用类型转换工具,以及用于 设置 UI 字段值的格式。您可以使用这些包作为更简单的替代方案PropertyEditorSupport实现。本章也讨论了它们。spring-doc.cadn.net.cn

Spring 通过设置基础设施和适配器支持 Java Bean 验证 Spring自己的Validator合同。应用程序可以全局启用一次 Bean 验证, 如 Java Bean 验证中所述,并专门将其用于所有验证 需要。在 Web 层中,应用程序可以进一步注册控制器本地 SpringValidator实例数DataBinder,如配置DataBinder,可以 对于插入自定义验证逻辑很有用。spring-doc.cadn.net.cn

3.1. 使用 Spring 的验证器接口进行验证

Spring 具有Validator可用于验证对象的接口。这Validator接口的工作原理是使用Errors对象,以便在验证时, 验证者可以向Errors对象。spring-doc.cadn.net.cn

考虑以下小数据对象的示例:spring-doc.cadn.net.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)

下一个示例提供了Person类通过实现 以下两种方法org.springframework.validation.Validator接口:spring-doc.cadn.net.cn

  • supports(Class):可以这样吗Validator验证提供的实例Class?spring-doc.cadn.net.cn

  • validate(Object, org.springframework.validation.Errors):验证给定对象 并且,如果出现验证错误,则将这些错误与给定的Errors对象。spring-doc.cadn.net.cn

实现Validator相当简单,尤其是当您知道ValidationUtilsSpring Framework 也提供的 helper 类。以下内容 示例机具ValidatorPerson实例:spring-doc.cadn.net.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")
        }
    }
}

static rejectIfEmpty(..)方法ValidationUtilsclass 用于 拒绝name属性(如果是)null或空字符串。看看ValidationUtilsJava文档 查看除了前面显示的示例之外,它还提供哪些功能。spring-doc.cadn.net.cn

虽然当然可以实现单个Validator类来验证每个 对于富对象中的嵌套对象,封装验证可能更好 逻辑Validator实现。一个简单的 “丰富”对象的示例是Customer由两个组成String属性(名字和第二个名字)和复合体Address对象。Address对象 可以独立使用Customer对象,因此AddressValidator已经实施。如果你想要你的CustomerValidator重用包含的逻辑 在AddressValidator类,无需诉诸复制和粘贴,您可以 dependency-inject 或实例化AddressValidator在您的CustomerValidator, 如以下示例所示:spring-doc.cadn.net.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()
        }
    }
}

验证错误将报告给Errors对象传递给验证者。在这种情况下 Spring Web MVC 的 MVC,您可以使用<spring:bind/>标记来检查错误消息,但 您还可以检查Errors反对自己。有关 它提供的方法可以在 JavaDoc 中找到。spring-doc.cadn.net.cn

3.2. 将代码解析为错误消息

我们介绍了数据绑定和验证。本节介绍输出对应的消息 验证错误。在上一节所示的示例中, 我们拒绝了nameage领域。如果我们想使用MessageSource,我们可以使用我们在拒绝字段时提供的错误代码来做到这一点 (在本例中为“姓名”和“年龄”)。当您调用(直接或间接地,通过使用 例如,ValidationUtils类)rejectValue或另一个reject方法 从Errors接口,底层实现不仅注册了 传入,但也注册了许多额外的错误代码。这MessageCodesResolver确定哪些错误代码Errors接口寄存器。默认情况下,DefaultMessageCodesResolver,它(例如)不仅注册消息 使用您提供的代码,但也会注册包含您传递的字段名称的消息 设置为 reject 方法。因此,如果您使用rejectValue("age", "too.darn.old"), 除了too.darn.oldcode,Spring 也注册too.darn.old.agetoo.darn.old.age.int(第一个包括字段名称,第二个包括类型 的领域)。这样做是为了方便开发人员在定位错误消息时提供帮助。spring-doc.cadn.net.cn

有关MessageCodesResolver并且可以找到默认策略 在 javadoc 的MessageCodesResolverDefaultMessageCodesResolver, 分别。spring-doc.cadn.net.cn

3.3. Bean作和BeanWrapper

org.springframework.beans包遵循 JavaBeans 标准。 JavaBean 是一个具有默认无参数构造函数的类,它遵循 命名约定,其中(例如)名为bingoMadness愿意 有一个 setter 方法setBingoMadness(..)和一个 getter 方法getBingoMadness().为 有关 JavaBeans 和规范的更多信息,请参阅 Javabeansspring-doc.cadn.net.cn

bean 包中一个非常重要的类是BeanWrapper接口及其 相应的实现 (BeanWrapperImpl).正如 javadoc 中引用的那样,BeanWrapper提供设置和获取属性值的功能(单独或在 bulk)、获取属性描述符和查询属性以确定它们是否 可读或可写。此外,BeanWrapper提供对嵌套属性的支持, 启用子属性上的属性设置到无限深度。这BeanWrapper还支持添加标准 JavaBeans 的功能PropertyChangeListenersVetoableChangeListeners,无需在目标类中支持代码。 最后但并非最不重要的一点是,BeanWrapper支持设置索引属性。 这BeanWrapper通常不直接由应用程序代码使用,而是由DataBinderBeanFactory.spring-doc.cadn.net.cn

方式BeanWrapperWorks 的名称部分表明:它将 bean 包装成 对该 Bean 执行作,例如设置和检索属性。spring-doc.cadn.net.cn

3.3.1. 设置和获取基本属性和嵌套属性

设置和获取属性是通过setPropertyValuegetPropertyValue重载方法变体BeanWrapper.请参阅他们的 Javadoc 详。下表显示了这些约定的一些示例:spring-doc.cadn.net.cn

表 11.属性示例
表达 解释

namespring-doc.cadn.net.cn

指示属性name对应于getName()isName()setName(..)方法。spring-doc.cadn.net.cn

account.namespring-doc.cadn.net.cn

指示嵌套属性name的财产account对应于 (例如)getAccount().setName()getAccount().getName()方法。spring-doc.cadn.net.cn

account[2]spring-doc.cadn.net.cn

指示索引属性的第三个元素account.索引属性 可以是类型array,list,或其他自然有序的集合。spring-doc.cadn.net.cn

account[COMPANYNAME]spring-doc.cadn.net.cn

指示由COMPANYNAME键的account Map财产。spring-doc.cadn.net.cn

(如果您不打算与 这BeanWrapper径直。如果您仅使用DataBinderBeanFactory及其默认实现,您应该跳到部分PropertyEditors.)spring-doc.cadn.net.cn

以下两个示例类使用BeanWrapper获取和设置 性能:spring-doc.cadn.net.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
}

以下代码片段显示了如何检索和作某些 实例化的属性Companys 和Employees:spring-doc.cadn.net.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 使用PropertyEditor实现ObjectString.它可以很方便 以与对象本身不同的方式表示属性。例如,一个Date可以用人类可读的方式表示(作为String:'2007-14-09'),而 我们仍然可以将人类可读的形式转换回原始日期(或者,甚至 最好将以人类可读形式输入的任何日期转换回Date对象)。这 可以通过注册类型java.beans.PropertyEditor.在BeanWrapper或 或者,在特定的 IoC 容器中(如上一章所述),给出它 了解如何将属性转换为所需类型的知识。有关的更多信息PropertyEditor的 javadocjava.beans来自 Oracle 的包.spring-doc.cadn.net.cn

在 Spring 中使用属性编辑的几个示例:spring-doc.cadn.net.cn

  • 在 Bean 上设置属性是通过使用PropertyEditor实现。 当您使用String作为您声明的某个 bean 的属性的值 在 XML 文件中,Spring(如果相应属性的 setter 具有Class参数)使用ClassEditor尝试将参数解析为Class对象。spring-doc.cadn.net.cn

  • 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种类型来完成的 之PropertyEditor可以在CommandController.spring-doc.cadn.net.cn

Spring 内置了许多PropertyEditor实施,让生活变得轻松。 它们都位于org.springframework.beans.propertyeditors包。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl.如果属性编辑器可以以某种方式进行配置,您可以 仍然注册您自己的变体以覆盖默认变体。下表描述了 各种PropertyEditorSpring 提供的实现:spring-doc.cadn.net.cn

表12.内置PropertyEditor实现
解释

ByteArrayPropertyEditorspring-doc.cadn.net.cn

字节数组的编辑器。将字符串转换为其相应的字节 交涉。默认注册者BeanWrapperImpl.spring-doc.cadn.net.cn

ClassEditorspring-doc.cadn.net.cn

将类表示为实际类的字符串,反之亦然。当 class 未找到,则IllegalArgumentException被抛出。默认情况下,注册者BeanWrapperImpl.spring-doc.cadn.net.cn

CustomBooleanEditorspring-doc.cadn.net.cn

可自定义的属性编辑器Boolean性能。默认情况下,注册者BeanWrapperImpl但可以通过将其自定义实例注册为 自定义编辑器。spring-doc.cadn.net.cn

CustomCollectionEditorspring-doc.cadn.net.cn

集合的属性编辑器,转换任何源Collection到给定目标Collection类型。spring-doc.cadn.net.cn

CustomDateEditorspring-doc.cadn.net.cn

可自定义的属性编辑器java.util.Date,支持自定义DateFormat.不 默认注册。必须根据需要使用适当的格式进行用户注册。spring-doc.cadn.net.cn

CustomNumberEditorspring-doc.cadn.net.cn

适用于任何的可定制属性编辑器Number子类,例如Integer,Long,FloatDouble.默认情况下,注册者BeanWrapperImpl但可以被 将其自定义实例注册为自定义编辑器。spring-doc.cadn.net.cn

FileEditorspring-doc.cadn.net.cn

将字符串解析为java.io.File对象。默认情况下,注册者BeanWrapperImpl.spring-doc.cadn.net.cn

InputStreamEditorspring-doc.cadn.net.cn

单向属性编辑器,可以接受字符串并生成(通过 中间ResourceEditorResource) 一个InputStream因此InputStream属性可以直接设置为字符串。请注意,默认用法不会关闭 这InputStream给你的。默认情况下,注册者BeanWrapperImpl.spring-doc.cadn.net.cn

LocaleEditorspring-doc.cadn.net.cn

可以将字符串解析为Locale对象,反之亦然(字符串格式为[language]_[country]_[variant],与toString()方法Locale).也接受空格作为分隔符,作为下划线的替代。 默认情况下,注册者BeanWrapperImpl.spring-doc.cadn.net.cn

PatternEditorspring-doc.cadn.net.cn

可以将字符串解析为java.util.regex.Pattern对象,反之亦然。spring-doc.cadn.net.cn

PropertiesEditorspring-doc.cadn.net.cn

可以转换字符串(格式化为 javadoc 中定义的格式java.util.Propertiesclass) 设置为Properties对象。默认情况下,已注册 由BeanWrapperImpl.spring-doc.cadn.net.cn

StringTrimmerEditorspring-doc.cadn.net.cn

修剪字符串的属性编辑器。可选地允许转换空字符串 变成一个null价值。默认情况下未注册 — 必须是用户注册的。spring-doc.cadn.net.cn

URLEditorspring-doc.cadn.net.cn

可以将 URL 的字符串表示形式解析为实际的URL对象。 默认情况下,注册者BeanWrapperImpl.spring-doc.cadn.net.cn

Spring 使用java.beans.PropertyEditorManager设置属性的搜索路径 可能需要的编辑。搜索路径还包括sun.bean.editors哪 包括PropertyEditor诸如Font,Color,以及大部分 原始类型。另请注意,标准 JavaBeans 基础架构 自动发现PropertyEditor类(无需注册它们 显式),如果它们与它们处理的类位于同一包中并且具有相同的 name 作为该类,并带有Editor附加。例如,可以有以下内容 类和包结构,这对于SomethingEditor类 识别并用作PropertyEditorSomething-typed 属性。spring-doc.cadn.net.cn

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

请注意,您也可以使用BeanInfoJavaBeans 机制也是如此 (此处在某种程度上进行了描述)。这 以下示例使用BeanInfo显式注册一个或多个PropertyEditor实例具有关联类的属性:spring-doc.cadn.net.cn

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

以下 Java 源代码用于引用SomethingBeanInfo类 员工 aCustomNumberEditor使用age属性的Something类:spring-doc.cadn.net.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) {
                @Override
                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 容器最终使用 标准 JavaBeansPropertyEditor实现将这些字符串转换为 财产。Spring 预注册了许多自定义PropertyEditor实现(例如,将 将表示为字符串的类名转换为Class对象)。此外 Java 的标准 JavaBeansPropertyEditor查找机制允许PropertyEditor对于类,请适当命名并放置在与类相同的包中 它为其提供支持,以便可以自动找到它。spring-doc.cadn.net.cn

如果需要注册其他自定义PropertyEditors,有几种机制 可用。最手动的方法,通常不方便或 recommended,就是使用registerCustomEditor()方法ConfigurableBeanFactory接口,假设你有一个BeanFactory参考。 另一种(稍微方便一点)的机制是使用专门的豆厂 后处理器调用CustomEditorConfigurer.虽然您可以使用 bean 工厂后处理器 跟BeanFactory实现,则CustomEditorConfigurer有一个 nested 属性设置,因此我们强烈建议您将其与ApplicationContext,您可以在其中以与任何其他 Bean 类似的方式部署它,并且 它可以被自动检测和应用。spring-doc.cadn.net.cn

请注意,所有 bean 工厂和应用程序上下文都会自动使用许多 内置属性编辑器,通过使用BeanWrapper自 处理属性转换。标准属性编辑器BeanWrapper寄存器在上一节中列出。 此外ApplicationContext也会覆盖或添加其他编辑器来处理 以适合特定应用程序上下文类型的方式进行资源查找。spring-doc.cadn.net.cn

标准 JavaBeansPropertyEditor实例用于转换属性值 表示为属性的实际复杂类型的字符串。您可以使用CustomEditorConfigurer,豆厂后处理器,方便添加 支持其他PropertyEditorinstances 到ApplicationContext.spring-doc.cadn.net.cn

考虑以下示例,该示例定义了一个名为ExoticType和 另一个名为DependsOnExoticType,需要ExoticType设置为属性:spring-doc.cadn.net.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
}

当事情设置正确后,我们希望能够将类型属性分配为 string,其中PropertyEditor转换为实际的ExoticType实例。以下 bean 定义显示了如何设置此关系:spring-doc.cadn.net.cn

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor实现可能类似于以下内容:spring-doc.cadn.net.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())
    }
}

最后,以下示例展示了如何使用CustomEditorConfigurer以注册新的PropertyEditor使用ApplicationContext,然后它将能够根据需要使用它:spring-doc.cadn.net.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 容器注册属性编辑器的另一种机制是 创建并使用PropertyEditorRegistrar.此界面在以下情况下特别有用 您需要在几种不同的情况下使用同一组属性编辑器。 您可以编写相应的注册商并在每种情况下重复使用它。PropertyEditorRegistrar实例与名为PropertyEditorRegistry,一个由 Spring 实现的接口BeanWrapper(和DataBinder).PropertyEditorRegistrar实例特别方便 当与CustomEditorConfigurer此处所述),它公开了一个属性 叫setPropertyEditorRegistrars(..).PropertyEditorRegistrar已添加实例 设置为CustomEditorConfigurer以这种方式可以轻松分享DataBinder和 Spring MVC 控制器。此外,它避免了自定义同步的需要 编辑:APropertyEditorRegistrar有望创造新鲜感PropertyEditor每个 Bean 创建尝试的实例。spring-doc.cadn.net.cn

以下示例演示如何创建自己的PropertyEditorRegistrar实现:spring-doc.cadn.net.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...
    }
}

另请参阅org.springframework.beans.support.ResourceEditorRegistrar举个例子PropertyEditorRegistrar实现。请注意,在实现registerCustomEditors(..)方法,它会创建每个属性编辑器的新实例。spring-doc.cadn.net.cn

下一个示例演示如何配置CustomEditorConfigurer并注入实例 我们的CustomPropertyEditorRegistrar进入其中:spring-doc.cadn.net.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 框架,使用PropertyEditorRegistrar在 与数据绑定 Web 控制器结合使用可以非常方便。以下内容 示例使用PropertyEditorRegistrar在实施@InitBinder方法:spring-doc.cadn.net.cn

Java
@Controller
public class RegisterUserController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    @InitBinder
    void initBinder(WebDataBinder binder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods related to registering a User
}
Kotlin
@Controller
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {

    @InitBinder
    fun initBinder(binder: WebDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }

    // other methods related to registering a User
}

这种风格的PropertyEditor注册可以导致简洁的代码(实现 的@InitBinder方法只有一行长),并且让PropertyEditor注册码封装在类中,然后在尽可能多的控制器之间共享 根据需要。spring-doc.cadn.net.cn

3.4. Spring Type Conversion

Spring 3 引入了一个core.convert提供通用类型转换的包 系统。系统定义一个SPI来实现类型转换逻辑和一个API 在运行时执行类型转换。在 Spring 容器中,您可以使用此系统 作为替代方案PropertyEditor转换外部化 bean 属性值的实现 字符串添加到所需的属性类型。您还可以在 需要类型转换的应用程序。spring-doc.cadn.net.cn

3.4.1. 转换器SPI

实现类型转换逻辑的 SPI 是简单且强类型的,如下所示 接口定义显示:spring-doc.cadn.net.cn

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}

要创建您自己的转换器,请实现Converter接口和参数化S作为您要转换的类型,以及T作为您要转换为的类型。您也可以透明地应用这样的 转换器,如果集合或数组为S需要是 转换为数组或集合T,前提是委托数组或集合 转换器也已注册(其中DefaultConversionService默认情况下)。spring-doc.cadn.net.cn

对于对convert(S),则保证 source 参数不为 null。你Converter如果转换失败,可能会抛出任何未检查的异常。具体来说,它应该抛出一个IllegalArgumentException以报告无效的源值。 请注意确保您的Converter实现是线程安全的。spring-doc.cadn.net.cn

中提供了多个转换器实现core.convert.support打包为 一种便利。其中包括从字符串到数字和其他常见类型的转换器。 以下列表显示了StringToInteger类,这是一个典型的Converter实现:spring-doc.cadn.net.cn

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

3.4.2. 使用ConverterFactory

当您需要集中整个类层次结构的转换逻辑时 (例如,从StringEnum对象),您可以实现ConverterFactory,如以下示例所示:spring-doc.cadn.net.cn

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

将 S 参数化为要转换的类型,将 R 参数化为定义的基本类型 可以转换为的类范围。然后实现getConverter(Class<T>), 其中 T 是 R 的子类。spring-doc.cadn.net.cn

考虑一下StringToEnumConverterFactory举个例子:spring-doc.cadn.net.cn

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

当您需要复杂的Converter实现时,请考虑使用GenericConverter接口。 具有更灵活但类型不那么强的签名 比Converter一个GenericConverter支持在多个源和target 类型之间进行转换。此外,一个GenericConverter使源和目标字段上下文可用,您可以在实现转换逻辑时使用。此类上下文允许类型转换由字段注释或在字段签名上声明的通用信息驱动。以下列表显示了GenericConverter:spring-doc.cadn.net.cn

package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

要实现GenericConvertergetConvertibleTypes()返回受支持的source→target 类型对。然后实现convert(Object, TypeDescriptor, TypeDescriptor)以包含您的转换逻辑。源TypeDescriptor提供 访问保存要转换的值的源字段。目标TypeDescriptor提供对要设置转换值的目标字段的访问。spring-doc.cadn.net.cn

一个很好的例子GenericConverter是一个在 Java 数组之间进行转换的转换器和集合。这样的ArrayToCollectionConverter内省声明目标集合类型以解析集合的元素类型。这使得每个源数组中的元素在集合被设置在目标字段上之前转换为集合元素类型。spring-doc.cadn.net.cn

因为GenericConverter是一个比较复杂的 SPI 接口,你应该使用它只在你需要的时候。 喜爱ConverterConverterFactory对于基本型转换需求。
ConditionalGenericConverter

有时,您想要一个Converter仅在特定条件成立时运行。 为 例如,您可能想要运行一个Converter仅当存在特定注释时,或者您可能想要运行Converter仅当特定方法 (例如static valueOf方法)在目标类上定义。ConditionalGenericConverterGenericConverterConditionalConverter接口,用于定义此类自定义匹配条件:spring-doc.cadn.net.cn

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

一个很好的例子ConditionalGenericConverter是一个IdToEntityConverter转换 在持久实体标识符和实体引用之间。这样的IdToEntityConverter仅当目标实体类型声明静态查找器方法(例如,findAccount(Long)).您可以在matches(TypeDescriptor, TypeDescriptor).spring-doc.cadn.net.cn

3.4.4.ConversionService应用程序接口

ConversionService定义一个统一的 API,用于执行类型转换逻辑 运行。转换器通常在以下立面接口后面运行:spring-doc.cadn.net.cn

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

ConversionService实现也实现ConverterRegistry哪 提供用于注册转换器的SPI。在内部,一个ConversionService实现委托给其注册的转换器来执行类型转换逻辑。spring-doc.cadn.net.cn

坚固的ConversionService实现在core.convert.support包。GenericConversionService通用实现是否适合 在大多数环境中使用。ConversionServiceFactory为提供便利的工厂 创造共同点ConversionService配置。spring-doc.cadn.net.cn

3.4.5. 配置ConversionService

一个ConversionService是一个无状态对象,设计用于在应用程序中实例化 启动,然后在多个线程之间共享。在 Spring 应用程序中,您通常 配置一个ConversionService实例(或ApplicationContext). Spring接住了这一点ConversionService并在类型 转换需要由框架执行。您也可以注入此ConversionService到你的任何 bean 中并直接调用它。spring-doc.cadn.net.cn

如果没有ConversionService在 Spring 中注册,原始PropertyEditor-基于 系统。

注册默认值ConversionService使用 Spring,添加以下 bean 定义 使用idconversionService:spring-doc.cadn.net.cn

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

默认ConversionService可以在字符串、数字、枚举、集合、 地图和其他常见类型。要使用 自己的自定义转换器,将converters财产。属性值可以实现 任何Converter,ConverterFactoryGenericConverter接口。spring-doc.cadn.net.cn

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

使用ConversionService在 Spring MVC 应用程序中。请参阅 Spring MVC 章节中的转换和格式化spring-doc.cadn.net.cn

在某些情况下,您可能希望在转换过程中应用格式。看FormatterRegistrySPI有关使用详情FormattingConversionServiceFactoryBean.spring-doc.cadn.net.cn

3.4.6. 使用ConversionService编程

要使用ConversionService实例中,您可以注入引用 就像你对任何其他豆子一样。以下示例显示了如何执行此作:spring-doc.cadn.net.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(...)
    }
}

对于大多数用例,您可以使用convert指定targetType,但它 不适用于更复杂的类型,例如参数化元素的集合。 例如,如果要将ListInteger设置为ListString编程 您需要提供源和目标类型的正式定义。spring-doc.cadn.net.cn

幸运TypeDescriptor提供了各种选项,使之变得简单, 如以下示例所示:spring-doc.cadn.net.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)))

请注意DefaultConversionService自动注册转换器 适用于大多数环境。这包括集合转换器、标量 转换器和基本Object-自-String变换 器。您可以注册相同的转换器 与任何ConverterRegistry通过使用 staticaddDefaultConverters方法DefaultConversionService类。spring-doc.cadn.net.cn

值类型的转换器被重用于数组和集合,因此有 无需创建特定的转换器即可从CollectionS设置为CollectionT,假设标准集合处理是适当的。spring-doc.cadn.net.cn

3.5. Spring 字段格式化

如上一节所述,core.convert是一个 通用型转换系统。它提供了一个统一的ConversionServiceAPI 作为 以及强类型的ConverterSPI 用于实现从一种类型实现转换逻辑 给另一个。Spring 容器使用此系统来绑定 bean 属性值。在 此外,Spring 表达式语言 (SpEL) 和DataBinder使用此系统 绑定字段值。例如,当 SpEL 需要强制Short设置为Long自 完成一个expression.setValue(Object bean, Object value)尝试,则core.convert系统执行强制。spring-doc.cadn.net.cn

现在考虑典型客户端环境的类型转换要求,例如 Web 或桌面应用程序。在此类环境中,您通常从String支持客户端回发过程,以及返回String以支持 视图渲染过程。此外,您经常需要进行本地化String值。更多 常规core.convert ConverterSPI 不满足此类格式要求 径直。为了直接解决这些问题,Spring 3 引入了一个方便的FormatterSPI 该 提供了一个简单而强大的替代方案PropertyEditor客户端环境的实现。spring-doc.cadn.net.cn

通常,您可以使用ConverterSPI 当您需要实现通用类型时 转换逻辑 — 例如,用于在java.util.DateLong. 您可以使用Formatter在客户端环境(例如 Web application),需要解析和打印本地化字段值。这ConversionService为两个 SPI 提供统一的类型转换 API。spring-doc.cadn.net.cn

3.5.1.FormatterSPI

Formatter实现字段格式化逻辑的 SPI 是简单且强类型的。这 以下列表显示了Formatter接口定义:spring-doc.cadn.net.cn

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

FormatterPrinterParser构建块接口。这 以下列表显示了这两个接口的定义:spring-doc.cadn.net.cn

public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}

创建您自己的Formatter,实现Formatter前面显示的界面。 参数化T设置为要格式化的对象类型 - 例如,java.util.Date.实现print()作以打印T为 显示在客户端区域设置中。实现parse()作来解析T从客户端区域设置返回的格式化表示形式。你Formatter应该抛出一个ParseExceptionIllegalArgumentException如果解析尝试失败。拿 注意确保您的Formatter实现是线程安全的。spring-doc.cadn.net.cn

format子包提供了几个Formatter为了方便起见。 这numberpackage 提供NumberStyleFormatter,CurrencyStyleFormatterPercentStyleFormatter格式化Number使用java.text.NumberFormat. 这datetimepackage 提供了一个DateFormatter格式化java.util.Date对象与 一个java.text.DateFormat.spring-doc.cadn.net.cn

以下内容DateFormatter就是一个例子Formatter实现:spring-doc.cadn.net.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 团队欢迎社区驱动Formatter贡献。请参阅 GitHub 问题进行贡献。spring-doc.cadn.net.cn

3.5.2. 注释驱动的格式

字段格式可以按字段类型或注释进行配置。绑定 对Formatter实现AnnotationFormatterFactory.以下内容 列表中显示了AnnotationFormatterFactory接口:spring-doc.cadn.net.cn

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

要创建实现,请执行以下作:spring-doc.cadn.net.cn

  1. 参数化 A 为字段annotationType您希望与之关联 格式化逻辑 — 例如org.springframework.format.annotation.DateTimeFormat.spring-doc.cadn.net.cn

  2. getFieldTypes()返回可以使用注释的字段类型。spring-doc.cadn.net.cn

  3. getPrinter()返回一个Printer以打印带注释的字段的值。spring-doc.cadn.net.cn

  4. getParser()返回一个Parser解析clientValue对于带注释的字段。spring-doc.cadn.net.cn

以下示例AnnotationFormatterFactory实现绑定了@NumberFormat格式化程序的注释,让数字样式或模式成为 指定:spring-doc.cadn.net.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.cadn.net.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 存在于org.springframework.format.annotation包。 您可以使用@NumberFormat格式化Number字段,例如DoubleLong@DateTimeFormat格式化java.util.Date,java.util.Calendar,Long(对于毫秒时间戳)以及 JSR-310java.time.spring-doc.cadn.net.cn

以下示例使用@DateTimeFormat格式化java.util.Date作为 ISO 日期(yyyy-MM-dd):spring-doc.cadn.net.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.FormatterRegistrySPI

FormatterRegistry是用于注册格式化器和转换器的 SPI。FormattingConversionServiceFormatterRegistry适合人群 大多数环境。您可以通过编程或声明方式配置此变体 作为 Spring bean,例如通过使用FormattingConversionServiceFactoryBean.因为这个 实现也实现ConversionService,可以直接配置 与 Spring 的DataBinder和 Spring 表达式语言 (SpEL)。spring-doc.cadn.net.cn

以下列表显示了FormatterRegistrySPI:spring-doc.cadn.net.cn

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addPrinter(Printer<?> printer);

    void addParser(Parser<?> parser);

    void addFormatter(Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}

如前面的列表所示,您可以按字段类型或注释注册格式化程序。spring-doc.cadn.net.cn

FormatterRegistrySPI 允许您集中配置格式规则,而不是 在控制器之间复制此类配置。例如,您可能想要 强制所有日期字段都以特定方式格式化,或者将字段设置为特定的 注释以某种方式格式化。使用共享的FormatterRegistry,则定义 这些规则一次,并在需要格式化时应用它们。spring-doc.cadn.net.cn

3.5.4.FormatterRegistrarSPI

FormatterRegistrar是一个 SPI,用于通过 FormatterRegistry 中。以下列表显示了其接口定义:spring-doc.cadn.net.cn

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}

一个FormatterRegistrar在注册多个相关转换器时很有用,并且 给定格式类别的格式化程序,例如日期格式。也可以是 在声明性注册不足的情况下很有用,例如,当格式化程序 需要在与其自身不同的特定字段类型下建立索引<T>或者当 注册一个Printer/Parser双。下一节将提供有关 转换器和格式化程序注册。spring-doc.cadn.net.cn

3.5.5. 在Spring MVC中配置格式化

请参阅 Spring MVC 章节中的转换和格式化spring-doc.cadn.net.cn

3.6. 配置全局日期和时间格式

默认情况下,日期和时间字段未使用@DateTimeFormat从 字符串,使用DateFormat.SHORT风格。如果您愿意,可以通过以下方式更改此设置 定义您自己的全局格式。spring-doc.cadn.net.cn

为此,请确保 Spring 不注册默认格式化程序。相反,请注册 在以下人员的帮助下手动格式化程序:spring-doc.cadn.net.cn

例如,以下 Java 配置注册一个全局yyyyMMdd格式:spring-doc.cadn.net.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 dateTimeRegistrar = new DateTimeFormatterRegistrar();
        dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        dateTimeRegistrar.registerFormatters(conversionService);

        // Register date conversion with a specific global format
        DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
        dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
        dateRegistrar.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 dateTimeRegistrar = DateTimeFormatterRegistrar()
            dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            dateTimeRegistrar.registerFormatters(this)

            // Register date conversion with a specific global format
            val dateRegistrar = DateFormatterRegistrar()
            dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
            dateRegistrar.registerFormatters(this)
        }
    }
}

如果您更喜欢基于 XML 的配置,则可以使用FormattingConversionServiceFactoryBean.以下示例显示了如何执行此作:spring-doc.cadn.net.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.standard.DateTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>

请注意,在 Web 中配置日期和时间格式时,还有额外的注意事项 应用。请参阅 WebMVC 转换和格式化WebFlux 转换和格式化spring-doc.cadn.net.cn

3.7. Java Bean 验证

Spring Framework 提供对 Java Bean Validation API 的支持。spring-doc.cadn.net.cn

3.7.1. Bean 验证概述

Bean Validation 通过约束声明和 Java 应用程序的元数据。要使用它,请使用 声明性验证约束,然后由运行时强制执行。有 内置约束,你也可以定义自己的自定义约束。spring-doc.cadn.net.cn

考虑以下示例,该示例显示了一个简单的PersonForm具有两个属性的模型:spring-doc.cadn.net.cn

Java
public class PersonForm {
    private String name;
    private int age;
}
Kotlin
class PersonForm(
        private val name: String,
        private val age: Int
)

Bean Validation 允许您声明约束,如以下示例所示:spring-doc.cadn.net.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 Validation 验证器根据声明的 约束。有关 Bean Validation 的一般信息,请参阅 Bean Validation API。请参阅 Hibernate Validator 文档 具体约束。了解如何将 Bean 验证提供程序设置为 Spring 豆子,继续阅读。spring-doc.cadn.net.cn

3.7.2. 配置 Bean 验证提供程序

Spring 为 Bean Validation API 提供全面支持,包括引导 Bean Validation 提供程序作为 Spring bean。这可以让你注入一个javax.validation.ValidatorFactoryjavax.validation.Validator无论验证在哪里 在您的应用程序中需要。spring-doc.cadn.net.cn

您可以使用LocalValidatorFactoryBean将默认验证器配置为 Spring bean,如以下示例所示:spring-doc.cadn.net.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 验证提供程序,例如 Hibernate Validator 的 Validator 应存在于类路径中并被自动检测。spring-doc.cadn.net.cn

注入验证器

LocalValidatorFactoryBean实现两者javax.validation.ValidatorFactoryjavax.validation.Validator,以及 Spring 的org.springframework.validation.Validator. 您可以将对这些接口中的任何一个的引用注入到需要调用的 bean 中 验证逻辑。spring-doc.cadn.net.cn

您可以注入对javax.validation.Validator如果您更喜欢使用 Bean 验证 API,如以下示例所示:spring-doc.cadn.net.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)

您可以注入对org.springframework.validation.Validator如果你的豆子 需要 Spring Validation API,如以下示例所示:spring-doc.cadn.net.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.cadn.net.cn

要将声明与实现相关联,每个@Constraint注解 引用相应的ConstraintValidator实现类。在运行时,一个ConstraintValidatorFactory实例化引用的实现,当 约束注释。spring-doc.cadn.net.cn

默认情况下,LocalValidatorFactoryBean配置一个SpringConstraintValidatorFactory使用 Spring 创建ConstraintValidator实例。这可以让您的自定义ConstraintValidators像任何其他 Spring Bean 一样受益于依赖注入。spring-doc.cadn.net.cn

以下示例显示了自定义@Constraint声明后跟关联的ConstraintValidator使用 Spring 进行依赖注入的实现:spring-doc.cadn.net.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 {

    // ...
}

如前面的示例所示,一个ConstraintValidator实现可以有其依赖关系@Autowired和任何其他春豆一样。spring-doc.cadn.net.cn

弹簧驱动方法验证

您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为 自定义扩展,也由 Hibernate Validator 4.3) 通过MethodValidationPostProcessor豆子定义:spring-doc.cadn.net.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 的@Validated注释,也可以选择声明验证 组。看MethodValidationPostProcessor有关 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息。spring-doc.cadn.net.cn

方法验证依赖于 AOP 代理 目标类,接口上方法的 JDK 动态代理或 CGLIB 代理。 使用代理存在某些限制,其中一些限制在了解 AOP 代理中进行了描述。此外,请记住 始终在代理类上使用方法和访问器;直接现场访问将不起作用。spring-doc.cadn.net.cn

其他配置选项

默认值LocalValidatorFactoryBean配置足以满足大多数需求 例。各种 Bean 验证有许多配置选项 结构,从消息插值到遍历解析。请参阅LocalValidatorFactoryBeanjavadoc 以获取有关这些选项的更多信息。spring-doc.cadn.net.cn

3.7.3. 配置DataBinder

从 Spring 3 开始,您可以配置DataBinder实例与Validator.一次 配置,您可以调用Validator通过调用binder.validate().任何验证Errors会自动添加到活页夹的BindingResult.spring-doc.cadn.net.cn

以下示例显示如何使用DataBinder以编程方式调用验证 绑定到目标对象后的逻辑:spring-doc.cadn.net.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

您还可以配置DataBinder与多个Validator实例通过dataBinder.addValidatorsdataBinder.replaceValidators.这在 将全局配置的 bean 验证与 Spring 相结合Validator配置 本地在 DataBinder 实例上。请参阅 Spring MVC 验证配置spring-doc.cadn.net.cn

3.7.4. Spring MVC 3 验证

请参阅 Spring MVC 章节中的验证spring-doc.cadn.net.cn

4. Spring 表达式语言 (SpEL)

Spring 表达式语言(简称“SpEL”)是一种功能强大的表达式语言,它 支持在运行时查询和作对象图。语言语法是 类似于统一 EL,但提供了附加功能,最显着的是方法调用和 基本字符串模板功能。spring-doc.cadn.net.cn

虽然还有其他几种可用的 Java 表达式语言——OGNL、MVEL 和 JBoss EL 的 Spring 表达式语言,仅举几例——Spring 表达式语言的创建是为了提供 Spring 社区,具有一种支持良好的表达式语言,可在所有 Spring 产品组合中的产品。它的语言特性是由 Spring 产品组合中项目的要求,包括工具要求 在 Spring Tools for Eclipse 中支持代码完成。 也就是说,SpEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,应集成实施。spring-doc.cadn.net.cn

而 SpEL 是 Spring 中表达评估的基础 portfolio,它不直接绑定到 Spring,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是 独立表达式语言。这需要创建一些引导 基础设施类,例如 parser。大多数 Spring 用户无需处理 此基础结构,并且只能创作表达式字符串以进行评估。 这种典型用途的一个例子是将 SpEL 集成到创建 XML 或 基于注释的 Bean 定义,如 表达式支持定义 Bean 定义中所示。spring-doc.cadn.net.cn

本章介绍表达式语言的功能、其 API 及其语言 语法。在几个地方,InventorSociety类用作目标 用于表达式评估的对象。这些类声明和用于 填充它们列在本章末尾。spring-doc.cadn.net.cn

表达式语言支持以下功能:spring-doc.cadn.net.cn

4.1. 评估

本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在语言参考中找到。spring-doc.cadn.net.cn

以下代码介绍了 SpEL API 来计算文字字符串表达式Hello World.spring-doc.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 消息变量的值为'Hello World'.
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 消息变量的值为'Hello World'.

您最有可能使用的 SpEL 类和接口位于org.springframework.expressionpackage 及其子包,例如spel.support.spring-doc.cadn.net.cn

ExpressionParser接口负责解析表达式字符串。在 前面的示例中,表达式 string 是一个字符串文字,由周围的单个表示 引号。这Expression接口负责评估先前定义的 表达式字符串。可以抛出的两个异常,ParseExceptionEvaluationException,调用parser.parseExpressionexp.getValue, 分别。spring-doc.cadn.net.cn

SpEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。spring-doc.cadn.net.cn

在下面的方法调用示例中,我们调用concat字符串文字上的方法:spring-doc.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 的值message现在是“Hello World!”。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 的值message现在是“Hello World!”。

以下调用 JavaBean 属性的示例调用String属性Bytes:spring-doc.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 此行将文字转换为字节数组。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 此行将文字转换为字节数组。

SpEL 还支持使用标准点表示法(例如prop1.prop2.prop3)以及相应的属性值设置。 也可以访问公共字段。spring-doc.cadn.net.cn

以下示例显示如何使用点表示法获取文字的长度:spring-doc.cadn.net.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.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 构造一个新的String从字面意思开始,并使其成为大写。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
1 构造一个新的String从字面意思开始,并使其成为大写。

请注意通用方法的使用:public <T> T getValue(Class<T> desiredResultType). 使用此方法无需将表达式的值转换为所需的值 结果类型。一EvaluationException如果该值无法强制转换为 类型T或使用注册类型转换器进行转换。spring-doc.cadn.net.cn

SpEL 更常见的用法是提供一个被计算的表达式字符串 针对特定对象实例(称为根对象)。以下示例显示 如何检索name属性Inventorclass 或 创建布尔条件:spring-doc.cadn.net.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

EvaluationContext在计算要解析的表达式时使用 interface 属性、方法或字段,并帮助执行类型转换。Spring 提供了两个 实现。spring-doc.cadn.net.cn

  • SimpleEvaluationContext:公开基本 SpEL 语言特性的子集,并且 配置选项,用于不需要完整范围的表达式类别 SpEL 语言语法,并且应该受到有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的筛选器。spring-doc.cadn.net.cn

  • StandardEvaluationContext:公开全套 SpEL 语言特性和 配置选项。您可以使用它来指定默认根对象并配置 每一种可用的评估相关策略。spring-doc.cadn.net.cn

SimpleEvaluationContext旨在仅支持 SpEL 语言语法的子集。 它不包括 Java 类型引用、构造函数和 bean 引用。它还要求 显式选择表达式中属性和方法的支持级别。 默认情况下,create()静态工厂方法仅允许对属性的读取访问。 您还可以获得构建器来配置所需的确切支持级别,定位 以下一种或某种组合:spring-doc.cadn.net.cn

类型转换

默认情况下,SpEL 使用 Spring 核心中可用的转换服务 (org.springframework.core.convert.ConversionService).此转换服务自带 具有许多用于常见转换的内置转换器,但也完全可扩展,因此 您可以在类型之间添加自定义转化。此外,它是 泛型感知。这意味着,当您在 表达式,SpEL 尝试转换以保持任何对象的类型正确性 它遇到。spring-doc.cadn.net.cn

这在实践中意味着什么?假设赋值,使用setValue(),正在使用 将List财产。属性的类型实际上是List<Boolean>.斯佩尔 认识到列表的元素需要转换为Boolean以前 被放置在其中。以下示例显示了如何执行此作:spring-doc.cadn.net.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 表达式解析器 对象 (org.springframework.expression.spel.SpelParserConfiguration).配置 对象控制某些表达式组件的行为。例如,如果您 index 添加到数组或集合中,并且指定索引处的元素为null、SpEL 可以自动创建元素。当使用由 属性引用链。如果索引到数组或列表并指定索引 超过数组或列表当前大小的末尾,SpEL 可以自动 增加数组或列表以容纳该索引。为了在 指定索引,SpEL 将尝试使用元素类型的默认值创建元素 构造函数,然后再设置指定值。如果元素类型没有 默认构造函数,null将被添加到数组或列表中。如果没有内置 或知道如何设置值的自定义转换器,null将保留在数组中或 list 在指定索引中。以下示例演示如何自动增长 列表:spring-doc.cadn.net.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.cadn.net.cn

SpEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为并使用它 类以实现更快的表达式评估。由于缺乏打字 表达式时,编译器使用在解释评估期间收集的信息 执行编译时表达式的。例如,它不知道类型 纯粹来自表达式的属性引用,但在第一次解释期间 评估,它会发现它是什么。当然,基于这样的派生编译 如果各种表达式元素的类型,信息以后可能会造成麻烦 随着时间的推移而变化。因此,编译最适合 类型信息不会在重复求值时更改。spring-doc.cadn.net.cn

考虑以下基本表达式:spring-doc.cadn.net.cn

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问,因此某些属性取消引用 和数字运算,性能提升可能非常明显。在一个例子中 微基准测试运行 50000 次迭代,使用 解释器,并且仅使用表达式的编译版本仅 3 毫秒。spring-doc.cadn.net.cn

编译器配置

默认情况下,编译器未打开,但您可以在以下两个中的任何一个中打开它 不同的方式。您可以使用解析器配置过程将其打开 (前面讨论过)或使用 Spring 属性 当 SpEL 用法嵌入到另一个组件中时。本节讨论 这些选项。spring-doc.cadn.net.cn

编译器可以在三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerMode枚举。模式如下:spring-doc.cadn.net.cn

  • OFF(默认):编译器已关闭。spring-doc.cadn.net.cn

  • IMMEDIATE:在即时模式下,表达式会尽快编译。这 通常在第一次解释评估之后。如果编译后的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。spring-doc.cadn.net.cn

  • MIXED:在混合模式下,表达式在解释和编译之间静默切换 随时间变化的模式。经过一定数量的解释运行后,它们切换到编译 form 和,如果编译的表单出现问题(例如类型更改,如 前面所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译表单并切换到它。基本上 用户进入的异常IMMEDIATE模式在内部处理。spring-doc.cadn.net.cn

IMMEDIATE模式存在,因为MIXEDmode 可能会导致表达式出现问题 有副作用。如果编译的表达式在部分成功后爆炸,则它 可能已经做了一些影响系统状态的事情。如果这 ,调用方可能不希望它在解释模式下静默地重新运行, 因为表达式的一部分可能运行两次。spring-doc.cadn.net.cn

选择模式后,使用SpelParserConfiguration以配置解析器。这 以下示例显示了如何执行此作:spring-doc.cadn.net.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)。 编译的表达式在提供的任何下创建的子类加载器中定义。 重要的是要确保,如果指定了类加载器,它可以看到 表达式评估过程。如果未指定类加载器,则使用缺省类加载器 (通常是表达式评估期间运行的线程的上下文类加载程序)。spring-doc.cadn.net.cn

配置编译器的第二种方法是在将 SpEL 嵌入到某些 其他组件,并且可能无法通过配置来配置它 对象。在这些情况下,可以将spring.expression.compiler.mode属性通过 JVM 系统属性(或通过SpringProperties机制)设置为SpelCompilerMode枚举值 (off,immediatemixed).spring-doc.cadn.net.cn

编译器限制

从 Spring Framework 4.1 开始,基本的编译框架就已经到位。然而,框架 尚不支持编译每种表达式。最初的重点是 可能在性能关键型上下文中使用的常见表达式。以下内容 表达式类型目前无法编译:spring-doc.cadn.net.cn

将来将可编译更多类型的表达式。spring-doc.cadn.net.cn

4.2. Bean 定义中的表达式

您可以将 SpEL 表达式与基于 XML 或基于注释的配置元数据一起使用,用于 定义BeanDefinition实例。在这两种情况下,定义表达式的语法都是 形式#{ <expression string> }.spring-doc.cadn.net.cn

4.2.1. XML配置

可以使用表达式设置属性或构造函数参数值,如下所示 示例显示:spring-doc.cadn.net.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,例如environment(类型org.springframework.core.env.Environment)以及systemPropertiessystemEnvironment(类型Map<String, Object>) 以访问运行时环境。spring-doc.cadn.net.cn

以下示例显示了对systemPropertiesbean 作为 SpEL 变量:spring-doc.cadn.net.cn

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

请注意,您不必在此处为预定义变量添加符号前缀。#spring-doc.cadn.net.cn

您还可以按名称引用其他 bean 属性,如以下示例所示:spring-doc.cadn.net.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. 注释配置

要指定默认值,可以将@Value字段、方法、 以及方法或构造函数参数。spring-doc.cadn.net.cn

以下示例设置字段的默认值:spring-doc.cadn.net.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
}

以下示例显示了属性 setter 方法上的等效项:spring-doc.cadn.net.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
}

Autowired 方法和构造函数也可以使用@Value注释,如下所示 示例显示:spring-doc.cadn.net.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.cadn.net.cn

4.3.1. 文字表达式

SpEL 支持以下类型的文字表达式。spring-doc.cadn.net.cn

字符串可以用单引号 () 或双引号 () 分隔。自 在用单引号括起来的字符串文字中包含单引号 标记,使用两个相邻的单引号字符。同样,要包含双精度 用双引号括起来的字符串文字中的引号,请使用两个 相邻的双引号字符。'"spring-doc.cadn.net.cn

数字支持使用负号、指数表示法和小数点。 默认情况下,实数是通过使用Double.parseDouble().spring-doc.cadn.net.cn

以下列表显示了文字的简单用法。通常,它们不用于 像这样的隔离,而是作为更复杂表达式的一部分——例如, 在逻辑比较运算符的一侧使用文字或作为 方法。spring-doc.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// evaluates to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

// evaluates to "Tony's Pizza"
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evaluates 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()

// evaluates to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String

// evaluates to "Tony's Pizza"
val pizzaParlor = parser.parseExpression("'Tony''s Pizza'").value as String

val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double

// evaluates to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int

val trueValue = parser.parseExpression("true").value as Boolean

val nullValue = parser.parseExpression("null").value

4.3.2. 属性、数组、列表、映射和索引器

使用属性引用进行导航很容易。为此,请使用句点来指示嵌套的 属性值。的实例Inventorpupintesla,是 填充了 Classes 中列出的数据,用于 examples 部分。要“向下”导航对象图并获取特斯拉的出生年份和 普平的出生城市,我们用以下表达方式:spring-doc.cadn.net.cn

Java
// evaluates to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
Kotlin
// evaluates to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int

val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String

允许属性名称的第一个字母不区分大小写。因此,表达式可以写成Birthdate.Year + 1900PlaceOfBirth.City分别。 此外,可以选择通过方法调用来访问属性——例如,getPlaceOfBirth().getCity()而不是placeOfBirth.city.spring-doc.cadn.net.cn

数组和列表的内容是使用方括号表示法获得的,如以下示例所示:spring-doc.cadn.net.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)

映射的内容是通过在 括弧。 在以下示例中,由于officersmap 是字符串,我们可以指定 字符串:spring-doc.cadn.net.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.cadn.net.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<*>

{}本身意味着一个空列表。出于性能原因,如果列表本身是 完全由固定文字组成,创建一个常量列表来表示 表达式(而不是在每个评估上构建一个新列表)。spring-doc.cadn.net.cn

4.3.4. 内联映射

您还可以使用{key:value}表示法。这 以下示例显示了如何执行此作:spring-doc.cadn.net.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<*, *>

{:}本身意味着一张空地图。出于性能原因,如果地图本身是 由固定文字或其他嵌套常量结构(列表或映射)组成,一个 创建常量映射来表示表达式(而不是在 每个评估)。映射键的引用是可选的(除非键包含句点 (.)).上面的示例不使用带引号的键。spring-doc.cadn.net.cn

4.3.5. 数组构造

您可以使用熟悉的 Java 语法构建数组,并可选择提供初始值设定项 在构造时填充数组。以下示例显示了如何执行此作:spring-doc.cadn.net.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>

当前无法在构造多维数组时提供初始值设定项。spring-doc.cadn.net.cn

4.3.6. 方法

您可以使用典型的 Java 编程语法调用方法。您还可以调用方法 在文字上。还支持变量参数。以下示例演示如何 调用方法:spring-doc.cadn.net.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.cadn.net.cn

关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 以下列表显示了运算符的一些示例:spring-doc.cadn.net.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)

大于和小于比较null遵循一个简单的规则:null被视为 nothing(即不为零)。因此,任何其他值总是更大 比null (X > null总是true),没有其他值比什么都少于没有 (X < null总是false).spring-doc.cadn.net.cn

如果您更喜欢数字比较,请避免基于数字的比较null比较 支持与零进行比较(例如,X > 0X < 0).spring-doc.cadn.net.cn

除了标准关系运算符外,SpEL 还支持instanceof和常规 基于表达式matches算子。以下列表显示了两者的示例:spring-doc.cadn.net.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)
小心原始类型,因为它们会立即被框入其 包装器类型。例如1 instanceof T(int)评估为false1 instanceof T(Integer)评估为true不出所料。

每个符号运算符也可以指定为纯字母等效符。这 避免了所使用的符号对文档类型具有特殊含义的问题 表达式嵌入的(例如在 XML 文档中)。文本等价物是:spring-doc.cadn.net.cn

所有文本运算符都不区分大小写。spring-doc.cadn.net.cn

逻辑运算符

SpEL 支持以下逻辑运算符:spring-doc.cadn.net.cn

以下示例演示如何使用逻辑运算符:spring-doc.cadn.net.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.cadn.net.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
赋值运算符

要设置属性,请使用赋值运算符 ()。这通常在 调用=setValue但也可以在调用getValue.以下内容 清单显示了使用赋值运算符的两种方法:spring-doc.cadn.net.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. 类型

您可以使用特殊的T运算符来指定java.lang.Class(这 类型)。静态方法也使用此运算符调用。这StandardEvaluationContext使用TypeLocator查找类型,以及StandardTypeLocator(可以替换)是在了解java.lang包。这意味着T()java.langpackage 不需要完全限定,但所有其他类型引用都必须是。这 以下示例演示如何使用T算子:spring-doc.cadn.net.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. 构造函数

您可以使用new算子。您应该使用 所有类型的限定类名称,但位于java.lang包 (Integer,Float,String,依此类推)。以下示例演示如何使用new运算符来调用构造函数:spring-doc.cadn.net.cn

Java
Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

// create new Inventor instance within the 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 the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

4.3.10. 变量

您可以使用#variableName语法。变量 通过使用setVariable方法EvaluationContext实现。spring-doc.cadn.net.cn

有效的变量名称必须由以下一个或多个受支持的变量组成 字符。spring-doc.cadn.net.cn

以下示例演示如何使用变量。spring-doc.cadn.net.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变量

#this变量始终被定义并引用当前评估对象 (解决不限定的引用)。这#root变量始终是 定义并引用根上下文对象。虽然#this可能因 表达式被计算,#root总是指根。以下示例 展示如何使用#this#root变量:spring-doc.cadn.net.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. 函数

您可以通过注册可在 表达式字符串。该函数通过EvaluationContext.这 以下示例显示如何注册用户定义的函数:spring-doc.cadn.net.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.cadn.net.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.cadn.net.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 解析器,则可以 使用符号从表达式中查找 bean。以下示例演示了如何 为此:@spring-doc.cadn.net.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.cadn.net.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)

您可以使用三元运算符在内部执行 if-then-else 条件逻辑 表达式。以下列表显示了一个最小示例:spring-doc.cadn.net.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.cadn.net.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.cadn.net.cn

4.3.14. 猫王操作员

Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须重复变量两次,因为 以下示例显示:spring-doc.cadn.net.cn

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,您可以使用猫王运算符(因与猫王的发型相似而得名)。 以下示例显示如何使用 Elvis 运算符:spring-doc.cadn.net.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.cadn.net.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 运算符在表达式中应用默认值。以下内容 示例展示了如何在@Value表达:spring-doc.cadn.net.cn

@Value("#{systemProperties['pop3.port'] ?: 25}")

这将注入系统属性pop3.port如果已定义,则为 25,如果未定义。spring-doc.cadn.net.cn

4.3.15. 安全导航操作员

安全导航操作员用于避免NullPointerException并来自 Groovy 语言。通常,当您有对对象的引用时,您可能需要验证 在访问对象的方法或属性之前,它不是 null。为避免这种情况,请 安全导航运算符返回 null 而不是抛出异常。以下内容 示例显示了如何使用安全导航运算符:spring-doc.cadn.net.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.cadn.net.cn

选择使用.?[selectionExpression].它过滤集合和 返回一个包含原始元素子集的新集合。例如 选择让我们可以轻松获得塞尔维亚发明人名单,如以下示例所示:spring-doc.cadn.net.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>

数组和任何实现java.lang.Iterablejava.util.Map.对于列表或数组,选择标准将根据每个 单个元素。针对地图,将根据每个地图评估选择条件 条目(Java 类型的对象Map.Entry).每个地图条目都有其keyvalue可作为属性访问,以便在选择中使用。spring-doc.cadn.net.cn

以下表达式返回一个新映射,该映射由 条目值小于 27 的原始地图:spring-doc.cadn.net.cn

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

除了返回所有选定元素外,还可以仅检索第一个或 最后一个元素。要获得与选择匹配的第一个元素,语法为.^[selectionExpression].要获取最后匹配的选择,语法为.$[selectionExpression].spring-doc.cadn.net.cn

4.3.17. 集合投影

投影允许集合驱动子表达式的计算,结果是 一个新系列。投影的语法是.![projectionExpression].例如 假设我们有一份发明家名单,但想要他们出生的城市名单。 实际上,我们希望为发明人中的每个条目评估“placeOfBirth.city” 列表。以下示例使用投影来执行此作:spring-doc.cadn.net.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<*>

数组和任何实现java.lang.Iterablejava.util.Map.使用贴图驱动投影时,投影表达式为 根据映射中的每个条目(表示为 JavaMap.Entry).结果 地图上的投影是一个列表,其中包含对投影的评估 表达式。spring-doc.cadn.net.cn

4.3.18. 表达式模板

表达式模板允许将文字文本与一个或多个求值块混合使用。 每个计算块都用前缀和后缀字符分隔,您可以 定义。常见的选择是用作分隔符,如以下示例所示 显示:#{ }spring-doc.cadn.net.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()方法 是ParserContext.这ParserContext接口用于影响如何 分析表达式以支持表达式模板功能。 的定义TemplateParserContext遵循:spring-doc.cadn.net.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.cadn.net.cn

Inventor.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.cadn.net.cn

Spring 的关键组件之一是 AOP 框架。而 Spring IoC container 不依赖于 AOP(这意味着如果您不想,则无需使用 AOP ),AOP 补充了 Spring IoC,以提供非常强大的中间件解决方案。spring-doc.cadn.net.cn

带有 AspectJ 切入点的 Spring AOP

Spring 通过使用基于模式的方法@AspectJ注释样式提供了简单而强大的方法来编写自定义方面。 这两种风格都提供了完全类型的建议和 AspectJ 切入点语言的使用 同时仍然使用 Spring AOP 进行编织。spring-doc.cadn.net.cn

本章讨论基于模式和@AspectJ的 AOP 支持。 下一章将讨论较低级别的 AOP 支持。spring-doc.cadn.net.cn

AOP 在 Spring Framework 中用于:spring-doc.cadn.net.cn

如果您只对通用声明性服务或其他预打包服务感兴趣 声明式中间件服务(如池化)时,您不需要直接使用 Spring AOP,可以跳过本章的大部分内容。

5.1. AOP 概念

让我们首先定义一些核心的 AOP 概念和术语。这些条款不是 特定于Spring。不幸的是,AOP 术语并不是特别直观。 但是,如果 Spring 使用自己的术语,那就更令人困惑了。spring-doc.cadn.net.cn

  • 方面:跨多个类的关注点的模块化。 事务管理是企业 Java 中跨领域关注的一个很好的例子 应用。在 Spring AOP 中,方面是通过使用常规类实现的 (基于模式的方法)或使用@Aspect注释(@AspectJ样式)。spring-doc.cadn.net.cn

  • 连接点:程序执行过程中的点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点始终 表示方法执行。spring-doc.cadn.net.cn

  • 建议:某个方面在特定连接点执行的作。不同类型的 建议包括“周围”、“之前”和“之后”建议。(讨论了建议类型 稍后。许多 AOP 框架,包括 Spring,将建议建模为拦截器和 在连接点周围保持拦截器链。spring-doc.cadn.net.cn

  • 切入点:匹配连接点的谓词。建议与 切入点表达式,并在与切入点匹配的任何连接点处运行(例如, 执行具有特定名称的方法)。匹配的连接点的概念 by 切入点表达式是 AOP 的核心,Spring 使用 AspectJ 切入点 表达式语言。spring-doc.cadn.net.cn

  • 简介:代表类型声明其他方法或字段。Spring AOP 允许您向任何 建议对象。例如,您可以使用引言使 bean 实现IsModified接口,以简化缓存。(引言称为 inter 类型声明。spring-doc.cadn.net.cn

  • 目标对象:由一个或多个方面建议的对象。也称为 “建议对象”。由于 Spring AOP 是使用运行时代理实现的,因此 对象始终是代理对象。spring-doc.cadn.net.cn

  • AOP 代理:AOP 框架为实现方面而创建的对象 合约(建议方法执行等)。在 Spring Framework 中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。spring-doc.cadn.net.cn

  • 编织:将各个方面与其他应用程序类型或对象链接起来,以创建一个 建议对象。这可以在编译时完成(使用 AspectJ 编译器,对于 示例)、加载时间或运行时。Spring AOP 和其他纯 Java AOP 框架一样, 在运行时执行编织。spring-doc.cadn.net.cn

Spring AOP 包括以下类型的建议:spring-doc.cadn.net.cn

  • 通知之前:在连接点之前运行但没有 阻止执行流继续到连接点的能力(除非它抛出 一个例外)。spring-doc.cadn.net.cn

  • 返回通知后:连接点完成后要运行的通知 通常(例如,如果方法返回而不抛出异常)。spring-doc.cadn.net.cn

  • 抛出建议后:如果方法通过抛出 例外。spring-doc.cadn.net.cn

  • 在(最后)建议之后:无论以何种方式运行建议 连接点退出(正常或异常返回)。spring-doc.cadn.net.cn

  • 围绕建议:围绕连接点(例如方法调用)的建议。 这是最有力的建议。周围建议可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续到连接点或通过返回其 自己的返回值或抛出异常。spring-doc.cadn.net.cn

围绕建议是最普遍的建议。由于 Spring AOP 和 AspectJ 一样, 提供全方位的建议类型,我们建议您使用最不强大的 可以实现所需行为的 advise 类型。例如,如果您只需要 使用方法的返回值更新缓存,最好实现 在返回建议后,比周围建议,尽管周围建议可以完成 同样的事情。使用最具体的建议类型提供了更简单的编程模型 出错的可能性较小。例如,您不需要调用proceed()方法JoinPoint用于周围建议,因此,您不能不调用它。spring-doc.cadn.net.cn

所有通知参数都是静态类型的,以便您可以使用 适当的类型(例如,方法执行的返回值的类型)而不是 比Object阵 列。spring-doc.cadn.net.cn

切入点匹配的连接点的概念是AOP的关键,它区分了 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象层次结构的目标。例如,您可以应用 围绕向一组跨越 多个对象(例如服务层中的所有业务作)。spring-doc.cadn.net.cn

5.2. Spring AOP 功能和目标

Spring AOP 是用纯 Java 实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适用于 Servlet 容器或应用程序服务器。spring-doc.cadn.net.cn

Spring AOP 目前仅支持方法执行连接点(建议执行 Spring bean 上的方法)。未实现现场拦截,但支持 可以在不破坏核心 Spring AOP API 的情况下添加字段拦截。如果您需要 要建议字段访问和更新连接点,请考虑使用诸如 AspectJ 之类的语言。spring-doc.cadn.net.cn

Spring AOP 的 AOP 方法与大多数其他 AOP 框架不同。目的是 不提供最完整的AOP实现(虽然Spring AOP相当 有能力)。相反,目的是在 AOP 实施和 Spring IoC,帮助解决企业应用中的常见问题。spring-doc.cadn.net.cn

因此,例如,Spring 框架的 AOP 功能通常用于 与 Spring IoC 容器结合使用。使用普通 bean 配置方面 定义语法(尽管这允许强大的“自动代理”功能)。这是一个 与其他 AOP 实现的关键区别。有些事情你不能做 使用 Spring AOP 轻松或高效地建议非常细粒度的对象(通常, domain 对象)。在这种情况下,AspectJ 是最佳选择。但是,我们的 经验是,Spring AOP 为大多数问题提供了很好的解决方案 适用于 AOP 的企业 Java 应用程序。spring-doc.cadn.net.cn

Spring AOP 从不努力与 AspectJ 竞争提供全面的 AOP 溶液。我们认为,无论是基于代理的框架,如 Spring AOP,还是成熟的 像 AspectJ 这样的框架很有价值,它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以启用 在一致的基于 Spring 的应用程序中 AOP 的所有用途 架构。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参阅下一章spring-doc.cadn.net.cn

Spring Framework 的核心原则之一是非侵入性。这 是你不应该被迫引入特定于框架的类的想法,并且 与您的业务或域模型接口。但是,在某些地方,Spring 框架 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是,在某些情况下,它 可能更容易阅读或编码某些特定的功能 一种方式。但是,Spring Framework(几乎)总是为您提供选择:您拥有 自由地做出明智的决定,选择最适合您的特定用途 案例或场景。spring-doc.cadn.net.cn

与本章相关的一个选择是 AOP 框架(和 选择哪个 AOP 样式)。您可以选择 AspectJ、Spring AOP 或两者兼而有之。你 还可以选择 @AspectJ 注释样式方法或 Spring XML 配置样式的方法。本章选择引入 首先@AspectJ式的方法不应被视为春季队的标志 更倾向于@AspectJ注释样式的方法,而不是 Spring XML 配置样式。spring-doc.cadn.net.cn

请参阅选择要使用的 AOP 声明样式,以更完整地讨论 每种风格。spring-doc.cadn.net.cn

5.3. AOP 代理

Spring AOP 默认使用 AOP 代理的标准 JDK 动态代理。这 允许代理任何接口(或一组接口)。spring-doc.cadn.net.cn

Spring AOP 也可以使用 CGLIB 代理。这是代理类所必需的,而不是 接口。缺省情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见)的情况下,您可以强制使用 CGLIB 需要建议未在接口上声明的方法或您需要在哪里声明 将代理对象作为具体类型传递给方法。spring-doc.cadn.net.cn

重要的是要掌握 Spring AOP 是基于代理的事实。请参阅了解 AOP 代理,全面检查这到底是什么 实施细节实际上意味着。spring-doc.cadn.net.cn

5.4. @AspectJ支持

@AspectJ指的是一种将方面声明为常规 Java 类的样式,并使用 附注。@AspectJ样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注释 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或编织器。spring-doc.cadn.net.cn

使用 AspectJ 编译器和编织器可以使用完整的 AspectJ 语言和 在将 AspectJ 与 Spring 应用程序一起使用中讨论。

5.4.1. 启用@AspectJ支持

要在 Spring 配置中使用@AspectJ方面,您需要启用 Spring 对 基于@AspectJ方面配置 Spring AOP 和基于 无论他们是否受到这些方面的建议。通过自动代理,我们的意思是,如果 Spring 确定一个 bean 是由一个或多个方面建议的,它会自动生成 该 bean 的代理,用于拦截方法调用并确保通知运行 根据需要。spring-doc.cadn.net.cn

可以通过 XML 或 Java 样式配置启用@AspectJ支持。在任一 case,您还需要确保 AspectJ 的aspectjweaver.jar库位于 应用程序的类路径(版本 1.8 或更高版本)。此库可在lib目录 AspectJ 发行版或 Maven Central 存储库。spring-doc.cadn.net.cn

使用 Java 配置启用@AspectJ支持

使用 Java 启用@AspectJ支持@Configuration,将@EnableAspectJAutoProxy注释,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
Kotlin
@Configuration
@EnableAspectJAutoProxy
class AppConfig
使用 XML 配置启用 @AspectJ 支持

要通过基于 XML 的配置启用@AspectJ支持,请使用aop:aspectj-autoproxy元素,如以下示例所示:spring-doc.cadn.net.cn

<aop:aspectj-autoproxy/>

这假定您使用基于 XML 模式的配置中所述的模式支持。 请参阅 AOP 架构,了解如何 导入aopNamespace。spring-doc.cadn.net.cn

5.4.2. 声明一个方面

启用@AspectJ支持后,在应用程序上下文中定义的任何 bean 类,该类是@AspectJ方面(具有@Aspect注释)自动 被 Spring 检测到并用于配置 Spring AOP。接下来的两个示例显示了 对于不太有用的方面,需要最少的定义。spring-doc.cadn.net.cn

两个示例中的第一个示例显示了应用程序中的常规 Bean 定义 上下文,该上下文指向具有@Aspect注解:spring-doc.cadn.net.cn

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>

两个示例中的第二个示例显示了NotVeryUsefulAspect类定义, 其中用org.aspectj.lang.annotation.Aspect注解;spring-doc.cadn.net.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

方面(用@Aspect) 可以有方法和字段,与任何 其他类。它们还可以包含切入点、建议和介绍(inter-type) 声明。spring-doc.cadn.net.cn

通过组件扫描自动检测各个方面
您可以在 Spring XML 配置中将 aspect 类注册为常规 bean, 通过@Bean方法@Configuration类,或者让 Spring 通过 类路径扫描 — 与任何其他 Spring 管理的 Bean 相同。但是,请注意@Aspect注释不足以在类路径中进行自动检测。为此 目的,您需要添加一个单独的@Component注释(或者,或者,自定义 stereotype 注释,根据 Spring 的组件扫描程序的规则)。
将方面与其他方面提供建议?
在 Spring AOP 中,方面本身不能成为来自其他方面建议的目标 方面。这@Aspect类上的注释将其标记为一个方面,因此排除 它来自自动代理。

5.4.3. 声明切入点

切入点确定感兴趣的连接点,从而使我们能够控制当通知运行时。Spring AOP 仅支持 Spring 的方法执行连接点bean,因此您可以将切入点视为与 Spring 上方法的执行相匹配 豆。 切入点声明由两部分组成:一个签名,包括名称和任何参数和一个切入点表达式,用于准确确定我们感兴趣的方法执行。在 AOP 的 @AspectJ 注释样式中,切入点签名由常规方法定义提供,切入点表达式是通过使用@Pointcut注释(用作切入点签名的方法必须有一个void返回类型)。spring-doc.cadn.net.cn

一个示例可能有助于区分切入点特征和切入点表达式。以下示例定义了名为anyOldTransfer那 匹配任何名为transfer:spring-doc.cadn.net.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

形成@Pointcut注释是常规的 AspectJ 切入点表达式。有关 AspectJ 切入点语言的完整讨论,请参阅 AspectJ 的 编程指南(以及对于扩展的 AspectJ 5 Developer's Notebook)或有关 AspectJ 的书籍之一(例如 Colyer 的 Eclipse AspectJ) 等人,或 AspectJ in Action,作者:Ramnivas Laddad)。spring-doc.cadn.net.cn

支持的切入点标号

Spring AOP 支持以下 AspectJ 切入点标号 (PCD) 用于切入点 表达 式:spring-doc.cadn.net.cn

  • execution:用于匹配方法执行连接点。这是主要的 使用 Spring AOP 时使用的切入点指示符。spring-doc.cadn.net.cn

  • within:限制匹配到某些类型(执行 使用 Spring AOP 时在匹配类型中声明的方法)。spring-doc.cadn.net.cn

  • this:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中 bean 引用(Spring AOP 代理)是给定类型的实例。spring-doc.cadn.net.cn

  • target:限制与连接点的匹配(使用 Spring AOP),其中目标对象(被代理的应用程序对象)是一个实例 给定类型的。spring-doc.cadn.net.cn

  • args:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中参数是给定类型的实例。spring-doc.cadn.net.cn

  • @target:限制与连接点的匹配(使用 Spring AOP),其中执行对象的类具有给定类型的注释。spring-doc.cadn.net.cn

  • @args:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。spring-doc.cadn.net.cn

  • @within:限制匹配以连接具有给定 注释(在类型中声明的方法在给定注释时 使用 Spring AOP)。spring-doc.cadn.net.cn

  • @annotation:将匹配限制为连接点的主体 (在 Spring AOP 中运行的方法)具有给定的注释。spring-doc.cadn.net.cn

其他切入点类型

完整的 AspectJ 切入点语言支持其他切入点指示符,但这些标号不是 在 Spring 中支持:call,get,set,preinitialization,staticinitialization,initialization,handler,adviceexecution,withincode,cflow,cflowbelow,if,@this@withincode.在切入点中使用这些切入点指示符 Spring AOP 解释的表达式会导致IllegalArgumentException存在 扔。spring-doc.cadn.net.cn

Spring AOP 支持的切入点指示符集将来可能会扩展 版本以支持更多 AspectJ 切入点指示符。spring-doc.cadn.net.cn

由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论 的切入点指示符给出的定义比您在 AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且 执行连接点,两者thistarget引用同一对象:该 执行该方法的对象。Spring AOP 是一个基于代理的系统,并区分了 在代理对象本身之间(绑定到this)和 proxy(绑定到target).spring-doc.cadn.net.cn

由于 Spring 的 AOP 框架基于代理的性质,目标对象内的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方式 代理上的调用可以被拦截。使用 CGLIB 时,公共和受保护的方法调用 代理被拦截(如有必要,甚至包可见的方法)。然而 通过代理进行的常见交互应始终通过公共签名进行设计。spring-doc.cadn.net.cn

请注意,切入点定义通常与任何截获的方法相匹配。如果切入点严格意义上是仅限公开的,即使在具有通过代理进行潜在的非公开交互的 CGLIB 代理场景中,也需要相应地定义它。spring-doc.cadn.net.cn

如果您的拦截需要包括目标中的方法调用甚至构造函数 类,考虑使用 Spring 驱动的原生 AspectJ 编织来代替 Spring 基于代理的 AOP 框架。这构成了一种不同的AOP使用模式 具有不同的特点,所以一定要让自己熟悉编织 在做出决定之前。spring-doc.cadn.net.cn

Spring AOP 还支持一个名为bean.此 PCD 可让您限制 连接点与特定命名的 Spring Bean 或一组命名的 Spring bean(使用通配符时)。这beanPCD有以下形式:spring-doc.cadn.net.cn

Java
bean(idOrNameOfBean)
Kotlin
bean(idOrNameOfBean)

idOrNameOfBeantoken 可以是任何 Spring Bean 的名称。有限通配符 提供了使用该字符的支持,因此,如果您建立一些命名 conventions 中,您可以编写一个*beanPCD表达 以选择它们。与其他切入点指示符一样,该beanPCD罐 与 (and) 一起使用,&&||(或),以及!(否定)运算符也是如此。spring-doc.cadn.net.cn

beanPCD 仅在 Spring AOP 中受支持 原生 AspectJ 编织。它是标准 PCD 的 Spring 特定扩展,它 AspectJ 定义了,因此不适用于@Aspect型。spring-doc.cadn.net.cn

beanPCD 在实例级别运行(基于 Spring bean 名称 概念),而不是仅在类型级别(基于编织的 AOP 仅限于该级别)。 基于实例的切入点指示符是 Spring 的 基于代理的 AOP 框架及其与 Spring bean 工厂的紧密集成,其中 按名称识别特定豆子是自然而直接的。spring-doc.cadn.net.cn

组合切入点表达式

您可以使用以下命令组合切入点表达式&&, ||!.也可以参考 按名称切割点表达式。以下示例显示了三个切入点表达式:spring-doc.cadn.net.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如果方法执行连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在交易模块中,则匹配。
3 tradingOperation如果方法执行代表 交易模块。
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如果方法执行连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在交易模块中,则匹配。
3 tradingOperation如果方法执行代表 交易模块。

最佳实践是从较小的命名中构建更复杂的切入点表达式 组件,如前所示。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以看到相同类型的私有切入点,在 层次结构、任何地方的公共切入点等)。可见性不影响切入点 匹配。spring-doc.cadn.net.cn

共享通用切入点定义

在使用企业应用程序时,开发人员通常希望从多个方面引用应用程序和特定作集的模块。 我们 建议定义一个CommonPointcuts捕获常见切入点表达式的方面为此目的。此类方面通常类似于以下示例:spring-doc.cadn.net.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() {
    }

}

您可以在需要pointcut 表达式的地方引用在此类方面中定义的切入点。例如,要使服务层具有事务性,您可以编写以下内容:spring-doc.cadn.net.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>

<aop:config><aop:advisor>元素在基于架构的AOP支持中进行了讨论。这 事务元素在事务管理中进行了讨论。spring-doc.cadn.net.cn

例子

Spring AOP 用户可能会使用execution切入点指示符最常使用。 执行表达式的格式如下:spring-doc.cadn.net.cn

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)

除返回类型模式 (ret-type-pattern在前面的片段中), 名称模式和参数模式是可选的。返回类型模式确定 方法的返回类型必须是什么才能匹配连接点。 最常用作返回类型模式。它匹配任何返回 类型。仅当方法返回给定的 类型。名称模式与方法名称匹配。可以将通配符用作 all 或 名称模式的一部分。如果指定声明类型模式, 包括尾随**.将其连接到名称模式组件。 参数模式稍微复杂一些:匹配 方法,而不采用任何参数,而()(..)匹配任意数量(零个或多个)参数。 该模式匹配采用任何类型的一个参数的方法。(*)(*,String)匹配接受两个参数的方法。第一个可以是任何类型,而 second 必须是String.咨询语言 AspectJ 编程指南的语义部分了解更多信息。spring-doc.cadn.net.cn

以下示例显示了一些常见的切入点表达式:spring-doc.cadn.net.cn

  • 任何公共方法的执行:spring-doc.cadn.net.cn

        execution(public * *(..))
  • 名称以set:spring-doc.cadn.net.cn

        execution(* set*(..))
  • 执行由AccountService接口:spring-doc.cadn.net.cn

        execution(* com.xyz.service.AccountService.*(..))
  • 执行service包:spring-doc.cadn.net.cn

        execution(* com.xyz.service.*.*(..))
  • 执行服务包或其子包之一中定义的任何方法:spring-doc.cadn.net.cn

        execution(* com.xyz.service..*.*(..))
  • 服务包中的任何连接点(仅在 Spring AOP 中执行方法):spring-doc.cadn.net.cn

        within(com.xyz.service.*)
  • 服务包内的任何连接点(仅在 Spring AOP 中执行方法)或其 子包:spring-doc.cadn.net.cn

        within(com.xyz.service..*)
  • 代理实现AccountService接口:spring-doc.cadn.net.cn

        this(com.xyz.service.AccountService)
    this更常以装订形式使用。请参阅声明通知部分,了解如何使代理对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中目标对象 实现AccountService接口:spring-doc.cadn.net.cn

        target(com.xyz.service.AccountService)
    target更常以装订形式使用。请参阅声明建议部分 了解如何使目标对象在通知正文中可用。
  • 任何接受单个参数的连接点(仅在 Spring AOP 中执行方法) 运行时传递的参数是Serializable:spring-doc.cadn.net.cn

        args(java.io.Serializable)
    args更常以装订形式使用。请参阅声明建议部分 了解如何在通知正文中使方法参数可用。

    请注意,本例中给出的切入点与execution(* *(java.io.Serializable)).如果运行时传递的参数为Serializable,如果方法签名声明单个 type 的参数Serializable.spring-doc.cadn.net.cn

  • 任何连接点(仅在 Spring AOP 中执行方法),其中目标对象具有@Transactional注解:spring-doc.cadn.net.cn

        @target(org.springframework.transaction.annotation.Transactional)
    您还可以使用@target以装订形式。请参阅声明建议部分 如何使注释对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中声明的类型为 target 对象有一个@Transactional注解:spring-doc.cadn.net.cn

        @within(org.springframework.transaction.annotation.Transactional)
    您还可以使用@within以装订形式。请参阅声明建议部分 如何使注释对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中执行方法具有@Transactional注解:spring-doc.cadn.net.cn

        @annotation(org.springframework.transaction.annotation.Transactional)
    您还可以使用@annotation以装订形式。请参阅声明建议部分 了解如何使注释对象在通知正文中可用。
  • 任何接受单个参数的连接点(仅在 Spring AOP 中执行方法) 传递的参数的运行时类型具有@Classified注解:spring-doc.cadn.net.cn

        @args(com.xyz.security.Classified)
    您还可以使用@args以装订形式。请参阅声明建议部分 如何在通知正文中使注释对象可用。
  • 名为 Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法)tradeService:spring-doc.cadn.net.cn

        bean(tradeService)
  • Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法)具有匹配通配符表达式*Service:spring-doc.cadn.net.cn

        bean(*Service)
写好的切入点

在编译过程中,AspectJ 处理切入点以优化匹配 性能。 检查代码并确定每个连接点是否匹配(静态或动态)给定的切入点是一个成本高昂的过程。(动态匹配意味着匹配无法从静态分析中完全确定,并且在代码中放置了一个测试,以确定代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ 将其重写为匹配的最佳形式 过程。 这是什么意思?基本上,切入点是用 DNF(析取范式)重写的,切入点的组件被排序,以便那些组件首先检查评估成本较低的组件。这意味着您不必担心关于了解各种切入点指示符的性能并可能提供它们在切入点声明中以任何顺序。spring-doc.cadn.net.cn

但是,AspectJ 只能使用它所告知的内容。为了获得最佳性能matching,您应该考虑他们试图实现的目标并缩小搜索范围定义中尽可能多的匹配空间。现有的指示符自然分为三组之一:kinded、sscopeing 和 contextual:spring-doc.cadn.net.cn

  • kinded 指示符选择特定类型的连接点:execution,get,set,callhandler.spring-doc.cadn.net.cn

  • 范围标号选择一组连接感兴趣点 (可能有很多种):withinwithincodespring-doc.cadn.net.cn

  • 上下文指示符根据上下文匹配(并可选择绑定):this,target@annotationspring-doc.cadn.net.cn

写得好的切入点应至少包括前两种类型(kinded 和 范围界定)。您可以包括要基于的上下文指示符进行匹配 连接点上下文或绑定该上下文以在通知中使用。仅提供一个 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),这是由于额外的处理和分析。范围 指示符的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速 消除不应进一步处理的连接点组。一个好的 如果可能的话,切入点应始终包含一个。spring-doc.cadn.net.cn

5.4.4. 声明通知

通知与切入点表达式相关联,并在切入点表达式之前、之后或周围运行 与切入点匹配的方法执行。切入点表达式可以是 对命名点切入或就地声明的切入点表达式的简单引用。spring-doc.cadn.net.cn

建议前

您可以使用@Before注解:spring-doc.cadn.net.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.cadn.net.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() {
        // ...
    }
}
退货后建议

返回通知后,当匹配的方法执行正常返回时运行。 您可以使用@AfterReturning注解:spring-doc.cadn.net.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() {
        // ...
    }
}
您可以有多个建议声明(以及其他成员), 都在同一个方面。我们在这些中仅显示一个建议声明 示例来集中每个效果。

有时,您需要在通知正文中访问返回的实际值。 您可以使用@AfterReturning绑定返回值以获取 访问权限,如以下示例所示:spring-doc.cadn.net.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) {
        // ...
    }
}

中使用的名称returning属性必须对应于参数的名称 在建议方法中。当方法执行返回时,返回值将传递给 advice 方法作为相应的参数值。一个returning子句 将匹配限制为仅返回 指定的类型(在本例中,Object,与任何返回值匹配)。spring-doc.cadn.net.cn

请注意,在以下情况下,不可能返回完全不同的引用 返回建议后使用。spring-doc.cadn.net.cn

抛出建议后

抛出建议后,当匹配的方法执行退出时,通过抛出 例外。您可以使用@AfterThrowing注释,作为 以下示例显示:spring-doc.cadn.net.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() {
        // ...
    }
}

通常,您希望通知仅在抛出给定类型的异常时运行,并且您还经常需要访问通知正文中抛出的异常。 您可以 使用throwing属性来限制匹配(如果需要 - 使用Throwable否则作为异常类型),并将抛出的异常绑定到 advice 参数。以下示例显示了如何执行此作:spring-doc.cadn.net.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) {
        // ...
    }
}

中使用的名称throwing属性必须对应于通知方法中的参数名称。当方法执行通过抛出异常退出时,异常作为相应的参数值传递给通知方法。 一个throwing第 还将匹配限制为仅抛出 指定类型 (DataAccessException,在本例中)。spring-doc.cadn.net.cn

请注意@AfterThrowing不指示常规异常处理回调。 具体来说,一个@AfterThrowingadvice 方法只应该接收异常 从连接点(用户声明的目标方法)本身,而不是从随附的@After/@AfterReturning方法。spring-doc.cadn.net.cn

在(最后)建议之后

After (finally) 通知在匹配的方法执行退出时运行。它是由 使用@After注解。在建议后必须准备好处理正常和 异常返回条件。它通常用于释放资源等 目的。以下示例显示了如何使用 after finally 建议:spring-doc.cadn.net.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() {
        // ...
    }
}

请注意@AfterAspectJ 中的建议被定义为“在最终建议之后”,类似 到 try-catch 语句中的 finally 块。它将被调用用于任何结果, 从连接点抛出的正常返回或异常(用户声明的目标方法), 与@AfterReturning这仅适用于成功的正常回报。spring-doc.cadn.net.cn

周边建议

最后一种建议是围绕建议。围绕建议“围绕”匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行并确定何时、如何运行,甚至该方法是否真的可以运行。 如果您需要在方法之前和之后共享状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。spring-doc.cadn.net.cn

始终使用满足您要求的最不强大的建议形式。spring-doc.cadn.net.cn

例如,如果之前的建议足以满足您的需求,请不要使用周围建议。spring-doc.cadn.net.cn

Around 建议是通过使用@Around注解。这 方法应声明Object作为其返回类型,以及方法的第一个参数 必须是ProceedingJoinPoint.在建议方法的正文中,您必须 调用proceed()ProceedingJoinPoint为了让底层方法 跑。调用proceed()没有参数将导致调用方的原始 调用基础方法时提供给底层方法的参数。高级使用 情况下,存在proceed()方法,该方法接受 参数 (Object[]).数组中的值将用作 调用 underlying 方法。spring-doc.cadn.net.cn

的行为proceed当使用Object[]与 的行为proceed关于 AspectJ 编译器编译的建议。对于大约 使用传统 AspectJ 语言编写的建议,传递给proceed必须与传递给 around 通知的参数数匹配(而不是数字 基础连接点所采用的参数),以及传递的值以在 给定参数位置替换实体连接点处的原始值 value 被绑定为(如果这现在没有意义,请不要担心)。spring-doc.cadn.net.cn

Spring 采用的方法更简单,与其基于代理的 仅执行语义。只有在编译@AspectJ为 Spring 编写的方面和使用proceed与 AspectJ 的参数 编译器和编织器。有一种方法可以编写 100% 兼容的此类方面 Spring AOP 和 AspectJ,这将在以下关于通知参数的部分中讨论。spring-doc.cadn.net.cn

周围通知返回的值是调用者看到的返回值 方法。例如,如果简单缓存方面具有 one 或调用proceed()(并返回该值)。请注意proceed可以在周围建议的正文中调用一次、多次或根本不调用。都 其中是合法的。spring-doc.cadn.net.cn

如果将 around 建议方法的返回类型声明为void,null将始终返回给调用者,从而有效地忽略任何调用的结果 之proceed().因此,建议使用 around 通知方法声明返回 类型Object.通知方法通常应返回从 调用proceed(),即使基础方法具有void返回类型。 但是,通知可以选择返回缓存值、包装值或其他一些值 值取决于用例。

以下示例显示了如何使用 around 建议:spring-doc.cadn.net.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
    }
}
通知参数

Spring 提供完全类型的建议,这意味着您可以在 advice 签名(正如我们之前在返回和抛出示例中看到的那样),而不是 与Object[]数组。我们看到如何进行论证和其他上下文 本节后面的建议机构可用的值。首先,我们来看看如何 写通用建议,了解建议当前建议的方法。spring-doc.cadn.net.cn

访问当前JoinPoint

任何通知方法都可以声明类型为org.aspectj.lang.JoinPoint.请注意,声明第一个需要周围建议 type 的参数ProceedingJoinPoint,它是JoinPoint.spring-doc.cadn.net.cn

JoinPointinterface 提供了许多有用的方法:spring-doc.cadn.net.cn

有关更多详细信息,请参阅 javadocspring-doc.cadn.net.cn

将参数传递给通知

我们已经看到了如何绑定返回值或异常值(使用 after 返回并在抛出建议后)。使参数值可用于通知 body,可以使用args.如果使用参数名称代替 类型名称argsexpression,则相应参数的值传递为 调用通知时的参数值。一个例子应该使这一点更清楚。 假设你想建议执行 DAO作,这些作需要Accountobject 作为第一个参数,并且您需要访问通知正文中的帐户。 你可以写以下内容:spring-doc.cadn.net.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) {
    // ...
}

args(account,..)切入点表达式的一部分有两个用途。首先,它 将匹配限制为仅那些方法执行,其中该方法至少采用一个 参数,传递给该参数的参数是Account. 其次,它使实际的Account对象,通过account参数。spring-doc.cadn.net.cn

另一种编写方式是声明一个切入点,该切入点“提供”了Account对象值,然后引用命名的切入点 从建议。这将如下所示:spring-doc.cadn.net.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.cadn.net.cn

代理对象 (this)、目标对象 (target)和注释(@within,@target,@annotation@args)都可以以类似的方式绑定。下一个 两个示例展示了如何匹配用@Auditable注释并提取审计代码:spring-doc.cadn.net.cn

两个示例中的第一个示例显示了@Auditable注解:spring-doc.cadn.net.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)

两个示例中的第二个示例显示了与执行相匹配的建议@Auditable方法:spring-doc.cadn.net.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.cadn.net.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 参数绑定到要截获方法的参数类型:spring-doc.cadn.net.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.cadn.net.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
}

为了做到这一点,我们必须检查集合的每个元素,这不是 合理,因为我们也无法决定如何治疗null一般值。实现 与此类似,您必须将参数键入Collection<?>和手动 检查元素的类型。spring-doc.cadn.net.cn

确定参数名称

通知调用中的参数绑定依赖于匹配切入点中使用的名称 表达式添加到通知和切入点方法签名中声明的参数名称。spring-doc.cadn.net.cn

本节可以互换使用术语参数参数,因为 AspectJ API 将参数名称称为参数名称。

Spring AOP 使用以下内容ParameterNameDiscoverer要确定的实现 参数名称。每个发现者都有机会发现参数名称,并且 第一个成功的发现者获胜。如果注册的发现者都不具备能力 确定参数名称时,将抛出异常。spring-doc.cadn.net.cn

AspectJAnnotationParameterNameDiscoverer

使用显式的参数名称 由用户通过argNames属性或 切入点注释。有关详细信息,请参阅显式参数名称spring-doc.cadn.net.cn

KotlinReflectionParameterNameDiscoverer

使用 Kotlin 反射 API 确定 参数名称。仅当类路径上存在此类 API 时,才使用此发现器。 GraalVM 本机映像不支持。spring-doc.cadn.net.cn

StandardReflectionParameterNameDiscoverer

使用标准java.lang.reflect.Parameter用于确定参数名称的 API。要求使用-parameters标志javac.Java 8+ 上的推荐方法。spring-doc.cadn.net.cn

LocalVariableTableParameterNameDiscoverer

分析可用的局部变量表 在通知类的字节码中,从调试信息中确定参数名称。 要求使用调试符号 (-g:vars至少)。荒废的 从 Spring Framework 6.0 开始,在 Spring Framework 6.1 中删除,有利于编译 code 替换为-parameters.GraalVM 本机映像不支持,除非相应的 类文件作为资源存在于映像中。spring-doc.cadn.net.cn

AspectJAdviceParameterNameDiscoverer

从切入点推断参数名称 表达returningthrowing第。有关所用算法的详细信息,请参阅 javadocspring-doc.cadn.net.cn

显式参数名称

@AspectJ建议和切入点注释具有可选的argNames属性 可用于指定带注释的方法的参数名称。spring-doc.cadn.net.cn

@AspectJ如果 AspectJ 编译器(ajc) 即使没有 debug 信息,则无需添加argNames属性,因为编译器 保留所需的信息。spring-doc.cadn.net.cn

同样,如果@AspectJ方面已使用javac使用-parameters标志,则无需添加argNames属性,因为编译器保留了 需要的信息。spring-doc.cadn.net.cn

以下示例演示如何使用argNames属性:spring-doc.cadn.net.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
}

如果第一个参数的类型为JoinPoint,ProceedingJoinPointJoinPoint.StaticPart,您可以从argNames属性。例如,如果修改上述通知以接收联接 point 对象,则argNames属性不需要包含它:spring-doc.cadn.net.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
}

对类型的第一个参数给予的特殊处理JoinPoint,ProceedingJoinPointJoinPoint.StaticPart特别方便建议 不收集任何其他连接点上下文的方法。在这种情况下,您可以 省略argNames属性。例如,以下建议不需要声明 这argNames属性:spring-doc.cadn.net.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
}
继续使用参数

我们之前提到过,我们将描述如何编写一个proceed调用 在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是 以确保通知签名按顺序绑定每个方法参数。 以下示例显示了如何执行此作:spring-doc.cadn.net.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.cadn.net.cn

建议订购

当多个建议都想在同一连接点运行时会发生什么? Spring AOP 遵循与 AspectJ 相同的优先级规则来确定通知的顺序 执行。最高优先级建议首先“在进入的路上”运行(因此,给定两块 之前的建议,优先级最高的优先顺序)。“在离开的路上”来自一个 连接点,最高优先级通知最后运行(因此,给定两个 After 建议,优先级最高的将排在第二位)。spring-doc.cadn.net.cn

当在不同方面定义的两条建议都需要同时运行时 join point,除非另有指定,否则执行顺序是未定义的。您可以 通过指定优先级来控制执行顺序。这是在正常情况下完成的 Spring 方式,通过实现org.springframework.core.Ordered界面 aspect 类或使用@Order注解。给定两个方面, aspect 从Ordered.getOrder()(或注释值)具有 优先级越高。spring-doc.cadn.net.cn

特定方面的每种不同建议类型在概念上都是要适用的 直接到连接点。因此,一个@AfterThrowing建议方法不是 应该从随附的@After/@AfterReturning方法。spring-doc.cadn.net.cn

从 Spring Framework 5.2.7 开始,在相同的@Aspect类 需要在同一连接点运行的优先级根据其通知类型在 以下顺序,从最高到最低优先级:@Around,@Before,@After,@AfterReturning,@AfterThrowing.但是请注意,一个@After建议方法将 在任何之后有效调用@AfterReturning@AfterThrowing建议方法 在同一方面,遵循 AspectJ 的“after finally advice”语义@After.spring-doc.cadn.net.cn

当两个相同类型的建议(例如,两个@After建议方法) 在相同的@Aspect类都需要在同一个连接点运行,排序 是未定义的(因为没有办法通过 javac 编译类的反射)。考虑将此类建议方法合并为一种 每个连接点的通知方法@Aspect将建议片段类化或重构为 分开@Aspect您可以通过以下方式在方面级别排序的类Ordered@Order.spring-doc.cadn.net.cn

5.4.5. 简介

引言(在 AspectJ 中称为类型间声明)使切面能够声明 建议对象实现给定接口,并提供 该接口代表这些对象。spring-doc.cadn.net.cn

您可以使用@DeclareParents注解。这个注释 用于声明匹配类型具有新的父级(因此得名)。例如 给定一个名为UsageTracked以及该接口的实现,名为DefaultUsageTracked,以下方面声明服务的所有实现者 接口还实现了UsageTracked接口(例如,通过 JMX 进行统计):spring-doc.cadn.net.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()
    }
}

要实现的接口由带注释的字段的类型决定。这value属性的@DeclareParents注释是 AspectJ 类型的模式。任何 匹配类型的 bean 实现了UsageTracked接口。请注意,在 在前面示例的建议之前,服务 bean 可以直接用作 的实现UsageTracked接口。如果以编程方式访问 bean, 你会写下:spring-doc.cadn.net.cn

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.4.6. 方面实例化模型

这是一个高级主题。如果您刚刚开始使用 AOP,您可以放心地跳过 直到以后。

默认情况下,应用程序中每个方面都有一个实例 上下文。AspectJ 称之为单例实例化模型。可以定义 具有替代生命周期的方面。Spring 支持 AspectJ 的perthispertarget实例化模型;percflow,percflowbelowpertypewithin目前没有 支持。spring-doc.cadn.net.cn

您可以声明一个perthisaspect 通过指定perthis子句中的@Aspect注解。请考虑以下示例:spring-doc.cadn.net.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() {
        // ...
    }
}

在前面的示例中,的perthis子句是一个方面实例 为执行业务服务的每个唯一服务对象创建 (每个唯一的 绑定到this在与切入点表达式匹配的连接点处)。方面 instance 在首次在服务对象上调用方法时创建。这 当服务对象超出范围时,方面将超出范围。方面之前 实例创建时,其中的任何通知都不会运行。一旦方面实例 ,则其中声明的通知在匹配的连接点运行,但仅 当服务对象是与此方面关联的对象时。查看 AspectJ 编程指南,了解更多信息per第。spring-doc.cadn.net.cn

pertarget实例化模型的工作方式与perthis,但它 在匹配的连接点处为每个唯一目标对象创建一个方面实例。spring-doc.cadn.net.cn

5.4.7. AOP 示例

现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起来做 有用的东西。spring-doc.cadn.net.cn

业务服务的执行有时会由于并发问题而失败(对于 例如,死锁失败者)。如果重试作,则很可能会成功 在下一次尝试中。对于适合重试的业务服务 条件(冪等作,不需要返回给用户进行冲突 resolution),我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException.这是一个明显跨越的要求 服务层中的多个服务,因此非常适合通过 方面。spring-doc.cadn.net.cn

因为我们想重试作,所以我们需要使用 around 建议,以便我们可以 叫proceed多次。以下列表显示了基本方面实现:spring-doc.cadn.net.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
    }
}

请注意,该方面实现了Ordered接口,以便我们可以设置 高于交易建议的方面(我们每次都希望有新的交易 重试)。这maxRetriesorder属性都由 Spring 配置。这 主要作发生在doConcurrentOperation围绕建议。请注意,对于 moment,我们将重试逻辑应用于每个businessService().我们尝试继续, 如果我们失败了PessimisticLockingFailureException,我们重试,除非 我们已经用尽了所有的重试尝试。spring-doc.cadn.net.cn

相应的 Spring 配置如下:spring-doc.cadn.net.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>

为了优化切面,使其仅重试幂等作,我们可以定义以下内容Idempotent注解:spring-doc.cadn.net.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation

然后,我们可以使用注释来注释服务作的实现。变化 到仅重试幂等作的方面涉及细化切入点 表达式,以便仅@Idempotent作匹配,如下所示:spring-doc.cadn.net.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. 基于模式的AOP支持

如果您更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持 使用aopnamespace 标签。完全相同的切入点表达式和建议类型 与使用@AspectJ样式一样,支持。因此,在本节中,我们将重点关注 该语法,并让读者参考上一节中的讨论 (@AspectJ支持)用于理解编写切入点表达式和绑定 的建议参数。spring-doc.cadn.net.cn

要使用本节中描述的 aop 命名空间标签,您需要导入spring-aopschema,如基于 XML Schema 的配置中所述。请参阅AOP架构,了解如何导入aopNamespace。spring-doc.cadn.net.cn

在 Spring 配置中,所有方面和顾问元素都必须放置在 一<aop:config>元素(可以有多个<aop:config>元素中的 应用程序上下文配置)。一<aop:config>元素可以包含切入点, advisor 和 aspect 元素(请注意,这些元素必须按该顺序声明)。spring-doc.cadn.net.cn

<aop:config>配置样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议 not being woven),如果你已经通过使用BeanNameAutoProxyCreator或类似的东西。推荐的使用模式是 仅使用<aop:config>style 或仅AutoProxyCreatorstyle 和 永远不要混合它们。

5.5.1. 声明切面

当您使用模式支持时,方面是定义为 bean 的常规 Java 对象 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 对象的方法,切入点和通知信息被捕获在 XML 中。spring-doc.cadn.net.cn

您可以使用<aop:aspect>元素,并引用后备 bean 通过使用ref属性,如以下示例所示:spring-doc.cadn.net.cn

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

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

支持方面 (aBean在这种情况下)当然可以配置和 像任何其他 Spring Bean 一样注入依赖。spring-doc.cadn.net.cn

5.5.2. 声明切入点

您可以在<aop:config>元素,让切入点 定义在多个方面和顾问之间共享。spring-doc.cadn.net.cn

表示服务层中任何业务服务执行的切入点可以 定义如下:spring-doc.cadn.net.cn

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式 @AspectJ支持中所述的语言。如果使用基于架构的声明 样式时,您可以引用类型 (@Aspects) 中定义的命名点切入 切入点表达式。定义上述切入点的另一种方法如下:spring-doc.cadn.net.cn

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>

</aop:config>

假设你有一个CommonPointcuts方面,如共享公共切入点定义中所述。spring-doc.cadn.net.cn

然后,在切面内声明切入点与声明顶级切入点非常相似, 如以下示例所示:spring-doc.cadn.net.cn

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...
    </aop:aspect>

</aop:config>

与@AspectJ方面大致相同,使用基于模式的 定义样式可以收集连接点上下文。例如,以下切入点 收集thisobject 作为连接点上下文并将其传递给通知:spring-doc.cadn.net.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.cadn.net.cn

Java
public void monitor(Object service) {
    // ...
}
Kotlin
fun monitor(service: Any) {
    // ...
}

组合切入点子表达式时,&amp;&amp;在 XML 中很尴尬 文档,因此您可以使用and,ornot关键字代替&amp;&amp;,||!分别。例如,前面的切入点可以更好地写成 遵循:spring-doc.cadn.net.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 引用id并且不能是 用作命名点切入以形成复合切入点。中的命名切入点支座 因此,基于模式的定义风格比@AspectJ提供的定义风格更受限制 风格。spring-doc.cadn.net.cn

5.5.3. 声明通知

基于模式的 AOP 支持使用与 @AspectJ 样式相同的五种建议,并且它们具有 完全相同的语义。spring-doc.cadn.net.cn

建议前

before 通知在匹配的方法执行之前运行。它是在<aop:aspect>通过使用<aop:before>元素,如以下示例所示:spring-doc.cadn.net.cn

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

这里dataAccessOperationid在顶部定义的切入点 (<aop:config>) 水平。要改为内联定义切入点,请将pointcut-ref属性替换为 一个pointcut属性,如下所示:spring-doc.cadn.net.cn

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...
</aop:aspect>

正如我们在讨论@AspectJ样式时所指出的,使用命名切入点可以 显着提高代码的可读性。spring-doc.cadn.net.cn

method属性标识方法(doAccessCheck) 提供 建议。必须为方面元素引用的 bean 定义此方法 其中包含建议。在执行数据访问作之前(方法 与切入点表达式匹配的连接点),则doAccessCheck方面方法 bean 被调用。spring-doc.cadn.net.cn

退货后建议

返回通知后,当匹配的方法执行正常完成时运行。是的 在<aop:aspect>与之前的建议相同。以下示例 显示如何声明它:spring-doc.cadn.net.cn

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...
</aop:aspect>

与@AspectJ样式一样,您可以在通知正文中获取返回值。 为此,请使用returning属性来指定参数的名称,以指定该参数的名称。 应传递返回值,如以下示例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...
</aop:aspect>

doAccessCheck方法必须声明一个名为retVal.这个的类型 参数以与@AfterReturning.为 例如,您可以按如下方式声明方法签名:spring-doc.cadn.net.cn

Java
public void doAccessCheck(Object retVal) {...
Kotlin
fun doAccessCheck(retVal: Any) {...
抛出建议后

抛出建议后,当匹配的方法执行退出时,通过抛出 例外。它是在<aop:aspect>通过使用after-throwing元素 如以下示例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

与@AspectJ样式一样,您可以在通知正文中获取抛出的异常。 为此,请使用throwing属性,以指定参数的名称 应传递异常,如以下示例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

doRecoveryActions方法必须声明一个名为dataAccessEx. 此参数的类型以与@AfterThrowing.例如,方法签名可以声明如下:spring-doc.cadn.net.cn

Java
public void doRecoveryActions(DataAccessException dataAccessEx) {...
Kotlin
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
在(最后)建议之后

在(最终)通知运行之后,无论匹配的方法执行如何退出。 您可以使用after元素,如以下示例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...
</aop:aspect>
周边建议

最后一种建议是围绕建议。围绕建议“围绕”匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行并确定何时、如何运行,甚至该方法是否真的可以运行。 如果您需要在方法之前和之后共享状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。spring-doc.cadn.net.cn

始终使用满足您要求的最不强大的建议形式。spring-doc.cadn.net.cn

例如,如果之前的建议足以满足您的需求,请不要使用周围建议。spring-doc.cadn.net.cn

您可以使用aop:around元素。建议方法应 宣Object作为其返回类型,并且该方法的第一个参数必须是 类型ProceedingJoinPoint.在通知方法的正文中,您必须调用proceed()ProceedingJoinPoint以便运行底层方法。 调用proceed()没有参数将导致调用者的原始参数 在调用基础方法时提供给它。对于高级用例,有 是proceed()接受参数数组的方法 (Object[]).数组中的值将用作基础 方法。有关通话的注意事项,请参阅 Around Adviceproceed使用Object[].spring-doc.cadn.net.cn

以下示例显示了如何在 XML 中声明通知:spring-doc.cadn.net.cn

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...
</aop:aspect>

的实现doBasicProfiling建议可以与 @AspectJ示例(当然,减去注释),如以下示例所示:spring-doc.cadn.net.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()
}
通知参数

基于模式的声明样式支持完全类型的通知,其方式与 描述@AspectJ支持——通过按名称匹配切入点参数 通知方法参数。有关详细信息,请参阅通知参数。如果您愿意 显式指定通知方法的参数名称(不依赖于 检测策略),您可以使用arg-names属性,其处理方式与argNames属性(如确定参数名称中所述)。 以下示例演示如何在 XML 中指定参数名称:spring-doc.cadn.net.cn

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

arg-names属性接受以逗号分隔的参数名称列表。spring-doc.cadn.net.cn

下面稍微更复杂的基于 XSD 的方法示例显示了 一些与许多强类型参数结合使用的建议:spring-doc.cadn.net.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)
    }
}

接下来是方面。请注意,该profile(..)方法接受许多 强类型参数,其中第一个恰好是用于 继续方法调用。此参数的存在表明profile(..)用作around建议,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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.cadn.net.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.cadn.net.cn

StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)
建议订购

当需要在同一连接点运行多个通知时(执行方法) 排序规则如通知排序中所述。优先顺序 between aspect 是通过order属性中的<aop:aspect>元素或 通过添加@Order对支持该方面的 bean 进行注释,或者通过 bean 实现了Ordered接口。spring-doc.cadn.net.cn

与同一@Aspect类,当两个建议在同一个<aop:aspect>元素都需要 在同一连接点运行,则优先级由通知 元素在封闭<aop:aspect>元素,从最高到最低 优先。spring-doc.cadn.net.cn

例如,给定一个around建议和before建议在相同的<aop:aspect>元素,以确保around建议的优先级高于before建议,<aop:around>元素必须是 在<aop:before>元素。spring-doc.cadn.net.cn

作为一般经验法则,如果您发现您定义了多条建议 在同一个<aop:aspect>元素,请考虑折叠 将此类通知方法转换为每个连接点中的一个通知方法<aop:aspect>元素 或将建议片段重构为单独的<aop:aspect>您可以订购的元素 在方面层面。spring-doc.cadn.net.cn

5.5.4. 简介

引言(在 AspectJ 中称为类型间声明)让切面声明 建议对象实现给定接口并提供 该接口代表这些对象。spring-doc.cadn.net.cn

您可以使用aop:declare-parents元素aop:aspect. 您可以使用aop:declare-parents元素来声明匹配类型具有新的父级(因此得名)。 例如,给定一个名为UsageTracked以及该接口的实现,名为DefaultUsageTracked,以下方面声明服务的所有实现者 接口还实现了UsageTracked接口。(为了曝光统计数据 例如,通过 JMX。spring-doc.cadn.net.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>

支持usageTracking然后 bean 将包含以下方法:spring-doc.cadn.net.cn

Java
public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
Kotlin
fun recordUsage(usageTracked: UsageTracked) {
    usageTracked.incrementUseCount()
}

要实现的接口由implement-interface属性。这 的值types-matching属性是 AspectJ 类型模式。任何 bean matching 类型实现了UsageTracked接口。请注意,在之前 前面示例的建议,Service Bean 可以直接用作 这UsageTracked接口。要以编程方式访问 bean,您可以编写 以后:spring-doc.cadn.net.cn

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.5.5. 方面实例化模型

模式定义方面唯一支持的实例化模型是单例 型。将来的版本可能会支持其他实例化模型。spring-doc.cadn.net.cn

5.5.6. 顾问

“顾问”的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接等价物。顾问就像一个小 独立的方面,只有一条建议。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的通知接口之一。顾问可以利用 AspectJ 切入点表达式。spring-doc.cadn.net.cn

Spring 通过<aop:advisor>元素。你最 通常看到它与交易建议结合使用,交易建议也有自己的 Spring 中的命名空间支持。以下示例显示了顾问:spring-doc.cadn.net.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-ref属性,也可以使用pointcut属性来定义内联的切入点表达式。spring-doc.cadn.net.cn

要定义顾问的优先级,以便通知可以参与排序, 使用order属性来定义Ordered顾问的价值。spring-doc.cadn.net.cn

5.5.7. AOP 模式示例

本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。spring-doc.cadn.net.cn

业务服务的执行有时会由于并发问题而失败(对于 例如,死锁失败者)。如果重试作,则很可能会成功 在下一次尝试中。对于适合重试的业务服务 条件(冪等作,不需要返回给用户进行冲突 resolution),我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException.这是一个明显跨越的要求 服务层中的多个服务,因此非常适合通过 方面。spring-doc.cadn.net.cn

因为我们想重试作,所以我们需要使用 around 建议,以便我们可以 叫proceed多次。以下列表显示了基本方面实现 (这是一个使用模式支持的常规 Java 类):spring-doc.cadn.net.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
    }
}

请注意,该方面实现了Ordered接口,以便我们可以设置 高于交易建议的方面(我们每次都希望有新的交易 重试)。这maxRetriesorder属性都由 Spring 配置。这 主要作发生在doConcurrentOperation围绕建议方法。我们尝试 进行。 如果我们在PessimisticLockingFailureException,我们再试一次,除非我们已经用尽了所有的重试尝试。spring-doc.cadn.net.cn

该类与@AspectJ示例中使用的类相同,但具有 注释已删除。

对应的Spring配置如下:spring-doc.cadn.net.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>

请注意,目前,我们假设所有业务服务都是幂等的。如果 事实并非如此,我们可以细化该方面,使其仅真正重试 幂等作,通过引入Idempotent注释和使用注释 对服务作的实现进行注释,如以下示例所示:spring-doc.cadn.net.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // marker annotation
}

这 更改为仅重试幂等作涉及细化 切入点表达式,以便仅@Idempotent作匹配,如下所示:spring-doc.cadn.net.cn

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>

5.6. 选择要使用的AOP声明样式

一旦您确定某个方面是实现给定的最佳方法 要求,您如何决定在使用 Spring AOP 或 AspectJ 之间以及在 方面语言(代码)样式、@AspectJ 注释样式还是 Spring XML 样式?这些 决策受多种因素影响,包括应用要求、 开发工具,以及团队对 AOP 的熟悉程度。spring-doc.cadn.net.cn

5.6.1. Spring AOP 还是 Full AspectJ?

使用最简单的东西。Spring AOP 比使用完整的 AspectJ 更简单,因为 无需将 AspectJ 编译器/编织器引入您的开发中 并构建流程。如果您只需要建议在 Spring 上执行作 beans,Spring AOP 是正确的选择。如果需要建议不受 Spring 容器(例如域对象,通常),您需要使用 方面J.如果您想建议加入点以外的连接点,您还需要使用 AspectJ 简单的方法执行(例如,字段获取或设置连接点等)。spring-doc.cadn.net.cn

当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,则已为您做出选择:使用代码样式。如果方面发挥大作用 角色,并且您可以使用 AspectJ 开发工具(AJDT)插件,AspectJ 语言语法是 首选选项。它更干净、更简单,因为语言是有目的的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中不起主要作用,您可能需要考虑使用 @AspectJ样式,在 IDE 中坚持常规 Java 编译,并添加 构建脚本的方面编织阶段。spring-doc.cadn.net.cn

5.6.2. Spring AOP 的 @AspectJ 还是 XML?

如果您选择使用 Spring AOP,则可以选择 @AspectJ 或 XML 样式。 需要考虑各种权衡。spring-doc.cadn.net.cn

XML 样式可能是现有 Spring 用户最熟悉的,并且有正版 POJOs。当使用 AOP 作为配置企业服务的工具时,XML 可以是一个很好的 选择(一个好的测试是您是否认为切入点表达式是 配置)。对于 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。spring-doc.cadn.net.cn

XML 样式有两个缺点。首先,它没有完全封装 在一个地方实现它所解决的要求。DRY 原理说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解需求如何 在后备 Bean 类的声明和 XML 中拆分 配置文件。使用@AspectJ样式时,将封装此信息 在单个模块中:方面。其次,XML 样式在 它可以表达比@AspectJ风格:只有“单例”方面实例化模型 ,并且无法组合在 XML 中声明的命名切入点。 例如,在@AspectJ样式中,您可以编写如下内容:spring-doc.cadn.net.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.cadn.net.cn

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XML 方法的缺点是无法定义accountPropertyAccess通过结合这些定义来切入点。spring-doc.cadn.net.cn

@AspectJ样式支持额外的实例化模型和更丰富的切入点 组成。它的优点是将方面保持为模块化单元。它还具有 @AspectJ方面可以通过以下方式理解(并因此被消费)的优势 Spring AOP 和 AspectJ。因此,如果您以后决定需要 AspectJ 的功能 要实现其他要求,您可以轻松迁移到经典的 AspectJ 设置。 总的来说,Spring 团队更喜欢@AspectJ样式来定制方面,而不是简单的 企业服务的配置。spring-doc.cadn.net.cn

5.7. 混合方面类型

通过使用自动代理支持,完全可以混合@AspectJ风格方面, 模式定义<aop:aspect>方面<aop:advisor>声明的顾问,甚至代理人 以及相同配置中其他样式的拦截器。所有这些都已实现 通过使用相同的底层支持机制,可以毫无困难地共存。spring-doc.cadn.net.cn

5.8. 代理机制

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的代理创建代理 目标对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是常见的 开源类定义库(重新打包到spring-core).spring-doc.cadn.net.cn

如果要代理的目标对象至少实现了一个接口,则 JDK 动态 代理。目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则创建 CGLIB 代理。spring-doc.cadn.net.cn

如果要强制使用 CGLIB 代理(例如,代理每个方法 为目标对象定义,而不仅仅是由其接口实现的对象), 你可以这样做。但是,您应该考虑以下问题:spring-doc.cadn.net.cn

  • 使用 CGLIB,final不能建议方法,因为它们不能在 运行时生成的子类。spring-doc.cadn.net.cn

  • 从 Spring 4.0 开始,代理对象的构造函数不再被调用两次, 因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当你的 JVM 这样做时 不允许绕过构造函数,则可能会看到双重调用和 来自 Spring 的 AOP 支持的相应调试日志条目。spring-doc.cadn.net.cn

要强制使用 CGLIB 代理,请将proxy-target-class属性 的<aop:config>元素设置为 true,如下所示:spring-doc.cadn.net.cn

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用 @AspectJ 自动代理支持时强制 CGLIB 代理,请将proxy-target-class属性的<aop:aspectj-autoproxy>元素设置为true, 如下:spring-doc.cadn.net.cn

<aop:aspectj-autoproxy proxy-target-class="true"/>

倍数<aop:config/>部分折叠到单个统一的自动代理创建器运行时,它应用了最强的代理设置,其中任何<aop:config/>部分(通常来自不同的 XML Bean 定义文件)。这也适用于<tx:annotation-driven/><aop:aspectj-autoproxy/>元素。spring-doc.cadn.net.cn

需要明确的是,使用proxy-target-class="true"<tx:annotation-driven/>,<aop:aspectj-autoproxy/><aop:config/>元素强制使用 CGLIB 这三个人的代理。spring-doc.cadn.net.cn

5.8.1. 了解 AOP 代理

Spring AOP 是基于代理的。掌握 在你编写自己的方面或使用任何方面之前,最后一个陈述的实际含义 Spring Framework 提供的基于 Spring AOP 的方面。spring-doc.cadn.net.cn

首先考虑这样一个场景:你有一个普通的、未代理的、 nothing-special-about-it,直接对象引用,如下所示 代码片段显示:spring-doc.cadn.net.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.cadn.net.cn

AOP 代理 Plain Pojo 调用
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.cadn.net.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()
}

这里要理解的关键是,客户端代码中的main(..)方法 的Main类具有对代理的引用。这意味着该方法调用该 对象引用是对代理的调用。因此,代理可以委托给所有 与该特定方法调用相关的拦截器(通知)。然而 一旦调用最终到达目标对象(SimplePojo引用 在这种情况下),它可能对自身进行的任何方法调用,例如this.bar()this.foo(),将针对this引用,而不是代理。 这具有重要意义。这意味着自我调用不会产生 在与方法调用相关的建议中,获得运行的机会。spring-doc.cadn.net.cn

好吧,那么对此该怎么办呢?最佳方法(使用术语“最佳” 松散地在这里)是重构你的代码,以便不会发生自我调用。 这确实需要您做一些工作,但这是最好的、侵入性最小的方法。 下一种方法绝对是可怕的,我们犹豫是否要准确指出它 因为它太可怕了。你可以(尽管这对我们来说很痛苦)完全束缚逻辑 在你的类中到 Spring AOP,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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.cadn.net.cn

5.9. @AspectJ代理的编程创建

除了在配置中声明方面之外,还可以使用<aop:config><aop:aspectj-autoproxy>,也可以以编程方式创建代理,为目标对象提供建议。有关 Spring AOP API 的完整详细信息,请参阅下一章。在这里,我们想重点关注自动的能力使用@AspectJ方面创建代理。spring-doc.cadn.net.cn

您可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory类 为一个或多个@AspectJ方面建议的目标对象创建代理。此类的基本用法非常简单,如以下示例所示:spring-doc.cadn.net.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.cadn.net.cn

5.10. 将 AspectJ 与 Spring 应用程序一起使用

到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中,我们将探讨如何使用 AspectJ 编译器或编织器来代替此外,如果您的需求超出了 Spring AOP 提供的功能,则还可以使用 Spring AOP 独自。spring-doc.cadn.net.cn

Spring 附带了一个小型 AspectJ aspect 库,该库可在您的分发中独立使用spring-aspects.jar.您需要按顺序将其添加到类路径中 使用其中的方面。使用 AspectJ 依赖注入具有 Spring 的域对象和其他 Spring 方面 对于 AspectJ 讨论 该库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面讨论了如何 依赖注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,Spring 框架中使用 AspectJ 的加载时编织介绍了 Spring 应用程序的加载时编织 使用 AspectJ。spring-doc.cadn.net.cn

5.10.1. 使用 AspectJ 使用 Spring 注入依赖性注入域对象

Spring 容器实例化并配置应用程序中定义的 bean 上下文。也可以要求 bean 工厂配置预先存在的 对象,给定包含要应用的配置的 Bean 定义的名称。spring-aspects.jar包含一个利用 允许注入任何对象的依赖项的能力。该支持旨在 用于在任何容器控制之外创建的对象。域对象 通常属于这一类,因为它们通常是使用new运算符或 ORM 工具作为数据库查询的结果。spring-doc.cadn.net.cn

@Configurable注释将类标记为符合 Spring 驱动的条件 配置。在最简单的情况下,您可以纯粹将其用作标记注释,如 以下示例显示:spring-doc.cadn.net.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 会配置 带注释的类型 (Account,在本例中),使用 bean 定义(通常 prototype-scoped),与完全限定类型名称同名 (com.xyz.myapp.domain.Account).由于 bean 的默认名称是 完全限定的名称,声明原型定义的便捷方式 是省略id属性,如以下示例所示:spring-doc.cadn.net.cn

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果要显式指定要使用的原型 Bean 定义的名称,请 可以直接在注释中执行此作,如以下示例所示:spring-doc.cadn.net.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 现在查找名为account并将其用作 配置 new 的定义Account实例。spring-doc.cadn.net.cn

您还可以使用自动装配来避免在 都。要让 Spring 应用自动布线,请使用autowire属性的@Configurable注解。您可以指定@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME)用于按类型或名称自动接线, 分别。作为替代方案,最好指定显式的、注释驱动的 依赖注入@Configurable豆子通过@Autowired@Inject在字段或方法级别(有关更多详细信息,请参阅基于注释的容器配置)。spring-doc.cadn.net.cn

最后,您可以为新中的对象引用启用 Spring 依赖项检查 使用dependencyCheck属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)).如果此属性为 设置为true,Spring 在配置后验证所有属性(其中 不是原语或集合)已被设置。spring-doc.cadn.net.cn

请注意,单独使用注释不会执行任何作。它是AnnotationBeanConfigurerAspectspring-aspects.jar作用于 注释。从本质上讲,该方面说,“从初始化返回后 一个类型的新对象,用@Configurable,配置新创建的对象 根据注释的属性使用 Spring“。在这种情况下, “初始化”是指新实例化的对象(例如,实例化的对象 使用new运算符)以及Serializable正在进行的对象 反序列化(例如,通过 readResolve())。spring-doc.cadn.net.cn

上一段的关键短语之一是“本质上”。在大多数情况下, “从新对象的初始化中返回后”的确切语义是 好。在这种情况下,“初始化后”意味着依赖项是 在构建对象后注入。这意味着依赖项 不可用于类的构造函数体。如果你想要 依赖项在构造函数体运行之前注入,因此 可用于构造函数的主体,您需要在@Configurable声明,如下所示:spring-doc.cadn.net.cn

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

您可以找到有关各种切入点的语言语义的更多信息 类型 在 AspectJ AspectJ 的附录 编程指南spring-doc.cadn.net.cn

为此,必须使用 AspectJ 编织器编织带注释的类型。您可以 使用构建时的 Ant 或 Maven 任务来执行此作(例如,请参阅 AspectJ 开发 环境指南)或加载时编织(请参阅 Spring 框架中使用 AspectJ 进行加载时编织)。这AnnotationBeanConfigurerAspect本身需要由 Spring 配置(以便获得 对用于配置新对象的 Bean 工厂的引用)。如果你 使用基于 Java 的配置,可以添加@EnableSpringConfigured到任何@Configuration类,如下所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
Kotlin
@Configuration
@EnableSpringConfigured
class AppConfig {
}

如果您更喜欢基于 XML 的配置,则 SpringcontextNamespace定义了一个方便的context:spring-configured元素,您可以按如下方式使用它:spring-doc.cadn.net.cn

<context:spring-configured/>

实例@Configurable在配置方面之前创建的对象 导致向调试日志发出消息,并且没有配置 发生的对象。一个示例可能是 Spring 配置中的 bean,它创建了 domain 对象。在这种情况下,您可以使用depends-onbean 属性来手动指定 bean 依赖于 配置方面。以下示例演示如何使用depends-on属性:spring-doc.cadn.net.cn

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>
不激活@Configurable通过 Bean Configurer 方面进行处理,除非您 实际上意味着在运行时依赖其语义。特别是,请确保您这样做 不使用@Configurable在注册为常规 Spring Bean 的 Bean 类上 与容器。这样做会导致双重初始化,一次通过 容器和一次通过方面。
单元测试@Configurable对象

的目标之一@Configurable支持是启用独立的单元测试 域对象的属性,而没有与硬编码查找相关的困难。 如果@Configurable类型没有被 AspectJ 编织,注释没有影响 在单元测试期间。您可以在对象中设置模拟或存根属性引用 测试并照常进行。如果@Configurable类型已被 AspectJ 编织, 您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告 每次构造@Configurable对象,表示它有 未由 Spring 配置。spring-doc.cadn.net.cn

使用多个应用程序上下文

AnnotationBeanConfigurerAspect用于实现@Configurable支持 是 AspectJ 单例方面。单例方面的作用域与作用域相同 之staticmembers:每个类加载器都有一个方面实例来定义类型。 这意味着,如果在同一类加载器中定义多个应用程序上下文 层次结构,您需要考虑在哪里定义@EnableSpringConfiguredbean 和 放置位置spring-aspects.jar在类路径上。spring-doc.cadn.net.cn

考虑一个典型的 Spring Web 应用程序配置,它有一个共享的父应用程序 定义通用业务服务的上下文,支持这些服务所需的一切, 以及每个 Servlet 的一个子应用程序上下文(其中包含特定的定义 到该 servlet)。所有这些上下文都共存于同一个类加载器层次结构中, 因此,AnnotationBeanConfigurerAspect只能保存对其中一个的引用。 在这种情况下,我们建议定义@EnableSpringConfigured共享中的 bean (父)应用程序上下文。这定义了您可能想要的服务 注入到域对象中。结果是无法配置域对象 使用 @Configurable机制(无论如何,这可能不是你想做的事情)。spring-doc.cadn.net.cn

在同一容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序加载spring-aspects.jar通过使用自己的类加载器 (例如,通过将spring-aspects.jarWEB-INF/lib).如果spring-aspects.jar仅添加到容器范围的类路径(因此由共享父级 类加载器),所有 Web 应用程序共享相同的 aspect 实例(这可能是 不是你想要的)。spring-doc.cadn.net.cn

5.10.2. AspectJ 的其他 Spring 方面

除了@Configurable方面spring-aspects.jar包含一个 AspectJ方面,您可以使用它来驱动 Spring 对类型和方法的事务管理用@Transactional注解。 这主要适用于以下用户:想要在 Spring 容器之外使用 Spring 框架的事务支持。spring-doc.cadn.net.cn

解释的方面@Transactional注释是AnnotationTransactionAspect.使用此方面时,必须对 实现类(或该类中的方法或两者兼而有之),而不是接口(如果 any) 的类实现。AspectJ 遵循 Java 的规则,即对 接口不会继承。spring-doc.cadn.net.cn

一个@Transactional类上的注释指定 类中任何公共作的执行。spring-doc.cadn.net.cn

一个@Transactional类中方法的注释将覆盖默认值 类注释(如果存在)给出的交易语义。任何方法 可见性可以进行注释,包括私有方法。注释非公共方法 直接是获取执行此类方法的事务分界的唯一方法。spring-doc.cadn.net.cn

从 Spring Framework 4.2 开始,spring-aspects提供了一个类似的方面,提供 与标准完全相同的功能javax.transaction.Transactional注解。检查JtaAnnotationTransactionAspect了解更多详情。

对于想要使用 Spring 配置和事务的 AspectJ 程序员 管理支持,但不想(或不能)使用注释,spring-aspects.jar还包含abstract您可以扩展以提供自己的切入点的方面 定义。请参阅AbstractBeanConfigurerAspectAbstractTransactionAspect方面以获取更多信息。例如,以下内容 摘录显示了如何编写一个方面来配置对象的所有实例 使用与 完全限定的类名:spring-doc.cadn.net.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 方面

当您将 AspectJ 方面与 Spring 应用程序一起使用时,很自然地想要和 期望能够使用 Spring 配置这些方面。AspectJ 运行时本身是 负责 aspect 创建,以及配置 AspectJ-created 的手段 方面通过 Spring 取决于 AspectJ 实例化模型(per-xxx条款) 由方面使用。spring-doc.cadn.net.cn

大多数 AspectJ 方面都是单例方面。这些的配置 方面很容易。您可以创建一个 bean 定义,该定义引用 aspect 类型为 normal 并包含factory-method="aspectOf"bean 属性。这确保了 Spring 通过向 AspectJ 请求它来获取 aspect 实例,而不是尝试创建 实例本身。以下示例演示如何使用factory-method="aspectOf"属性:spring-doc.cadn.net.cn

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> (1)

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 请注意factory-method="aspectOf"属性

非单例方面更难配置。但是,可以通过以下方式做到这一点 创建原型 Bean 定义并使用@Configurable支持spring-aspects.jar在创建 Bean 后配置 aspect 实例 AspectJ 运行时。spring-doc.cadn.net.cn

如果你有一些@AspectJ方面要用 AspectJ 编织(例如, 对域模型类型使用加载时编织)和您想要的其他@AspectJ方面 与 Spring AOP 一起使用,并且这些方面都在 Spring 中配置,您 需要告诉 Spring AOP @AspectJ自动代理支持的哪个确切子集 配置中定义的@AspectJ方面应用于自动代理。您可以 为此,请使用一个或多个<include/>元素<aop:aspectj-autoproxy/>声明。每<include/>元素指定一个名称模式,并且只有带有 与至少一种模式匹配的名称用于 Spring AOP 自动代理 配置。以下示例演示如何使用<include/>元素:spring-doc.cadn.net.cn

<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被名称误导<aop:aspectj-autoproxy/>元素。使用它 导致创建 Spring AOP 代理。@AspectJ样式的方面 这里使用声明,但不涉及 AspectJ 运行时。

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

加载时间编织 (LTW) 是指将 AspectJ 方面编织成 应用程序的类文件,因为它们被加载到 Java 虚拟机 (JVM) 中。 本节的重点是在 Spring 框架。本节不是对 LTW 的一般介绍。有关 LTW 的细节和仅使用 AspectJ 配置 LTW(Spring 不是 完全涉及),请参阅 AspectJ 的 LTW 部分 开发环境指南spring-doc.cadn.net.cn

Spring Framework 为 AspectJ LTW 带来的价值在于实现了许多 对织造过程进行更细粒度的控制。“Vanilla”AspectJ LTW 是通过使用 Java (5+) 代理,通过在启动 JVM.因此,它是一个 JVM 范围的设置,在某些情况下可能没问题,但通常是 有点太粗糙了。启用 Spring 的 LTW 允许您在 每-ClassLoader基础,哪个粒度更细,哪个可以做更多 在“单 JVM-多应用程序”环境中的意义(例如在典型的 应用服务器环境)。spring-doc.cadn.net.cn

此外,在某些环境中,此支持使 加载时编织,而无需对应用程序服务器的启动进行任何修改 需要添加的脚本-javaagent:path/to/aspectjweaver.jar或(正如我们所描述的 在本节后面)-javaagent:path/to/spring-instrument.jar.开发人员配置 应用程序上下文,以启用加载时编织,而不是依赖管理员 通常负责部署配置(例如启动脚本)的人。spring-doc.cadn.net.cn

现在推销已经结束,让我们首先浏览一个 AspectJ 的快速示例 LTW 的 Spring,然后是 例。有关完整示例,请参阅 Petclinic 示例应用程序spring-doc.cadn.net.cn

第一个例子

假设您是负责诊断的应用程序开发人员 系统中某些性能问题的原因。而不是爆发一个 分析工具,我们将打开一个简单的分析方面,让我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具立即到该特定区域。spring-doc.cadn.net.cn

此处提供的示例使用 XML 配置。您还可以配置和 将@AspectJ与 Java 配置一起使用。具体来说,您可以使用@EnableLoadTimeWeaving注释作为替代<context:load-time-weaver/>(详情见下文)。

以下示例显示了分析方面,这并不花哨。 它是一个基于时间的分析器,使用 @AspectJ 样式的方面声明:spring-doc.cadn.net.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() {
    }
}

我们还需要创建一个META-INF/aop.xml文件,以通知 AspectJ 编织者 我们想编织我们的ProfilingAspect进入我们的班级。这个文件约定,即 Java 类路径上存在一个(或多个文件),称为META-INF/aop.xml是 标准 AspectJ.以下示例显示了aop.xml文件:spring-doc.cadn.net.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 的部分。我们需要 配置LoadTimeWeaver(稍后解释)。此加载时间编织器是 负责将方面配置编织在一个或 更多META-INF/aop.xml文件添加到应用程序中的类中。好的 问题是它不需要很多配置(还有更多 您可以指定的选项,但稍后会详细介绍这些选项),如 以下示例:spring-doc.cadn.net.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>

现在,所有必需的工件(方面、META-INF/aop.xml文件,并且 Spring 配置)就位,我们可以创建以下内容 驱动程序类,并带有main(..)演示 LTW 实际效果的方法:spring-doc.cadn.net.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()
}

我们还有最后一件事要做。本节的引言确实说过,可以 在每个ClassLoader与 Spring 的基础,这是真的。 但是,对于此示例,我们使用Java代理(Spring随附)来打开LTW。 我们使用以下命令运行Main前面显示的类:spring-doc.cadn.net.cn

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

-javaagent是用于指定和启用代理的标志 检测在 JVM 上运行的程序。Spring Framework 附带了这样的 agent,则InstrumentationSavingAgent,它打包在spring-instrument.jar作为-javaagent参数 前面的示例。spring-doc.cadn.net.cn

执行Main程序看起来像下一个例子。(我已经引入了一个Thread.sleep(..)语句添加到calculateEntitlement()实现,以便分析器实际捕获 0毫秒(01234毫秒不是 AOP 引入的开销)。以下列表显示了我们在运行分析器时获得的输出:spring-doc.cadn.net.cn

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于这个 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于建议Spring 豆。以下对Main程序产生相同的结果 结果:spring-doc.cadn.net.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 容器,然后创建一个新的实例StubEntitlementCalculationService完全在外面Spring的背景。分析建议仍然被编织进去。spring-doc.cadn.net.cn

诚然,这个例子很简单。然而,Spring 中 LTW 支持的基础知识在前面的例子中都已经介绍过了,本节的其余部分详细解释了每个配置和用法背后的“为什么”。spring-doc.cadn.net.cn

ProfilingAspect这个例子中使用的可能是基本的,但它非常有用。这是一个开发人员可以在开发过程中使用的开发时方面的好例子然后轻松地从正在部署的应用程序的构建中排除进入 UAT 或生产。
方面

您在 LTW 中使用的方面必须是 AspectJ 方面。你可以将它们写在AspectJ 语言本身,或者你可以用 @AspectJ 样式编写你的方面。然后你的方面都是有效的 AspectJ 和 Spring AOP 方面。此外,编译后的方面类需要在类路径上可用。spring-doc.cadn.net.cn

“元输入/aop.xml”

AspectJ LTW 基础结构使用一个或多个META-INF/aop.xml位于 Java 类路径上的文件(直接或更典型的是 jar 文件中)。spring-doc.cadn.net.cn

该文件的结构和内容详见 AspectJ 参考的 LTW 部分 文档。因为aop.xml文件是 100% AspectJ,我们在这里不再进一步描述它。spring-doc.cadn.net.cn

所需库 (JARS)

至少,您需要以下库才能使用 Spring Framework 的支持 对于 AspectJ LTW:spring-doc.cadn.net.cn

弹簧配置

Spring LTW 支持的关键组件是LoadTimeWeaver接口(在org.springframework.instrument.classloadingpackage)和众多实现 其中与春季发行版一起发货。一个LoadTimeWeaver负责 添加一个或多个java.lang.instrument.ClassFileTransformers设置为ClassLoader在 运行时,它为各种有趣的应用程序打开了大门,其中之一 恰好是方面的 LTW。spring-doc.cadn.net.cn

如果您不熟悉运行时类文件转换的概念,请参阅 javadoc API 文档java.lang.instrument打包后再继续。 虽然该文档并不全面,但至少您可以看到关键接口 和类(供您阅读本节时参考)。

配置LoadTimeWeaver对于特定的ApplicationContext可以像 添加一行。(请注意,您几乎肯定需要使用ApplicationContext作为 Spring 容器 — 通常,一个BeanFactory莫 足够了,因为 LTW 支持BeanFactoryPostProcessors.)spring-doc.cadn.net.cn

要启用 Spring Framework 的 LTW 支持,您需要配置一个LoadTimeWeaver, 这通常是通过使用@EnableLoadTimeWeaving注释,如下所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

或者,如果您更喜欢基于 XML 的配置,请使用<context:load-time-weaver/>元素。请注意,该元素在contextNamespace。以下示例演示如何使用<context:load-time-weaver/>:spring-doc.cadn.net.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,例如LoadTimeWeaverAspectJWeavingEnabler给你的。 默认值LoadTimeWeaverDefaultContextLoadTimeWeaver类,该类尝试 装饰自动检测到LoadTimeWeaver.的确切类型LoadTimeWeaver即“自动检测”取决于您的运行时环境。 下表总结了各种LoadTimeWeaver实现:spring-doc.cadn.net.cn

表 13.DefaultContextLoadTimeWeaver LoadTimeWeavers
运行时环境 LoadTimeWeaver实现

Apache Tomcat 中运行spring-doc.cadn.net.cn

TomcatLoadTimeWeaverspring-doc.cadn.net.cn

GlassFish 中运行(仅限于 EAR 部署)spring-doc.cadn.net.cn

GlassFishLoadTimeWeaverspring-doc.cadn.net.cn

在 Red Hat 的 JBoss ASWildFly 中运行spring-doc.cadn.net.cn

JBossLoadTimeWeaverspring-doc.cadn.net.cn

在 IBM 的 WebSphere 中运行spring-doc.cadn.net.cn

WebSphereLoadTimeWeaverspring-doc.cadn.net.cn

在 Oracle 的 WebLogic 中运行spring-doc.cadn.net.cn

WebLogicLoadTimeWeaverspring-doc.cadn.net.cn

JVM 从 Spring 开始InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)spring-doc.cadn.net.cn

InstrumentationLoadTimeWeaverspring-doc.cadn.net.cn

回退,期望底层 ClassLoader 遵循通用约定 (即addTransformer和可选的getThrowawayClassLoader方法)spring-doc.cadn.net.cn

ReflectiveLoadTimeWeaverspring-doc.cadn.net.cn

请注意,该表仅列出了LoadTimeWeavers当您 使用DefaultContextLoadTimeWeaver.您可以准确指定哪个LoadTimeWeaver实现以使用。spring-doc.cadn.net.cn

要指定特定的LoadTimeWeaver使用 Java 配置时,实现LoadTimeWeavingConfigurer接口并覆盖getLoadTimeWeaver()方法。 以下示例指定ReflectiveLoadTimeWeaver:spring-doc.cadn.net.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/>元素。同样,以下示例指定了ReflectiveLoadTimeWeaver:spring-doc.cadn.net.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>

LoadTimeWeaver由配置定义和注册的,可以稍后 使用众所周知的名称从 Spring 容器中检索,loadTimeWeaver. 请记住,LoadTimeWeaver仅作为 Spring LTW 的机制存在 基础结构,以添加一个或多个ClassFileTransformers.实际的ClassFileTransformerLTW 是ClassPreProcessorAgentAdapter(从 这org.aspectj.weaver.loadtimepackage) 类。请参阅ClassPreProcessorAgentAdapterclass 了解更多详细信息,因为具体如何 实际进行的编织超出了本文档的范围。spring-doc.cadn.net.cn

还有一个配置的最后一个属性需要讨论:aspectjWeaving属性(或aspectj-weaving如果您使用 XML)。此属性控制 LTW 是否启用。它接受三个可能的值之一,默认值为autodetect如果该属性不存在。下表总结了这三个 可能的值:spring-doc.cadn.net.cn

表 14.AspectJ 编织属性值
注释值 XML 值 解释

ENABLEDspring-doc.cadn.net.cn

onspring-doc.cadn.net.cn

AspectJ 编织已打开,并且根据需要在加载时编织 Aspect 。spring-doc.cadn.net.cn

DISABLEDspring-doc.cadn.net.cn

offspring-doc.cadn.net.cn

LTW 已关闭。加载时没有任何方面是编织的。spring-doc.cadn.net.cn

AUTODETECTspring-doc.cadn.net.cn

autodetectspring-doc.cadn.net.cn

如果 Spring LTW 基础设施可以找到至少一个META-INF/aop.xml文件 然后 AspectJ 编织开始。否则,它关闭。这是默认值。spring-doc.cadn.net.cn

特定于环境的配置

最后一部分包含您需要的任何其他设置和配置 当您在应用程序服务器和 Web 等环境中使用 Spring 的 LTW 支持时 器皿。spring-doc.cadn.net.cn

Tomcat、JBoss、WebSphere、WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 提供通用应用ClassLoader能够本地检测。Spring的 原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。 如前所述,您可以简单地启用加载时编织。 具体来说,您不需要修改 JVM 启动脚本来添加-javaagent:path/to/spring-instrument.jar.spring-doc.cadn.net.cn

请注意,在 JBoss 上,您可能需要禁用应用程序服务器扫描以防止它 在应用程序实际启动之前加载类。快速解决方法是将 到您的工件中,一个名为WEB-INF/jboss-scanning.xml内容如下:spring-doc.cadn.net.cn

<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序

在不受支持的环境中需要类检测时 特定LoadTimeWeaver实现中,JVM 代理是通用解决方案。 对于这种情况,Spring 提供了InstrumentationLoadTimeWeaver这需要一个 特定于 Spring 的(但非常通用的)JVM 代理,spring-instrument.jar、自动检测 由 common@EnableLoadTimeWeaving<context:load-time-weaver/>设置。spring-doc.cadn.net.cn

要使用它,您必须通过提供 以下 JVM 选项:spring-doc.cadn.net.cn

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改 JVM 启动脚本,这可能会阻止您 在应用程序服务器环境中使用它(取决于您的服务器和您的 运营策略)。也就是说,对于每个 JVM 一个应用程序部署,例如独立 Spring Boot 应用程序,您通常在任何情况下都可以控制整个 JVM 设置。spring-doc.cadn.net.cn

5.11. 更多资源

有关 AspectJ 的更多信息,请访问 AspectJ 网站spring-doc.cadn.net.cn

阿德里安·科利尔 (Adrian Colyer) 等人的《日食 AspectJ》。(Addison-Wesley,2005)提供了一个 全面的介绍和参考 AspectJ 语言。spring-doc.cadn.net.cn

AspectJ in Action,第二版,作者:Ramnivas Laddad(曼宁,2009 年)名列前茅 推荐。这本书的重点是 AspectJ,但很多通用的 AOP 主题都是 探索过(在某种程度上)。spring-doc.cadn.net.cn

6. Spring AOP API

上一章描述了 Spring 对 AOP 的支持,具有@AspectJ和基于模式 方面定义。在本章中,我们将讨论较低级别的 Spring AOP API。对于普通 应用程序,我们建议将 Spring AOP 与 AspectJ 切入点一起使用,如 上一章。spring-doc.cadn.net.cn

6.1. Spring中的切入点API

本节介绍 Spring 如何处理关键的切入点概念。spring-doc.cadn.net.cn

6.1.1. 概念

Spring 的切入点模型支持独立于通知类型的切入点重用。您可以 用相同的切入点瞄准不同的建议。spring-doc.cadn.net.cn

org.springframework.aop.Pointcutinterface 是中央接口,用于 针对特定类和方法的建议。完整界面如下:spring-doc.cadn.net.cn

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

拆分Pointcut接口分为两部分,允许重用类和方法 匹配部件和细粒度组合作(例如执行“并集” 使用另一个方法匹配器)。spring-doc.cadn.net.cn

ClassFilter接口用于将切入点限制为一组给定的目标 类。如果matches()方法始终返回 true,则所有目标类都是 匹配。以下列表显示了ClassFilter接口定义:spring-doc.cadn.net.cn

public interface ClassFilter {

    boolean matches(Class clazz);
}

MethodMatcher接口通常更重要。完整界面如下:spring-doc.cadn.net.cn

public interface MethodMatcher {

    boolean matches(Method m, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class<?> targetClass, Object... args);
}

matches(Method, Class)方法用于测试该切入点是否曾经 匹配目标类上的给定方法。当 AOP 创建代理是为了避免在每次方法调用时进行测试。如果 双参数matches方法返回true对于给定的方法,并且isRuntime()方法,MethodMatcher 返回true,则三参数匹配方法是 在每个方法调用时调用。这允许切入点查看传递的参数 到目标通知开始前的方法调用。spring-doc.cadn.net.cn

MethodMatcher实现是静态的,这意味着它们的isRuntime()方法 返回false.在这种情况下,三参数matches方法永远不会被调用。spring-doc.cadn.net.cn

如果可能,请尝试使切入点静态,允许 AOP 框架缓存 创建AOP代理时的切入点评估结果。

6.1.2. 切入点的作

Spring 支持对切入点的作(特别是并集和交集)。spring-doc.cadn.net.cn

联合表示任一切入点匹配的方法。 交点是指两个切入点匹配的方法。 联合通常更有用。 您可以使用org.springframework.aop.support.Pointcuts类或使用ComposablePointcut类。但是,使用 AspectJ 切入点 表达式通常是一种更简单的方法。spring-doc.cadn.net.cn

6.1.3. AspectJ 表达式切入点

从 2.0 开始,Spring 使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut.这是一个切入点 使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串。spring-doc.cadn.net.cn

有关支持的 AspectJ 切入点基元的讨论,请参阅上一章spring-doc.cadn.net.cn

6.1.4. 便利切入点实现

Spring 提供了几种方便的切入点实现。您可以使用其中的一些 径直;其他的则旨在在特定于应用程序的切入点中进行子类化。spring-doc.cadn.net.cn

静态切入点(Static Pointcuts)

静态切入点基于方法和目标类,不能考虑 方法的参数。对于大多数用途,静态切入点已经足够了,而且是最好的。 Spring 只能在首次调用方法时评估一次静态切入点。 之后,无需在每次方法调用时再次评估切入点。spring-doc.cadn.net.cn

本节的其余部分描述了一些静态切入点实现,这些实现是 包含在 Spring 中。spring-doc.cadn.net.cn

正则表达式切入点

指定静态切入点的一种明显方法是正则表达式。几个AOP 除了 Spring 之外的框架使这成为可能。org.springframework.aop.support.JdkRegexpMethodPointcut是通用常规 使用 JDK 中正则表达式支持的表达式切入点。spring-doc.cadn.net.cn

使用JdkRegexpMethodPointcut类,则可以提供模式字符串列表。 如果其中任何一个是匹配的,则切入点的计算结果为true.(因此, 生成的切入点实际上是指定图案的并集。spring-doc.cadn.net.cn

以下示例演示如何使用JdkRegexpMethodPointcut:spring-doc.cadn.net.cn

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring 提供了一个名为RegexpMethodPointcutAdvisor,这让我们 还引用Advice(请记住,一个Advice可以拦截,在建议之前, 抛出建议等)。在幕后,Spring 使用JdkRegexpMethodPointcut. 用RegexpMethodPointcutAdvisor简化了布线,因为 One Bean 封装了两者 切入点和建议,如以下示例所示:spring-doc.cadn.net.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>

您可以使用RegexpMethodPointcutAdvisor与任何Advice类型。spring-doc.cadn.net.cn

属性驱动的切入点

静态切入点的一种重要类型是元数据驱动的切入点。这使用 元数据属性的值(通常是源级元数据)。spring-doc.cadn.net.cn

动态切入点

动态切入点的评估成本高于静态切入点。他们考虑到 方法参数以及静态信息。这意味着它们必须是 在每次方法调用时都会计算结果,并且无法缓存结果,因为参数将 不同。spring-doc.cadn.net.cn

主要示例是control flow切入点。spring-doc.cadn.net.cn

控制流切入点

弹簧控制流切入点在概念上类似于 AspectJcflow切入点,尽管功能较弱。(目前无法指定切入点运行在与另一个切入点匹配的连接点下方。控制流切入点与当前调用堆栈匹配。例如,如果连接点是由方法调用的,它可能会触发 在com.mycompany.webpackage 或通过SomeCaller类。 控制流切入点通过使用org.springframework.aop.support.ControlFlowPointcut类。spring-doc.cadn.net.cn

控制流切入点在运行时的评估成本甚至比其他动态切入点要高得多。在 Java 1.4 中,成本大约是其他动态切入点的五倍。

6.1.5. 切入点超类

Spring 提供了有用的切入点超类来帮助您实现自己的切入点。spring-doc.cadn.net.cn

因为静态切入点最有用,所以你可能应该对StaticMethodMatcherPointcut. 这只需要实现一个abstract 方法(尽管您可以覆盖其他方法来自定义行为)。 这 以下示例演示如何将子类化StaticMethodMatcherPointcut:spring-doc.cadn.net.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.cadn.net.cn

6.1.6. 自定义切入点

因为 Spring AOP 中的切入点是 Java 类而不是语言功能(如AspectJ),所以您可以声明自定义切入点,无论是静态还是动态。 习惯 Spring 中的切入点可以任意复杂。但是,如果可以的话,我们建议使用 AspectJ 切入点表达式语言。spring-doc.cadn.net.cn

更高版本的 Spring 可能会提供对 JAC 提供的“语义切入点”的支持——例如,“更改目标对象中实例变量的所有方法”。

6.2. Spring 中的建议 API

现在我们可以检查 Spring AOP 如何处理建议。spring-doc.cadn.net.cn

6.2.1. 通知生命周期

每个建议都是春豆。建议实例可以在所有建议的 对象或对每个建议对象都是唯一的。这对应于每个类或 每个实例的建议。spring-doc.cadn.net.cn

最常使用每类建议。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。他们只是根据方法和论点采取行动。spring-doc.cadn.net.cn

每个实例的建议适用于介绍,以支持混合。在这种情况下, 建议将状态添加到代理对象。spring-doc.cadn.net.cn

您可以在同一 AOP 代理中混合使用共享通知和每个实例通知。spring-doc.cadn.net.cn

6.2.2. Spring 中的通知类型

Spring 提供了多种建议类型,并且可扩展以支持 任意建议类型。本节介绍基本概念和标准通知类型。spring-doc.cadn.net.cn

围绕建议的拦截

Spring 中最基本的建议类型是围绕建议的拦截。spring-doc.cadn.net.cn

Spring 符合 AOPAlliance使用方法的建议的接口 拦截。实现的类MethodInterceptor并且围绕建议实施也应该实施 以下接口:spring-doc.cadn.net.cn

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

MethodInvocation参数设置为invoke()method 公开了 调用,目标连接点,AOP代理和方法的参数。这invoke()方法应返回调用的结果:连接的返回值 点。spring-doc.cadn.net.cn

以下示例显示了一个简单的MethodInterceptor实现:spring-doc.cadn.net.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
    }
}

请注意对proceed()方法MethodInvocation.这会继续进行 拦截器链朝向连接点。大多数拦截器调用此方法和 返回其返回值。但是,一个MethodInterceptor,就像任何周围的建议一样,可以 返回不同的值或抛出异常,而不是调用 proceed 方法。 但是,您不想在没有充分理由的情况下这样做。spring-doc.cadn.net.cn

MethodInterceptor实现提供与其他符合 AOP 联盟的 AOP 的互作性 实现。本节其余部分讨论的其他建议类型 实现常见的AOP概念,但以特定于Spring的方式。虽然有优势 在使用最具体的建议类型时,请坚持使用MethodInterceptor周围建议如果 您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点 目前在框架之间不可互作,AOP 联盟也无法互作 当前定义切入点界面。
建议前

一种更简单的建议类型是之前的建议。这不需要MethodInvocation对象,因为它仅在进入方法之前被调用。spring-doc.cadn.net.cn

before 通知的主要优点是无需调用proceed()方法,因此,不可能无意中无法继续 拦截器链。spring-doc.cadn.net.cn

以下列表显示了MethodBeforeAdvice接口:spring-doc.cadn.net.cn

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

(Spring 的 API 设计将允许 字段在建议之前,尽管通常的对象适用于字段拦截,但它是 Spring 不太可能实现它。spring-doc.cadn.net.cn

请注意,返回类型为void.Before 通知可以在联接之前插入自定义行为 point 运行,但无法更改返回值。如果 before 建议抛出 异常,它会停止拦截器链的进一步执行。例外 向上传播拦截器链。如果未选中或签名为 调用的方法,它直接传递给客户端。否则,它是 被 AOP 代理包装在未经检查的异常中。spring-doc.cadn.net.cn

以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:spring-doc.cadn.net.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
    }
}
建议之前可以用于任何切入点。
投掷建议

如果引发连接点,则在返回连接点后调用抛出建议 异常。Spring 提供打字投掷建议。请注意,这意味着org.springframework.aop.ThrowsAdviceinterface 不包含任何方法。这是一个 标记接口,标识给定对象实现一个或多个类型化抛出 建议方法。这些应采用以下形式:spring-doc.cadn.net.cn

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于建议方法是否对方法感兴趣,并且 参数。接下来的两个列表显示了作为抛掷建议示例的类。spring-doc.cadn.net.cn

如果RemoteException被抛出(包括来自子类):spring-doc.cadn.net.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
    }
}

与前面的 建议,下一个示例声明了四个参数,以便它可以访问调用的方法 method 参数和目标对象。如果ServletException被抛出:spring-doc.cadn.net.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
    }
}

最后一个示例说明了如何在单个类中使用这两种方法 处理两者RemoteExceptionServletException.任意数量的投掷建议 方法可以组合在单个类中。以下列表显示了最后一个示例:spring-doc.cadn.net.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 方法抛出已检查的异常,则它必须 匹配目标方法的声明异常,因此在某种程度上是 耦合到特定的目标方法签名。不要抛出未声明的选中 与目标方法的签名不兼容的异常!
投掷建议可用于任何切入点。
退货后建议

Spring 中的返回通知后必须实现org.springframework.aop.AfterReturningAdvice界面,如下表所示:spring-doc.cadn.net.cn

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

返回通知后可以访问返回值(它无法修改), 调用的方法、方法的参数和目标。spring-doc.cadn.net.cn

返回通知后的以下内容会计算所有成功的方法调用,这些方法调用具有 not throwed 异常:spring-doc.cadn.net.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.cadn.net.cn

返回后,建议可用于任何切入点。
介绍建议

Spring 将介绍建议视为一种特殊的拦截建议。spring-doc.cadn.net.cn

引言需要一个IntroductionAdvisorIntroductionInterceptor那 实现以下接口:spring-doc.cadn.net.cn

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

invoke()继承自 AOP 联盟的方法MethodInterceptor接口必须 实施介绍。也就是说,如果调用的方法位于引入的 接口,引入拦截器负责处理方法调用——它 无法调用proceed().spring-doc.cadn.net.cn

介绍建议不能与任何切入点一起使用,因为它仅适用于课堂, 而不是方法,水平。您只能将介绍建议与IntroductionAdvisor,它有以下方法:spring-doc.cadn.net.cn

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}

没有MethodMatcher因此,没有Pointcut与介绍相关 建议。只有类过滤是合乎逻辑的。spring-doc.cadn.net.cn

getInterfaces()方法返回此顾问引入的接口。spring-doc.cadn.net.cn

validateInterfaces()方法在内部使用,以查看 引入的接口可以通过配置的IntroductionInterceptor.spring-doc.cadn.net.cn

考虑 Spring 测试套件中的一个示例,假设我们想要 将以下接口引入一个或多个对象:spring-doc.cadn.net.cn

Java
public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
Kotlin
interface Lockable {
    fun lock()
    fun unlock()
    fun locked(): Boolean
}

这说明了混合。我们希望能够将建议对象转换为Lockable, 无论它们的类型如何,并调用锁定和解锁方法。如果我们将lock()方法,我们 希望所有 setter 方法抛出一个LockedException.因此,我们可以添加一个方面 提供了使对象不可变的能力,而无需他们对此一无所知: AOP 的一个很好的例子。spring-doc.cadn.net.cn

首先,我们需要一个IntroductionInterceptor这完成了繁重的工作。在这个 case,我们将org.springframework.aop.support.DelegatingIntroductionInterceptor便利类。我们可以实施IntroductionInterceptor直接,但使用DelegatingIntroductionInterceptor对于大多数情况来说是最好的。spring-doc.cadn.net.cn

DelegatingIntroductionInterceptor旨在将介绍委托给 实际实现引入的接口,隐蔽使用拦截 这样做。您可以使用构造函数参数将委托设置为任何对象。这 默认委托(当使用无参数构造函数时)是this.因此,在下一个示例中, 委托是LockMixin的子类DelegatingIntroductionInterceptor. 给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor实例 查找委托实现的所有接口(除了IntroductionInterceptor)并支持针对其中任何一个的介绍。 子类,例如LockMixin可以调用suppressInterface(Class intf)抑制不应公开的接口的方法。然而,无论有多少 接口IntroductionInterceptor准备支持,IntroductionAdvisorused 控制实际公开的接口。一 引入的接口隐藏目标对同一接口的任何实现。spring-doc.cadn.net.cn

因此LockMixin延伸DelegatingIntroductionInterceptor和机具Lockable本身。超类会自动拾取Lockable可以支持 介绍,所以我们不需要具体说明。我们可以引入任意数量的 接口。spring-doc.cadn.net.cn

请注意使用locked实例变量。这有效地添加了额外的状态 到目标对象中持有的那个。spring-doc.cadn.net.cn

以下示例显示了示例LockMixin类:spring-doc.cadn.net.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)
    }

}

通常,您不需要覆盖invoke()方法。这DelegatingIntroductionInterceptor实现(调用delegate方法 如果 引入该方法,否则朝着连接点前进)通常 够。在本例中,我们需要添加一个检查:不能调用任何 setter 方法 如果处于锁定模式。spring-doc.cadn.net.cn

所需的引言只需要保留一个不同的LockMixin实例并指定引入的接口(在本例中,仅Lockable).更复杂的示例可能会参考引言 拦截器(将被定义为原型)。在这种情况下,没有 与LockMixin,因此我们使用new. 以下示例显示了我们的LockMixinAdvisor类:spring-doc.cadn.net.cn

Java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
Kotlin
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)

我们可以非常简单地应用这个顾问,因为它不需要配置。(但是,它不可能使用IntroductionInterceptor没有IntroductionAdvisor.) 与介绍一样,顾问必须是每个实例,因为它是有状态的。我们需要一个不同的实例LockMixinAdvisor,因此LockMixin,对于每个建议对象。顾问程序包括建议对象的 州。spring-doc.cadn.net.cn

我们可以通过使用Advised.addAdvisor()方法或(推荐的方式)在 XML 配置中,就像任何其他顾问一样。所有代理创建下面讨论的选择,包括“自动代理创建者”,正确处理介绍和有状态混合。spring-doc.cadn.net.cn

6.3. Spring 中的 Advisor API

在 Spring 中,Advisor 是一个方面,它仅包含与关联的通知对象与切入点表达式。spring-doc.cadn.net.cn

除了介绍的特殊情况外,任何顾问都可以获得任何建议。org.springframework.aop.support.DefaultPointcutAdvisor是最常用的 顾问类。它可以与MethodInterceptor,BeforeAdviceThrowsAdvice.spring-doc.cadn.net.cn

可以在 Spring 中在同一个 AOP 代理中混合使用 advisor 和 advise 类型。 为 例如,您可以在一个代理配置中使用 advice、throws advice 和before advice 的拦截。Spring 自动创建必要的拦截器 链。spring-doc.cadn.net.cn

6.4. 使用ProxyFactoryBean创建 AOP 代理

如果您使用 Spring IoC 容器(ApplicationContextBeanFactory) 用于您的业务对象(您应该如此!),您想使用 Spring 的 AOP 之一FactoryBean实现。 (请记住,工厂 bean 引入了一层间接层,让它创建不同类型的对象。spring-doc.cadn.net.cn

Spring AOP 支持还在盖子下使用工厂 bean。

在 Spring 中创建 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean. 这可以完全控制切入点、任何适用的建议及其顺序。但是,有更简单的如果您不需要此类控制,则更可取的选项。spring-doc.cadn.net.cn

6.4.1. 基础知识

ProxyFactoryBean,像其他弹簧一样FactoryBean实现,引入了间接级别。如果您定义了ProxyFactoryBeanfoo,对象 参考foo看不到ProxyFactoryBean实例本身,但是一个对象由getObject()方法ProxyFactoryBean. 这 方法创建一个包装目标对象的 AOP 代理。spring-doc.cadn.net.cn

使用ProxyFactoryBean或其他 IoC 感知创建 AOP 代理的类是建议和切入点也可以由 IoC 管理。这是一项强大的功能,支持某些难以实现的方法使用其他 AOP 框架实现。例如,建议本身可能引用应用程序对象(除了目标,该对象应该在任何 AOP 中可用框架),受益于依赖注入提供的所有可插拔性。spring-doc.cadn.net.cn

6.4.2. JavaBean 属性

与大多数人共同FactoryBeanSpring 提供的实现,则ProxyFactoryBeanclass 本身就是一个 JavaBean。它的属性用于:spring-doc.cadn.net.cn

一些关键属性继承自org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括 以下内容:spring-doc.cadn.net.cn

  • proxyTargetClass:true如果要代理目标类,而不是 目标类的接口。如果此属性值设置为true,然后是 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。spring-doc.cadn.net.cn

  • optimize:控制是否对代理应用积极优化 通过 CGLIB 创建。除非您完全 了解相关 AOP 代理如何处理优化。目前使用 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。spring-doc.cadn.net.cn

  • frozen:如果代理配置为frozen,对配置的更改是不再允许。这既可以作为轻微的优化,也适用于以下情况当您不希望调用者能够作代理时(通过Advisedinterface) 创建代理后。此属性的默认值为false,因此允许更改(例如添加其他建议)。spring-doc.cadn.net.cn

  • exposeProxy:确定当前代理是否应在ThreadLocal以便目标可以访问它。如果目标需要获取代理和exposeProxy属性设置为true,目标可以使用AopContext.currentProxy()方法。spring-doc.cadn.net.cn

特定于的其他属性ProxyFactoryBean包括以下内容:spring-doc.cadn.net.cn

  • proxyInterfaces:数组String接口名称。如果未提供此值,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。spring-doc.cadn.net.cn

  • interceptorNames:一个String数组Advisor、拦截器或其他建议名称 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。spring-doc.cadn.net.cn

    这些名称是当前工厂中的 bean 名称,包括祖先中的 bean 名称 工厂。您不能在这里提及 bean 引用,因为这样做会导致ProxyFactoryBean忽略通知的单例设置。spring-doc.cadn.net.cn

    您可以在拦截器名称后附加星号 ()。这样做会导致 应用名称以星号前部分开头的所有 Advisor Bean 待应用。您可以在使用“全局”顾问中找到使用此功能的示例。*spring-doc.cadn.net.cn

  • singleton:工厂是否应该返回单个对象,无论如何 通常getObject()方法被调用。几个FactoryBean实施提供 这样的方法。默认值为true.如果您想使用有状态建议 - 对于 例如,对于有状态的 mixin - 使用原型建议以及单例值false.spring-doc.cadn.net.cn

6.4.3. 基于JDK和CGLIB的代理

本节是关于如何ProxyFactoryBean选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理 对象(要代理)。spring-doc.cadn.net.cn

的行为ProxyFactoryBean关于创建基于 JDK 或 CGLIB 的 代理在 Spring 1.2.x 和 2.0 版本之间发生了变化。这ProxyFactoryBean现在 在自动检测接口方面表现出与TransactionProxyFactoryBean类。

如果要代理的目标对象的类(以下简称为 目标类)不实现任何接口,基于 CGLIB 的代理是 创建。这是最简单的场景,因为 JDK 代理是基于接口的,没有 接口意味着 JDK 代理甚至不可能。您可以插入目标 Bean 并通过设置interceptorNames财产。请注意,一个 创建基于 CGLIB 的代理,即使proxyTargetClass属性的ProxyFactoryBean已设置为false.(这样做没有意义,而且是最好的 从 bean 定义中删除,因为它充其量是多余的,最坏的情况是多余的 令人困惑。spring-doc.cadn.net.cn

如果目标类实现一个(或多个)接口,则代理类型为 created 取决于ProxyFactoryBean.spring-doc.cadn.net.cn

如果proxyTargetClass属性的ProxyFactoryBean已设置为true, 创建基于 CGLIB 的代理。这是有道理的,并且符合 最小惊喜原则。即使proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个完全限定的接口名称,事实 该proxyTargetClass属性设置为true导致基于 CGLIB 代理生效。spring-doc.cadn.net.cn

如果proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个 完全限定的接口名称,则创建基于 JDK 的代理。创建的 proxy 实现在proxyInterfaces财产。如果目标类恰好实现了比 中指定的proxyInterfaces财产,这一切都很好,但那些 返回的代理不会实现其他接口。spring-doc.cadn.net.cn

如果proxyInterfaces属性的ProxyFactoryBean还没有设定,但是 目标类确实实现了一个(或多个)接口,即ProxyFactoryBean自动检测目标类实际执行的事实 实现至少一个接口,并创建一个基于 JDK 的代理。接口 实际代理的是目标类的所有接口 实现。实际上,这与提供每个 目标类实现到proxyInterfaces财产。然而 它的工作量明显减少,也不易出现印刷错误。spring-doc.cadn.net.cn

6.4.4. 代理接口

考虑一个简单的例子ProxyFactoryBean在行动。此示例涉及:spring-doc.cadn.net.cn

以下列表显示了示例:spring-doc.cadn.net.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>

请注意,interceptorNames属性采用String,其中包含 当前工厂的拦截器或顾问。您可以使用顾问、拦截器、之前、之后 返回,并抛出建议对象。顾问的顺序很重要。spring-doc.cadn.net.cn

您可能想知道为什么该列表不包含 bean 引用。原因是 如果ProxyFactoryBean设置为false,它必须能够 返回独立的代理实例。如果任何顾问本身是原型,则 独立实例需要返回,因此必须能够获得 出厂时原型的实例。持有推荐信是不够的。

person前面显示的 bean 定义可以用来代替Person实现,作为 遵循:spring-doc.cadn.net.cn

Java
Person person = (Person) factory.getBean("person");
Kotlin
val person = factory.getBean("person") as Person;

同一 IoC 上下文中的其他 bean 可以表达对它的强类型依赖关系,如 使用普通的 Java 对象。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

PersonUser此示例中的 class 公开了 typePerson.就 它担心,AOP 代理可以透明地代替“真实”人使用 实现。但是,它的类将是一个动态代理类。这是可能的 将其转换为Advised界面(稍后讨论)。spring-doc.cadn.net.cn

您可以使用匿名 内豆。只有ProxyFactoryBean定义不同。这 仅为完整起见,包含建议。以下示例演示如何使用 匿名内豆:spring-doc.cadn.net.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 的优点是只有一个类型的对象Person.如果我们想要,这很有用 防止应用程序上下文的用户获取对 UN-advised 的引用 对象或需要避免 Spring IoC 自动布线的任何歧义。还有, 可以说,一个优势在于ProxyFactoryBean定义是独立的。 但是,有时能够从 工厂实际上可能是一个优势(例如,在某些测试场景中)。spring-doc.cadn.net.cn

6.4.5. 代理类

如果您需要代理一个类,而不是一个或多个接口,该怎么办?spring-doc.cadn.net.cn

想象一下,在我们前面的示例中,没有Person接口。我们需要提供建议 一个名为Person没有实现任何业务接口。在这种情况下,您 可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass属性ProxyFactoryBean前面显示为true.虽然最好 程序到接口而不是类,能够建议没有 实现接口在处理遗留代码时非常有用。(一般来说,Spring 不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制 特殊方法。spring-doc.cadn.net.cn

如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你确实有 接口。spring-doc.cadn.net.cn

CGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 配置此生成的子类,以将方法调用委托给原始目标。这 子类用于实现 Decorator 模式,编织 vice。spring-doc.cadn.net.cn

CGLIB 代理通常应该对用户透明。但是,也存在一些问题 要考虑:spring-doc.cadn.net.cn

  • Final不能建议方法,因为它们不能被覆盖。spring-doc.cadn.net.cn

  • 无需将 CGLIB 添加到类路径中。从 Spring 3.2 开始,CGLIB 被重新打包 并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 工作“出于 盒子“,JDK 动态代理也是如此。spring-doc.cadn.net.cn

CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应成为决定性的考虑因素。spring-doc.cadn.net.cn

6.4.6. 使用“全局”顾问

通过将星号附加到拦截器名称,所有具有匹配 bean 名称的顾问 星号之前的部分将添加到顾问链中。这可以派上用场 如果您需要添加一组标准的“全球”顾问。以下示例定义 两位全球顾问:spring-doc.cadn.net.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.cadn.net.cn

首先,我们为代理创建一个父模板 bean 定义,如下所示:spring-doc.cadn.net.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.cadn.net.cn

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

您可以覆盖父模板中的属性。在以下示例中, 我们覆盖事务传播设置:spring-doc.cadn.net.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 定义显式标记为 通过设置abstract属性设置为true,如前所述,因此它实际上可能永远不会 实例。应用程序上下文(但不是简单的 bean 工厂),默认情况下, 预实例化所有单例。因此,这很重要(至少对于单例 bean 来说是这样) 如果你有一个(父)bean 定义,你打算只用作模板, 并且此定义指定了一个类,您必须确保将abstract属性设置为true.否则,应用程序上下文实际上会尝试 预实例化它。spring-doc.cadn.net.cn

6.6. 使用ProxyFactory

使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以使用 不依赖 Spring IoC 的 Spring AOP。spring-doc.cadn.net.cn

目标对象实现的接口是 自动代理。以下列表显示了为目标对象创建代理,其中一个 拦截器和一名顾问:spring-doc.cadn.net.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

第一步是构造一个类型为org.springframework.aop.framework.ProxyFactory.您可以使用目标创建此 对象,如前面的示例所示,或指定要在备用 构造 函数。spring-doc.cadn.net.cn

您可以添加建议(将拦截器作为一种专门的建议)、顾问或两者兼而有之 并纵他们以维持生命ProxyFactory.如果您添加IntroductionInterceptionAroundAdvisor,您可以使代理实现额外的 接口。spring-doc.cadn.net.cn

上也有方便的方法ProxyFactory(继承自AdvisedSupport) 允许您添加其他建议类型,例如之前和抛出建议。AdvisedSupport是两者的超类ProxyFactoryProxyFactoryBean.spring-doc.cadn.net.cn

将 AOP 代理创建与 IoC 框架集成是大多数人的最佳实践 应用。我们建议您使用 AOP 从 Java 代码外部化配置, 正如你一般应该做的那样。

6.7.作建议对象

无论您如何创建 AOP 代理,您都可以通过使用org.springframework.aop.framework.Advised接口。任何 AOP 代理都可以转换为此 接口,无论它实现了哪些其他接口。此接口包括 以下方法:spring-doc.cadn.net.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

getAdvisors()方法返回一个Advisor对于每个顾问、拦截器或 已添加到工厂的其他通知类型。如果您添加了Advisor这 此索引处返回的顾问是您添加的对象。如果您添加了 拦截器或其他建议类型,Spring 将其包装在一个顾问中,并带有 始终返回的切入点true.因此,如果您添加了MethodInterceptor、顾问 为此索引返回的是DefaultPointcutAdvisor返回您的MethodInterceptor以及匹配所有类和方法的切入点。spring-doc.cadn.net.cn

addAdvisor()方法可用于添加任何Advisor.通常,顾问持有 切入点和建议是通用的DefaultPointcutAdvisor,您可以将其与 任何建议或切入点(但不用于介绍)。spring-doc.cadn.net.cn

默认情况下,即使曾经是代理,也可以添加或删除顾问或拦截器 已被创建。唯一的限制是无法添加或删除 介绍顾问,因为工厂的现有代理不显示界面 改变。(您可以从工厂获取新的代理以避免此问题。spring-doc.cadn.net.cn

以下示例演示了将 AOP 代理转换为Advised接口和检查以及 纵其建议:spring-doc.cadn.net.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)
是否建议修改对业务对象的建议是否可取(没有双关语)是值得怀疑的,尽管毫无疑问,存在合法的用例。但是,它在开发中非常有用(例如,在测试中)。我们有时发现能够以拦截器或其他建议的形式添加测试代码非常有用,进入我们想要测试的方法调用。(例如,建议可以进入为该方法创建的事务,也许可以运行 SQL 来检查数据库是否正确更新,然后再将事务标记为回滚。

根据您创建代理的方式,您通常可以将frozen旗。 在该情况下,该Advised isFrozen()方法返回true,以及任何修改的尝试 通过添加或删除的建议会导致AopConfigException.能力 在某些情况下,冻结建议对象的状态很有用(例如,将 防止调用代码删除安全拦截器)。spring-doc.cadn.net.cn

6.8. 使用“自动代理”工具

到目前为止,我们已经考虑了通过使用ProxyFactoryBean或 类似工厂豆。spring-doc.cadn.net.cn

Spring 还允许我们使用“自动代理”bean 定义,它可以自动 代理选择的 Bean 定义。这是建立在 Spring 的“bean 后处理器”之上的 基础设施,它允许在容器加载时修改任何 Bean 定义。spring-doc.cadn.net.cn

在此模型中,您在 XML Bean 定义文件中设置了一些特殊的 Bean 定义 以配置自动代理基础结构。这使您可以声明目标 符合自动代理的条件。您无需使用ProxyFactoryBean.spring-doc.cadn.net.cn

有两种方法可以做到这一点:spring-doc.cadn.net.cn

  • 通过使用引用当前上下文中特定 bean 的自动代理创建器。spring-doc.cadn.net.cn

  • 值得单独考虑的自动代理创建的特例: 由源级元数据属性驱动的自动代理创建。spring-doc.cadn.net.cn

6.8.1. 自动代理 Bean 定义

本节介绍由org.springframework.aop.framework.autoproxy包。spring-doc.cadn.net.cn

BeanNameAutoProxyCreator

BeanNameAutoProxyCreatorclass 是一个BeanPostProcessor自动创建 名称与文字值或通配符匹配的 bean 的 AOP 代理。以下内容 示例演示了如何创建BeanNameAutoProxyCreator豆:spring-doc.cadn.net.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>

ProxyFactoryBean,有一个interceptorNames属性而不是列表 的拦截器,以允许原型顾问正确行为。命名为“拦截器” 可以是顾问或任何建议类型。spring-doc.cadn.net.cn

与一般的自动代理一样,使用的主要点BeanNameAutoProxyCreator是 将相同的配置一致地应用于多个对象,并将最小体积 配置。它是将声明性事务应用于多个 对象。spring-doc.cadn.net.cn

名称匹配的 Bean 定义,例如jdkMyBeanonlyJdk在前面的 示例,是具有目标类的普通旧 Bean 定义。AOP 代理是 由BeanNameAutoProxyCreator.同样的建议也适用 到所有匹配的豆子。请注意,如果使用顾问(而不是 前面的示例),切入点可能以不同的方式应用于不同的 bean。spring-doc.cadn.net.cn

DefaultAdvisorAutoProxyCreator

一个更通用且极其强大的自动代理创建器是DefaultAdvisorAutoProxyCreator.这会自动将符合条件的顾问应用于 当前上下文,无需在自动代理中包含特定的 bean 名称 advisor 的 bean 定义。它具有相同的优点,即配置一致,并且 避免重复BeanNameAutoProxyCreator.spring-doc.cadn.net.cn

使用此机制涉及:spring-doc.cadn.net.cn

  • 指定DefaultAdvisorAutoProxyCreatorbean 定义。spring-doc.cadn.net.cn

  • 在相同或相关上下文中指定任意数量的顾问。请注意,这些必须是顾问,而不是拦截器或其他建议。这是必要的,因为必须有一个切入点来评估,以检查每个通知的资格到候选 bean 定义。spring-doc.cadn.net.cn

DefaultAdvisorAutoProxyCreator自动评估每个顾问程序中包含的切入点,以查看它应该应用于每个业务对象的哪些(如果有)建议(例如businessObject1businessObject2在示例中)。spring-doc.cadn.net.cn

这意味着可以自动将任意数量的顾问应用于每个业务 对象。 如果任何顾问程序中没有切入点与业务对象中的任何方法匹配,对象不会被代理。当为新的业务对象添加 Bean 定义时,如有必要,它们会自动代理。spring-doc.cadn.net.cn

一般来说,自动代理的优点是使调用者或依赖项无法获得非建议对象。 叫getBean("businessObject1")关于这个ApplicationContext返回一个 AOP 代理,而不是目标业务对象。(前面显示的“内部bean”习惯用法也提供了这个好处。spring-doc.cadn.net.cn

以下示例创建了一个DefaultAdvisorAutoProxyCreatorbean 和其他本节讨论的元素:spring-doc.cadn.net.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"/>

DefaultAdvisorAutoProxyCreator如果您想将相同的建议一致地应用于许多业务对象,则非常有用。一旦基础架构定义到位,您可以添加新的业务对象,而无需包含特定的代理配置。您还可以轻松地添加其他方面(例如,跟踪或性能监控方面),只需对配置进行最小的更改。spring-doc.cadn.net.cn

DefaultAdvisorAutoProxyCreator提供对筛选的支持(通过使用 约定,以便仅评估某些顾问,这允许使用多个 配置不同,AdvisorAutoProxyCreators 在同一工厂中)和排序。 顾问可以实现org.springframework.core.Ordered接口以确保 如果这是一个问题,请正确订购。这TransactionAttributeSourceAdvisor用于 前面的示例具有可配置的订单值。默认设置为无序。spring-doc.cadn.net.cn

6.9. 使用TargetSource实现

Spring 提供了TargetSource,以org.springframework.aop.TargetSource接口。该接口负责 返回实现连接点的“目标对象”。这TargetSource每次 AOP 代理处理方法时都会要求实现目标实例 调用。spring-doc.cadn.net.cn

使用 Spring AOP 的开发人员通常不需要直接使用TargetSource实现,但 这提供了一种支持池化、热插拔和其他 复杂的目标。例如,池化TargetSource可以返回不同的目标 实例,通过使用池来管理实例。spring-doc.cadn.net.cn

如果未指定TargetSource,则默认实现用于包装 local 对象。每次调用都会返回相同的目标(正如您所期望的那样)。spring-doc.cadn.net.cn

本节的其余部分介绍了 Spring 提供的标准目标源以及如何使用它们。spring-doc.cadn.net.cn

使用自定义目标源时,您的目标通常需要是原型 而不是单例 bean 定义。这允许 Spring 创建一个新的目标 实例。

6.9.1. 热插拔目标源

org.springframework.aop.target.HotSwappableTargetSource存在是为了让目标 的 AOP 代理进行切换,同时让调用方保留对它的引用。spring-doc.cadn.net.cn

更改目标源的目标立即生效。这HotSwappableTargetSource是线程安全的。spring-doc.cadn.net.cn

您可以使用swap()方法,如以下示例所示:spring-doc.cadn.net.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.cadn.net.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>

前面的swap()调用更改可交换 bean 的目标。持有 引用该 bean 时不知道更改,但立即开始点击 新目标。spring-doc.cadn.net.cn

虽然这个例子没有添加任何建议(没有必要向 使用TargetSource)、任何TargetSource可与 任意建议。spring-doc.cadn.net.cn

6.9.2. 池化目标源

使用池化目标源提供与无状态会话类似的编程模型 EJB,其中维护相同实例的池,并调用方法。 去释放池中的对象。spring-doc.cadn.net.cn

Spring 池化和 SLSB 池化之间的一个关键区别在于,Spring 池化可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。spring-doc.cadn.net.cn

Spring 为 Commons Pool 2.2 提供支持,它提供了一个 相当有效的池化实现。您需要commons-poolJar 在您的 应用程序的类路径以使用此功能。您还可以将子类化org.springframework.aop.target.AbstractPoolingTargetSource支持任何其他 池化 API。spring-doc.cadn.net.cn

Commons Pool 1.5+ 也受支持,但从 Spring Framework 4.2 开始被弃用。

以下列表显示了示例配置:spring-doc.cadn.net.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>

请注意,目标对象 (businessObjectTarget在前面的示例中)必须是 原型。这允许PoolingTargetSource实现创建新实例 目标的,以根据需要扩大池。请参阅javadoc 的AbstractPoolingTargetSource以及您希望用于信息的具体子类 关于它的属性。maxSize是最基本的,总是保证在场。spring-doc.cadn.net.cn

在这种情况下,myInterceptor是需要 在同一 IoC 上下文中定义。但是,您无需指定拦截器 使用池化。如果您只想要池化而不需要其他建议,请不要将interceptorNames财产。spring-doc.cadn.net.cn

您可以将 Spring 配置为能够将任何池化对象转换为org.springframework.aop.target.PoolingConfig接口,用于公开信息 通过介绍池的配置和当前规模。你 需要定义类似于以下内容的顾问:spring-doc.cadn.net.cn

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

此顾问是通过调用AbstractPoolingTargetSource类,因此使用MethodInvokingFactoryBean.这 顾问姓名 (poolConfigAdvisor,此处)必须位于 这ProxyFactoryBean,这将公开池化对象。spring-doc.cadn.net.cn

演员阵容定义如下:spring-doc.cadn.net.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)
通常不需要池化无状态服务对象。我们认为它不应该 是默认选择,因为大多数无状态对象自然是线程安全的,并且实例 如果缓存了资源,则池化是有问题的。

使用自动代理可以实现更简单的池化。您可以将TargetSource实现 由任何自动代理创建者使用。spring-doc.cadn.net.cn

6.9.3. 原型目标源

设置“原型”目标源类似于设置池TargetSource.在这个 情况下,每次方法调用时都会创建一个目标的新实例。虽然 在现代 JVM 中创建新对象的成本并不高,连接 new 对象(满足其 IoC 依赖项)可能更昂贵。因此,您不应该 没有充分理由使用这种方法。spring-doc.cadn.net.cn

为此,您可以修改poolTargetSource定义如下 (为了清楚起见,我们还更改了名称):spring-doc.cadn.net.cn

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一的属性是目标 Bean 的名称。继承用于TargetSource实现以确保命名的一致性。与池化目标一样 source,目标 Bean 必须是原型 Bean 定义。spring-doc.cadn.net.cn

6.9.4.ThreadLocal目标来源

ThreadLocal如果需要为每个对象创建对象,则目标源非常有用 传入请求(即每个线程)。的概念ThreadLocal提供 JDK 范围的 将资源透明地存储在线程旁边。设置ThreadLocalTargetSource与其他类型的解释几乎相同 目标源,如以下示例所示:spring-doc.cadn.net.cn

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal实例会出现严重问题(可能导致内存泄漏) 在多线程和多类加载器环境中错误地使用它们。你 应该始终考虑将 threadlocal 包装在其他类中,并且永远不要直接使用 这ThreadLocal本身(包装器类除外)。此外,你应该 始终记住正确设置和取消设置(后者仅涉及调用ThreadLocal.set(null)) 线程本地资源。取消设置应在 无论如何,因为不取消设置可能会导致有问题的行为。Spring的ThreadLocal支持会为您执行此作,并且应始终考虑使用ThreadLocal没有其他正确处理代码的实例。

6.10. 定义新的通知类型

Spring AOP 被设计为可扩展的。而拦截实施策略 目前在内部使用,可以支持 除了围绕建议的拦截之外,before,抛出建议,以及 在返回建议后。spring-doc.cadn.net.cn

org.springframework.aop.framework.adapterpackage 是一个 SPI 包,它允许 在不更改核心框架的情况下添加对新自定义通知类型的支持。 自定义的唯一约束Advicetype 是它必须实现org.aopalliance.aop.Advicemarker 接口。spring-doc.cadn.net.cn

7. 零安全

尽管 Java 不允许您使用其类型系统来表达 null 安全性,但 Spring Framework 现在在org.springframework.lang套餐让你 声明 API 和字段的可空性:spring-doc.cadn.net.cn

Spring Framework 本身利用了这些注释,但它们也可以用于任何 基于 Spring 的 Java 项目,用于声明空安全 API 和可选的空安全字段。 泛型类型参数、varargs 和数组元素可空性尚不支持,但 应包含在即将发布的版本中,请参阅 SPR-15942 了解最新信息。可空性声明应在 Spring Framework 版本,包括次要版本。方法内部使用的类型的可空性 正文不属于此功能的范围。spring-doc.cadn.net.cn

其他常见库(例如 Reactor 和 Spring Data)提供空安全 API,这些 API 使用类似的可空性安排,为 Spring 应用程序开发人员。

7.1. 用例

除了为 Spring Framework API 可空性提供显式声明之外,IDE(例如 IDEA 或 Eclipse)可以使用这些注释来提供有用的与空安全相关的警告,以避免NullPointerException在运行时。spring-doc.cadn.net.cn

它们还用于使 Kotlin 项目中的 Spring API 为空安全,因为 Kotlin 本身支持 null 安全。更多详情可在 Kotlin 支持文档中找到。spring-doc.cadn.net.cn

7.2. JSR-305 元注解

Spring 注解使用 JSR 305 注解(一种休眠但广泛传播的 JSR)进行元注解。JSR-305 元注解让工具提供商如 IDEA 或 Kotlin 以通用方式提供空安全支持,而无需对 Spring 注解的硬代码支持。spring-doc.cadn.net.cn

没有必要也不建议将 JSR-305 依赖项添加到项目类路径中以利用 Spring 空安全 API。只有使用基于 Spring 的库等项目才应在其代码库中使用null-safety 注释com.google.code.findbugs:jsr305:3.0.2compileOnlyGradle 配置或 Mavenprovided作用域以避免编译警告。spring-doc.cadn.net.cn

8. 数据缓冲区和编解码器

Java NIO 提供ByteBuffer但许多库在上面构建了自己的字节缓冲区 API, 特别是对于重用缓冲区和/或使用直接缓冲区的网络作 有利于性能。例如,Netty 具有ByteBuf层次结构,Undertow 使用 XNIO、Jetty 使用池字节缓冲区和要释放的回调,依此类推。 这spring-core模块提供了一组抽象来处理各种字节缓冲区 API 如下所示:spring-doc.cadn.net.cn

8.1.DataBufferFactory

DataBufferFactory用于通过以下两种方式之一创建数据缓冲区:spring-doc.cadn.net.cn

  1. 分配一个新的数据缓冲区,如果已知,可以选择预先指定容量,即 即使实现DataBuffer可以按需增长和缩小。spring-doc.cadn.net.cn

  2. 包装现有的byte[]java.nio.ByteBuffer,它用 一个DataBuffer实施,这不涉及分配。spring-doc.cadn.net.cn

请注意,WebFlux 应用程序不会创建DataBufferFactory直接而是 通过ServerHttpResponseClientHttpRequest在客户端。 工厂的类型取决于底层客户端或服务器,例如NettyDataBufferFactory对于 Reactor Netty,DefaultDataBufferFactory对于其他人来说。spring-doc.cadn.net.cn

8.2.DataBuffer

DataBuffer接口提供与java.nio.ByteBuffer而且还 带来了一些额外的好处,其中一些是受到 Netty 的启发ByteBuf. 以下是部分福利列表:spring-doc.cadn.net.cn

8.3.PooledDataBuffer

正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可能驻留在 Java 堆之外 这消除了本机 I/O作的复制需求。这使得直接缓冲区 对于通过套接字接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。spring-doc.cadn.net.cn

PooledDataBufferDataBuffer这有助于引用计数,其中对于字节缓冲池至关重要。它是如何工作的?当一个PooledDataBuffer是 分配的引用计数为 1。调用retain()增加计数,而 调用release()递减它。只要计数高于 0,缓冲区为 保证不放。当计数减少到 0 时,池化缓冲区可以 released,这实际上可能意味着缓冲区的保留内存将返回到 内存池。spring-doc.cadn.net.cn

请注意,而不是在PooledDataBuffer直接,在大多数情况下更好 使用DataBufferUtils将发布或保留应用于DataBuffer仅当它是PooledDataBuffer.spring-doc.cadn.net.cn

8.4.DataBufferUtils

DataBufferUtils提供了许多实用方法来对数据缓冲区进行作:spring-doc.cadn.net.cn

  • 将数据缓冲区流连接到一个缓冲区中,可能具有零副本,例如通过 复合缓冲区,如果基础字节缓冲区 API 支持。spring-doc.cadn.net.cn

  • InputStream或蔚来ChannelFlux<DataBuffer>,反之亦然Publisher<DataBuffer>OutputStream或蔚来Channel.spring-doc.cadn.net.cn

  • 释放或保留DataBuffer如果缓冲区是PooledDataBuffer.spring-doc.cadn.net.cn

  • 跳过或从字节流中获取,直到特定的字节计数。spring-doc.cadn.net.cn

8.5. 编解码器

org.springframework.core.codecpackage 提供以下策略接口:spring-doc.cadn.net.cn

spring-core模块提供byte[],ByteBuffer,DataBuffer,ResourceString编码器和解码器实现。这spring-web模块添加 Jackson JSON, Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器spring-doc.cadn.net.cn

8.6. 使用DataBuffer

使用数据缓冲区时,必须特别注意确保释放缓冲区 因为它们可以合并。我们将使用编解码器来说明 这是如何工作的,但这些概念更普遍地适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。spring-doc.cadn.net.cn

一个Decoder在创建更高级别之前,最后读取输入数据缓冲区 对象,因此它必须按如下方式释放它们:spring-doc.cadn.net.cn

  1. 如果Decoder只需读取每个输入缓冲区并准备好 立即释放它,它可以通过DataBufferUtils.release(dataBuffer).spring-doc.cadn.net.cn

  2. 如果Decoder正在使用FluxMono运算符,例如flatMap,reduce和 其他在内部预取和缓存数据项,或者使用filter,skip,以及其他遗漏项目的项目,然后doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)必须添加到组合链,以确保此类缓冲区在被丢弃之前被释放,可能是也是错误或取消信号的结果。spring-doc.cadn.net.cn

  3. 如果Decoder以任何其他方式保留一个或多个数据缓冲区,它必须确保它们在完全读取时被释放,或者在出现错误或取消信号时释放发生在缓存的数据缓冲区被读取和释放之前。spring-doc.cadn.net.cn

请注意DataBufferUtils#join提供了一种安全有效的数据聚合方式 缓冲区流到单个数据缓冲区中。同样skipUntilByteCounttakeUntilByteCount是解码器使用的其他安全方法。spring-doc.cadn.net.cn

Encoder分配其他人必须读取(和释放)的数据缓冲区。所以一个Encoder没什么可做的。然而,一个Encoder如果出现以下情况,必须注意释放数据缓冲区 使用数据填充缓冲区时发生序列化错误。例如:spring-doc.cadn.net.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

的消费者Encoder负责释放它接收到的数据缓冲区。在 WebFlux 应用程序中,Encoder用于写入 HTTP 服务器响应,或写入客户端 HTTP 请求,在这种情况下,释放数据缓冲区是编写到服务器响应或客户端请求的代码的责任。spring-doc.cadn.net.cn

请注意,在 Netty 上运行时,有用于解决缓冲区泄漏问题的调试选项。spring-doc.cadn.net.cn

9. 日志记录

从 Spring Framework 5.0 开始,Spring 实现了自己的 Commons Logging 桥 在spring-jcl模块。 该实现检查类路径中是否存在 Log4j 2.xAPI 和 SLF4J 1.7 API,并使用第一个作为logging 实现,回退到 Java 平台的核心日志记录工具(也称为称为 JULjava.util.logging),如果 Log4j 2.x 和 SLF4J 都不可用。spring-doc.cadn.net.cn

将 Log4j 2.x 或 Logback(或其他 SLF4J 提供程序)放在您的类路径中,无需任何额外的桥接器,并让框架自动适应您的选择。有关更多信息,请参阅 Spring引导日志记录参考文档spring-doc.cadn.net.cn

Spring 的 Commons Logging 变体仅用于基础设施日志记录核心框架和扩展中的目的。spring-doc.cadn.net.cn

对于应用程序代码中的日志记录需求,最好直接使用 Log4j 2.x、SLF4J 或 JUL。spring-doc.cadn.net.cn

一个Log可以通过以下方式检索实现org.apache.commons.logging.LogFactory如下面的例子。spring-doc.cadn.net.cn

Java
public class MyBean {
    private final Log log = LogFactory.getLog(getClass());
    // ...
}
Kotlin
class MyBean {
  private val log = LogFactory.getLog(javaClass)
  // ...
}

10. 附录

10.1. XML模式

附录的这一部分列出了与核心容器相关的 XML 模式。spring-doc.cadn.net.cn

10.1.1.util图式

顾名思义,util标签处理常见的实用程序配置 问题,例如配置集合、引用常量等。 要使用utilschema,您需要在顶部有以下前导码 Spring XML 配置文件的(代码片段中的文本引用了 correct schema,以便util命名空间可供您使用):spring-doc.cadn.net.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.cadn.net.cn

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

前面的配置使用 SpringFactoryBean实现(FieldRetrievingFactoryBean) 设置isolationbean 上的属性 设置为java.sql.Connection.TRANSACTION_SERIALIZABLE不断。这是 一切都很好,但它很冗长,并且(不必要地)暴露了 Spring 的内部 为最终用户提供管道。spring-doc.cadn.net.cn

下面基于 XML Schema 的版本更简洁,清楚地表达了 developer's intent (“inject this constant value”),读起来更好:spring-doc.cadn.net.cn

<bean id="..." class="...">
    <property name="isolation">
        <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
</bean>
从字段值设置 Bean 属性或构造函数参数

FieldRetrievingFactoryBean是一个FactoryBean检索static或非静态字段值。通常是 用于检索public static final常量,然后可用于设置 另一个 bean 的属性值或构造函数参数。spring-doc.cadn.net.cn

以下示例显示了如何static字段,通过使用staticField财产:spring-doc.cadn.net.cn

<bean id="myField"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

还有一个方便的使用形式,其中static字段被指定为 bean name,如以下示例所示:spring-doc.cadn.net.cn

<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

这确实意味着不再有任何选择的豆子id是(所以任何其他 指代它的 bean 也必须使用这个较长的名称),但这种形式非常 定义简洁,非常方便用作内 bean,因为id没有 为 bean 引用指定,如以下示例所示:spring-doc.cadn.net.cn

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

您还可以访问另一个 bean 的非静态(实例)字段,如 在 API 文档中描述FieldRetrievingFactoryBean类。spring-doc.cadn.net.cn

将枚举值作为属性或构造函数参数注入 bean 是 Spring很容易做。您实际上不需要做任何事情或了解任何事情 Spring 内部(甚至关于类,例如FieldRetrievingFactoryBean). 以下示例枚举显示了注入枚举值是多么容易:spring-doc.cadn.net.cn

Java
package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
Kotlin
package javax.persistence

enum class PersistenceContextType {

    TRANSACTION,
    EXTENDED
}

现在考虑以下类型的 setterPersistenceContextType以及相应的 bean 定义:spring-doc.cadn.net.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.cadn.net.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"/>

前面的配置使用 SpringFactoryBean实现(PropertyPathFactoryBean) 创建一个 bean(类型为int) 调用testBean.age那 的值等于age属性的testBean豆。spring-doc.cadn.net.cn

现在考虑以下示例,该示例添加了一个<util:property-path/>元素:spring-doc.cadn.net.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.beanProperty.在这种情况下,它会选择age名为testBean.它的价值age属性是10.spring-doc.cadn.net.cn

<util:property-path/>设置 Bean 属性或构造函数参数

PropertyPathFactoryBean是一个FactoryBean计算给定target 对象上的属性路径。目标对象可以直接指定,也可以通过 bean 名称指定。然后,您可以使用此value 在另一个 bean 定义中作为属性值或构造函数 论点。spring-doc.cadn.net.cn

以下示例按名称显示了用于另一个 Bean 的路径:spring-doc.cadn.net.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.cadn.net.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.cadn.net.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"/>

这种形式确实意味着豆子的名字没有选择。任何对它的引用 还必须使用相同的id,这是路径。如果用作内衬 bean,完全不需要引用它,如下例所示:spring-doc.cadn.net.cn

<bean id="..." class="...">
    <property name="age">
        <bean id="person.age"
                class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>

您可以在实际定义中专门设置结果类型。这不是必需的 对于大多数用例,但有时它可能很有用。有关更多信息,请参阅 javadoc 此功能。spring-doc.cadn.net.cn

<util:properties/>

请考虑以下示例:spring-doc.cadn.net.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>

前面的配置使用 SpringFactoryBean实现(PropertiesFactoryBean) 实例化一个java.util.Properties具有值的实例 从提供的Resource位置)。spring-doc.cadn.net.cn

以下示例使用util:properties元素来进行更简洁的表示:spring-doc.cadn.net.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.cadn.net.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>

前面的配置使用 SpringFactoryBean实现(ListFactoryBean) 创建一个java.util.List实例并使用取值对其进行初始化 从提供的sourceList.spring-doc.cadn.net.cn

以下示例使用<util:list/>元素来进行更简洁的表示:spring-doc.cadn.net.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>

您还可以显式控制List实例化的,并且 通过使用list-class属性<util:list/>元素。为 例如,如果我们真的需要一个java.util.LinkedList要实例化,我们可以使用 以下配置:spring-doc.cadn.net.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-class属性,则容器选择List实现。spring-doc.cadn.net.cn

<util:map/>

请考虑以下示例:spring-doc.cadn.net.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>

前面的配置使用 SpringFactoryBean实现(MapFactoryBean) 创建一个java.util.Map使用键值对初始化的实例 取自提供的'sourceMap'.spring-doc.cadn.net.cn

以下示例使用<util:map/>元素来进行更简洁的表示:spring-doc.cadn.net.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>

您还可以显式控制Map实例化的,并且 通过使用'map-class'属性<util:map/>元素。为 例如,如果我们真的需要一个java.util.TreeMap要实例化,我们可以使用 以下配置:spring-doc.cadn.net.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'属性,则容器选择Map实现。spring-doc.cadn.net.cn

<util:set/>

请考虑以下示例:spring-doc.cadn.net.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>

前面的配置使用 SpringFactoryBean实现(SetFactoryBean) 创建一个java.util.Set实例初始化,取值 从提供的sourceSet.spring-doc.cadn.net.cn

以下示例使用<util:set/>元素来进行更简洁的表示:spring-doc.cadn.net.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>

您还可以显式控制Set实例化的,并且 通过使用set-class属性<util:set/>元素。为 例如,如果我们真的需要一个java.util.TreeSet要实例化,我们可以使用 以下配置:spring-doc.cadn.net.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-class属性,则容器选择Set实现。spring-doc.cadn.net.cn

10.1.2.aop图式

aop标签处理在 Spring 中配置 AOP 的所有内容,包括 Spring 的 自己的基于代理的 AOP 框架以及 Spring 与 AspectJ AOP 框架的集成。 这些标签在标题为 Spring 面向方面编程的章节中得到了全面介绍。spring-doc.cadn.net.cn

为了完整起见,要使用aopschema,你需要有 Spring XML 配置文件顶部的以下前导码( snippet 引用正确的架构,以便aopNamespace 可供您使用):spring-doc.cadn.net.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>

10.1.3.context图式

context标签处理ApplicationContext与管道相关的配置——也就是说,通常不是对最终用户很重要的 bean,而是对最终用户很重要的 bean 春季的很多“咕噜声”工作,比如BeanfactoryPostProcessors.以下内容 代码段引用正确的架构,以便context命名空间是 可供您:spring-doc.cadn.net.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给你的。如果您需要对特定PropertySourcesPlaceholderConfigurersetup,您可以自己显式将其定义为 bean。spring-doc.cadn.net.cn

<annotation-config/>

此元素激活 Spring 基础设施以检测 bean 类中的注释:spring-doc.cadn.net.cn

或者,您可以选择显式激活个人BeanPostProcessors对于这些注释。spring-doc.cadn.net.cn

此元素不会激活 Spring 的@Transactional注解; 您可以使用<tx:annotation-driven/>元素。同样,Spring 的缓存注释也需要显式启用
<component-scan/>

此元素在基于注释的容器配置部分中进行了详细说明。spring-doc.cadn.net.cn

<load-time-weaver/>
<spring-configured/>

此元素在使用 AspectJ 向 Spring 依赖注入域对象的部分中进行了详细说明。spring-doc.cadn.net.cn

<mbean-export/>

此元素在有关配置基于注释的 MBean 导出的部分中进行了详细说明。spring-doc.cadn.net.cn

10.1.4. Bean 模式

最后但并非最不重要的一点是,我们在beans图式。 这些元素从框架诞生之初就一直在 Spring 中。各种元素的示例 在beans此处未显示模式,因为它们被非常全面地涵盖在依赖关系和配置中详细介绍(事实上,在整个章节中)。spring-doc.cadn.net.cn

请注意,您可以将零个或多个键值对添加到<bean/>XML 定义。如果有的话,对这些额外的元数据做什么完全取决于你自己的自定义逻辑(因此通常只有在你按照描述编写自己的自定义元素时才有用在标题为 XML 模式创作的附录中)。spring-doc.cadn.net.cn

以下示例显示了<meta/>元素在周围环境的上下文中<bean/>(请注意,如果没有任何逻辑来解释它,元数据实际上毫无用处就目前而言)。spring-doc.cadn.net.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.cadn.net.cn

10.2. XML架构创作

从 2.0 版开始,Spring 具有一种将基于模式的扩展添加到 用于定义和配置 bean 的基本 Spring XML 格式。本节涵盖 如何编写您自己的自定义 XML Bean 定义解析器和 将此类解析器集成到 Spring IoC 容器中。spring-doc.cadn.net.cn

为了方便创作使用架构感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果您不是 熟悉 Spring 当前标准附带的 XML 配置扩展 Spring 发行版,您应该首先阅读上一节关于 XML Schemas 的部分。spring-doc.cadn.net.cn

要创建新的 XML 配置扩展,请执行以下作:spring-doc.cadn.net.cn

  1. 创作 XML 架构来描述自定义元素。spring-doc.cadn.net.cn

  2. 对自定义进行编码NamespaceHandler实现。spring-doc.cadn.net.cn

  3. 编写一个或多个代码BeanDefinitionParser实现 (这是真正完成工作的地方)。spring-doc.cadn.net.cn

  4. 向 Spring 注册您的新工件。spring-doc.cadn.net.cn

对于一个统一的示例,我们创建了一个 XML 扩展(自定义 XML 元素),允许我们配置SimpleDateFormat(从java.text包)。当我们完成时, 我们将能够定义类型SimpleDateFormat如下:spring-doc.cadn.net.cn

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

(我们包括更详细的 本附录后面将介绍示例。第一个简单示例的目的是引导您 通过制作自定义扩展的基本步骤。spring-doc.cadn.net.cn

10.2.1. 创作架构

创建用于 Spring 的 IoC 容器的 XML 配置扩展以 创作 XML 架构来描述扩展。对于我们的示例,我们使用以下架构 配置SimpleDateFormat对象:spring-doc.cadn.net.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 指示的行包含所有可识别标签的扩展基数 (这意味着他们有一个id属性,我们可以将其用作 容器)。我们可以使用此属性,因为我们导入了 Spring 提供的beansNamespace。

前面的架构允许我们配置SimpleDateFormat对象直接在 XML 应用程序上下文文件,使用<myns:dateformat/>元素,如下所示 示例显示:spring-doc.cadn.net.cn

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

请注意,在创建基础结构类后,前面的 XML 片段是 与以下 XML 片段基本相同:spring-doc.cadn.net.cn

<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-MM-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

前面两个片段中的第二个 在容器中创建一个 bean(由名称dateFormat类型SimpleDateFormat) 并设置了几个属性。spring-doc.cadn.net.cn

基于模式的创建配置格式的方法允许紧密集成 使用具有架构感知 XML 编辑器的 IDE。通过使用正确创作的架构,您可以 可以使用自动完成功能让用户在多个配置选项之间进行选择 在枚举中定义。

10.2.2. 编写NamespaceHandler

除了模式之外,我们还需要一个NamespaceHandler解析 Spring 在解析配置文件时遇到的这个特定命名空间。对于此示例,NamespaceHandler应该负责myns:dateformat元素。spring-doc.cadn.net.cn

NamespaceHandler界面具有三种方式:spring-doc.cadn.net.cn

  • init():允许初始化NamespaceHandler并由 在使用处理程序之前弹簧。spring-doc.cadn.net.cn

  • BeanDefinition parse(Element, ParserContext):当 Spring 遇到 顶级元素(不嵌套在 Bean 定义或不同的命名空间中)。 此方法本身可以注册 Bean 定义,返回 Bean 定义,或两者兼而有之。spring-doc.cadn.net.cn

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext):叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。一个或多个 bean 定义的装饰(例如)与 Spring 支持的作用域一起使用。我们首先突出显示一个简单的示例,而不使用装饰,然后我们在一个更高级的示例中展示装饰。spring-doc.cadn.net.cn

虽然你可以编写自己的代码NamespaceHandler对于整个命名空间(因此提供解析命名空间中每个元素的代码),通常情况下,Spring XML 配置文件中的每个顶级 XML 元素都会产生单个 bean 定义(如在我们的例子中,其中单个<myns:dateformat/>元素的结果是单个SimpleDateFormatbean 定义)。Spring 具有多个支持此场景的便利类。在下面的示例中,我们使用NamespaceHandlerSupport类:spring-doc.cadn.net.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())
    }
}

你可能会注意到,实际上并没有太多的解析逻辑。事实上,NamespaceHandlerSupport类有一个内置的概念 代表团。 它支持任意数量的注册BeanDefinitionParser实例,当它需要解析其中的元素时,它会委托给该实例 Namespace。 这种对关注点的干净分离让NamespaceHandler处理编排其命名空间中所有自定义元素的解析,同时委托给BeanDefinitionParsers来完成 XML 解析的繁重工作。 这 意味着每个BeanDefinitionParser仅包含用于解析单个custom 元素的逻辑,正如我们在下一步中看到的那样。spring-doc.cadn.net.cn

10.2.3. 使用BeanDefinitionParser

一个BeanDefinitionParser如果NamespaceHandler遇到 XML 已映射到特定 bean 定义解析器的类型的元素 (dateformat在这种情况下)。换句话说,BeanDefinitionParser是 负责解析架构中定义的一个不同的顶级 XML 元素。在 解析器,我们可以访问 XML 元素(因此也可以访问其子元素),以便 我们可以解析自定义 XML 内容,如以下示例所示:spring-doc.cadn.net.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 提供的AbstractSingleBeanDefinitionParser处理很多 创建单个BeanDefinition.
2 我们提供AbstractSingleBeanDefinitionParsersuperclass 的类型是我们的 单BeanDefinition代表。
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 提供的AbstractSingleBeanDefinitionParser处理很多 创建单个BeanDefinition.
2 我们提供AbstractSingleBeanDefinitionParsersuperclass 的类型是我们的 单BeanDefinition代表。

在这个简单的情况下,这就是我们需要做的。我们的单曲的创作BeanDefinitionAbstractSingleBeanDefinitionParser超类,因为是 bean 定义的唯一标识符的提取和设置。spring-doc.cadn.net.cn

10.2.4. 注册处理程序和模式

编码完成。剩下要做的就是制作 Spring XML 解析基础设施,了解我们的自定义元素。我们通过注册我们的自定义来做到这一点namespaceHandler以及两个特殊用途属性文件中的自定义 XSD 文件。这些 属性文件都放置在META-INF目录和 例如,可以与 JAR 文件中的二进制类一起分发。Spring XML 解析基础结构通过使用 这些特殊属性文件,其格式将在接下来的两节中详细介绍。spring-doc.cadn.net.cn

写作META-INF/spring.handlers

名为spring.handlers包含 XML 模式 URI 到 命名空间处理程序类。对于我们的示例,我们需要编写以下内容:spring-doc.cadn.net.cn

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

(这:character 是 Java 属性格式中的有效分隔符,因此:字符需要使用反斜杠转义。spring-doc.cadn.net.cn

键值对的第一部分(键)是与自定义关联的 URI namespace 扩展名,并且需要与targetNamespace属性,如自定义 XSD 架构中指定的那样。spring-doc.cadn.net.cn

编写“META-INF/spring.schemas”

名为spring.schemas包含 XML 模式位置的映射 (与架构声明一起在使用架构作为一部分的 XML 文件中引用 的xsi:schemaLocation属性)添加到类路径资源。需要此文件 以防止 Spring 绝对必须使用默认值EntityResolver这需要 Internet 访问以检索架构文件。如果在此中指定映射 properties 文件中,Spring 搜索模式(在本例中,myns.xsdorg.springframework.samples.xmlpackage)在类路径上。 以下代码片段显示了我们需要为自定义架构添加的行:spring-doc.cadn.net.cn

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(请记住,:字符必须转义。spring-doc.cadn.net.cn

建议您将 XSD 文件(或多个文件)部署在 这NamespaceHandlerBeanDefinitionParser类路径上的类。spring-doc.cadn.net.cn

10.2.5. 在 Spring XML 配置中使用自定义扩展

使用您自己实现的自定义扩展与使用 Spring 提供的“自定义”扩展之一。以下内容 示例使用自定义<dateformat/>在前面的步骤中开发的元素 在 Spring XML 配置文件中:spring-doc.cadn.net.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 我们的定制豆子。

10.2.6. 更详细的示例

本节介绍一些更详细的自定义 XML 扩展示例。spring-doc.cadn.net.cn

在自定义元素中嵌套自定义元素

本节中介绍的示例显示了如何编写所需的各种工件以满足以下配置的目标:spring-doc.cadn.net.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>

前面的配置将自定义扩展嵌套在彼此之间。类实际上是由<foo:component/>元素是Component类(如下一个示例所示)。请注意Component类不会公开setter 方法components财产。 这使得它变得困难(或者更确切地说是不可能)为Component类。以下列表显示了Component类:spring-doc.cadn.net.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
    }
}

此问题的典型解决方案是创建自定义FactoryBean这会公开一个 setter 属性的components财产。以下列表显示了这样的自定义FactoryBean:spring-doc.cadn.net.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.cadn.net.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>

再次按照前面描述的过程, 然后我们创建一个自定义NamespaceHandler:spring-doc.cadn.net.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())
    }
}

接下来是定制BeanDefinitionParser.请记住,我们正在创造 一个BeanDefinition它描述了一个ComponentFactoryBean.以下内容 列表显示我们的自定义BeanDefinitionParser实现:spring-doc.cadn.net.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 基础设施注册各种工件, 通过修改META-INF/spring.handlersMETA-INF/spring.schemas文件,如下所示:spring-doc.cadn.net.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
“普通”元素上的自定义属性

编写您自己的自定义解析器和相关工件并不难。然而 有时这样做不是正确的。考虑一个需要 将元数据添加到现有的 Bean 定义。在这种情况下,您当然 不想编写自己的整个自定义扩展。相反,你只是 想要向现有 Bean 定义元素添加其他属性。spring-doc.cadn.net.cn

通过另一个示例,假设您为 服务对象(它不知道)访问集群 JCache,并且您希望确保 named JCache 实例在周围集群中急切启动。 以下列表显示了这样的定义:spring-doc.cadn.net.cn

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>

然后我们可以创建另一个BeanDefinition'jcache:cache-name'属性。这BeanDefinition然后初始化 为我们命名的 JCache。我们还可以修改现有的BeanDefinition对于'checkingAccountService'因此它依赖于这个新的 JCache 初始化BeanDefinition.以下列表显示了我们的JCacheInitializer:spring-doc.cadn.net.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...
    }
}

现在我们可以继续使用自定义扩展。首先,我们需要创作 描述自定义属性的 XSD 架构,如下所示:spring-doc.cadn.net.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>

接下来,我们需要创建关联的NamespaceHandler如下:spring-doc.cadn.net.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 属性,我们编写一个BeanDefinitionDecorator而不是BeanDefinitionParser. 以下列表显示了我们的BeanDefinitionDecorator实现:spring-doc.cadn.net.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 基础设施注册各种工件 通过修改META-INF/spring.handlersMETA-INF/spring.schemas文件,如下所示:spring-doc.cadn.net.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

10.3. 应用程序启动步骤

附录的这一部分列出了现有的StartupSteps核心容器被检测。spring-doc.cadn.net.cn

每个启动步骤的名称和详细信息不是公共合同的一部分,并且 可能会发生变化;这被视为核心容器的实现细节,并将紧随其后 它的行为发生了变化。
表 15.核心容器中定义的应用程序启动步骤
名称 描述 标签

spring.beans.instantiatespring-doc.cadn.net.cn

Bean 及其依赖项的实例化。spring-doc.cadn.net.cn

beanName豆子的名字,beanType注射点所需的类型。spring-doc.cadn.net.cn

spring.beans.smart-initializespring-doc.cadn.net.cn

初始化SmartInitializingSingleton豆。spring-doc.cadn.net.cn

beanName豆子的名称。spring-doc.cadn.net.cn

spring.context.annotated-bean-reader.createspring-doc.cadn.net.cn

创建AnnotatedBeanDefinitionReader.spring-doc.cadn.net.cn

spring.context.base-packages.scanspring-doc.cadn.net.cn

扫描基本包。spring-doc.cadn.net.cn

packages用于扫描的基本包数组。spring-doc.cadn.net.cn

spring.context.beans.post-processspring-doc.cadn.net.cn

豆子后处理阶段。spring-doc.cadn.net.cn

spring.context.bean-factory.post-processspring-doc.cadn.net.cn

调用BeanFactoryPostProcessor豆。spring-doc.cadn.net.cn

postProcessor当前后处理器。spring-doc.cadn.net.cn

spring.context.beandef-registry.post-processspring-doc.cadn.net.cn

调用BeanDefinitionRegistryPostProcessor豆。spring-doc.cadn.net.cn

postProcessor当前后处理器。spring-doc.cadn.net.cn

spring.context.component-classes.registerspring-doc.cadn.net.cn

通过注册组件类AnnotationConfigApplicationContext#register.spring-doc.cadn.net.cn

classes用于注册的给定类数组。spring-doc.cadn.net.cn

spring.context.config-classes.enhancespring-doc.cadn.net.cn

使用 CGLIB 代理增强配置类。spring-doc.cadn.net.cn

classCount增强类的计数。spring-doc.cadn.net.cn

spring.context.config-classes.parsespring-doc.cadn.net.cn

配置类解析阶段使用ConfigurationClassPostProcessor.spring-doc.cadn.net.cn

classCount已处理的类计数。spring-doc.cadn.net.cn

spring.context.refreshspring-doc.cadn.net.cn

应用程序上下文刷新阶段。spring-doc.cadn.net.cn