核心

1. IoC 容器

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

1.1. Spring IoC 容器和 bean 简介

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

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

简而言之,BeanFactory提供配置框架和基本 功能,以及ApplicationContext添加更多特定于企业的功能。 这ApplicationContextBeanFactory和 被使用 仅在本章对 Spring 的 IoC 容器的描述中。了解更多 有关使用BeanFactory而不是ApplicationContext,BeanFactory.spring-doc.cadn.net.cn

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

1.2. 容器概述

org.springframework.context.ApplicationContextinterface 表示 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 描述符 XML,位于web.xml文件 通常就足够了(参见 Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一种由 Eclipse 提供支持的开发 环境中),您可以通过单击几下鼠标或 击 键。spring-doc.cadn.net.cn

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

容器魔术
图 1.Spring IoC 容器

1.2.1. 配置元数据

如上图所示, Spring IoC 容器使用一种形式的 配置元数据。此配置元数据表示您作为 application developer,告诉 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)、表示 Struts 等对象Action实例、基础设施对象(如 Hibernate)SessionFactories、JMSQueues等。通常,不配置 fine-grained domain 对象,因为它通常是 的 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 idattribute 是标识各个 bean 定义的字符串。
2 classattribute 定义 bean 的类型,并使用完全限定的 classname 的

idattribute 引用协作对象。的 XML 此示例中未显示对协作对象的引用。有关更多信息,请参阅依赖项spring-doc.cadn.net.cn

1.2.2. 实例化容器

位置路径或路径 提供给ApplicationContextconstructor 是资源字符串,它们让 容器加载来自各种外部资源的配置元数据,例如 作为本地文件系统,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 的Resourceabstraction (如 参考资料 中所述),它提供了一个方便的 从 URI 语法中定义的位置读取 InputStream 的机制。特别Resourcepaths 用于构建应用程序上下文,如 Application Contexts 和 Resource Paths 中所述。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 Object-Relational Mapping 标准上)。这property name元素引用 name,以及ref元素引用另一个 bean 的名称 定义。这种idref元素表示 协作对象。有关配置对象依赖项的详细信息,请参阅依赖项spring-doc.cadn.net.cn

编写基于 XML 的配置元数据

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

你可以使用应用程序上下文构造函数从所有这些 bean 中加载 bean 定义 XML 片段。此构造函数采用多个Resourcelocations 的 LOCATIONS 中,如上一节所示。或者,使用一个或多个 的<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必须在 与执行导入的文件相同的目录或 Classpath 位置,而messageSource.xmlthemeSource.xml必须位于resources位置位于 导入文件的位置。如您所见,前导斜杠将被忽略。然而,鉴于 这些路径是相对的,最好根本不使用斜杠。这 正在导入的文件的内容,包括 Top Level<beans/>元素,必须 根据 Spring Schema 是有效的 XML bean 定义。spring-doc.cadn.net.cn

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

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

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

Groovy Bean 定义 DSL

作为外部化配置元数据的另一个示例,bean 定义还可以 用 Spring 的 Groovy Bean 定义 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 配置命名空间。它还允许导入 XML Bean 定义文件通过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 配置,引导看起来非常相似。它有不同的背景 implementation 类,该类是 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与 Reader 结合使用 delegates — 例如,使用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),允许您声明 通过元数据(例如自动装配 Comments)对特定 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

  • 要在新创建的对象中设置的其他配置设置 — 例如,大小 pool 的限制或要在管理 连接池。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 中,ApplicationContext实现还允许注册现有的 在容器外部创建的对象(由用户创建)。这是通过访问 ApplicationContext 的 BeanFactory 通过getBeanFactory()方法,该方法返回 豆工厂DefaultListableBeanFactory实现。DefaultListableBeanFactory支持通过registerSingleton(..)registerBeanDefinition(..)方法。但是,典型的应用程序仅使用 bean 通过常规 bean 定义元数据定义。spring-doc.cadn.net.cn

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

1.3.1. 命名 Bean

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

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

您无需提供nameid对于一个 bean。如果您未提供nameid显式地,容器为该 bean 生成唯一的名称。然而 如果要按名称引用该 bean,请使用ref元素或 Service Locator 样式查找,您必须提供名称。 不提供名称的动机与使用 inner 有关 beanautowiring collaboratorsspring-doc.cadn.net.cn

Bean 命名约定

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

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

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

在 bean 定义本身中,您可以通过使用 最多 1 个 Name 的组合,该名称由id属性和任意数量的其他 names 在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 的配置元数据可以引用 名称为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配置,则@Beanannotation 可用于提供别名。 看使用@Bean注解了解详情。spring-doc.cadn.net.cn

1.3.2. 实例化 Bean

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

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

  • 通常,在容器 它本身通过反射性地调用其构造函数来直接创建 Bean 等同于 Java 代码,其中new算子。spring-doc.cadn.net.cn

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

嵌套类名

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

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

使用 Constructor 进行实例化

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

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

使用 Static Factory Method 进行实例化

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

以下 Bean 定义指定通过调用 Factory 方法。定义没有指定返回对象的类型(类), 仅包含 Factory 方法的类。在此示例中,createInstance()method 必须是静态方法。下面的示例演示如何指定工厂方法: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()
        fun createInstance() = clientService
    }
}

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

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

类似于通过静态 工厂方法,使用实例工厂方法进行实例化会调用非静态 方法创建一个新的 bean。要使用此功能 机制中,离开class属性为空,并在factory-bean属性 在当前 (或 parent) 容器中指定 bean 的名称,其中包含 要调用以创建对象的 instance 方法。设置 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 容器,它通过实例静态工厂方法创建对象。相比之下,FactoryBean(注意大写)指的是特定于 Spring 的FactoryBeanimplementation 类。
确定 Bean 的运行时类型

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

了解特定 Bean 的实际运行时类型的推荐方法是 一个BeanFactory.getTypecall 指定的 bean 名称。这需要以上所有 cases 中,并返回BeanFactory.getBeancall 为 将返回相同的 bean 名称。spring-doc.cadn.net.cn

1.4. 依赖项

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

1.4.1. 依赖注入

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

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

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

基于构造函数的 DI 是通过容器调用带有 个参数,每个参数表示一个依赖项。调用static工厂方法 使用特定的参数来构造 bean 几乎是等效的,并且此讨论 将参数视为构造函数和staticfactory 方法。这 以下示例显示了一个只能使用 constructor 进行依赖项注入的类 注射: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 定义中定义构造函数参数的顺序是 Order 其中,当 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>
构造函数参数索引

您可以使用indexattribute 显式指定构造函数参数的索引, 如下例所示: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 可以从构造函数中查找参数名称。 如果您不能或不想使用 debug 标志编译代码,则可以使用 @ConstructorProperties JDK 注释显式命名构造函数参数。sample 类将 然后必须查看如下: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 方法完成的 调用 no-argument constructor 或 no-argument 之后的 beanstaticfactory 方法设置为 实例化你的 bean。spring-doc.cadn.net.cn

以下示例显示了一个只能使用 pure setter 注入。此类是传统的 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 的 bean it 的 DI 管理。它还支持基于设置程序的 DI,因为某些依赖项已经 通过 constructor 方法注入。您可以以 一个BeanDefinition,您可以将其与PropertyEditor实例设置为 将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不工作 直接使用这些类(即以编程方式),而是使用 XMLbean定义、带注释的组件(即,带@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 团队通常提倡构造函数注入,因为它允许您实现 application 组件作为不可变对象,并确保所需的依赖项 不是null.此外,构造函数注入的组件总是返回给客户端 (调用)处于完全初始化状态的代码。顺便说一句,大量的 constructor arguments 是一种不良的代码味道,这意味着该类可能有太多 责任,应该重构以更好地解决适当的关注点分离问题。spring-doc.cadn.net.cn

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

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

依赖项解析过程

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

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

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

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

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

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

循环依赖关系

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

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

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

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

您通常可以相信 Spring 会做正确的事情。它检测配置问题, 例如对不存在的 bean 和循环依赖项的引用,在 Container load-time 的Spring 会尽可能晚地设置属性并解析依赖关系,当 Bean 实际上是创建的。这意味着已加载的 Spring 容器 如果存在 创建该对象或其依赖项之一时出现问题,例如,Bean 会抛出 异常。这可能会延迟 一些配置问题的可见性就是原因ApplicationContextimplementations by 默认的 pre-instantiate singleton bean。以一些前期时间和内存为代价 在实际需要这些 bean 之前创建它们,则会发现配置问题 当ApplicationContext是创建的,而不是更晚的。您仍然可以覆盖此默认值 行为,以便单例 bean 惰性初始化,而不是 agerly 预实例化。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。一个小 的一部分指定了一些 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 不是使用构造函数,而是 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.
        fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
            val eb = ExampleBean (...)
            // some other operations...
            return eb
        }
    }
}

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

1.4.2. 依赖项和配置详解

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

Straight 值(Primitives、Strings 等)

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-namespace 以获得更简洁的 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实例PropertyEditor机制。这 是一个不错的快捷方式,并且是 Spring 团队确实喜欢使用 嵌套的<value/>元素覆盖在value属性样式。spring-doc.cadn.net.cn

idref元素

idref元素只是一种防错的方式,用于将id(字符串值 - not 引用)附加到容器中另一个 bean 的<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属性idref元素在 4.0 bean 中不再受支持 XSD 的 ID,因为它没有提供比常规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,则它可能会 已由容器初始化。所有引用最终都是对 另一个对象。范围界定和验证取决于您是指定 other 对象通过beanparent属性。spring-doc.cadn.net.cn

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

<ref bean="someBean"/>

通过parentattribute 创建对 Bean 的引用 即在当前容器的父容器中。的parent属性可以与id目标 Bean 的属性或 值中的name目标 Bean 的属性。目标 Bean 必须位于 当前容器的父容器。您应该主要使用此 bean 引用变体 当您具有容器层次结构并且希望将现有 bean 包装在父 bean 中时 容器,其代理与父 Bean 同名。以下一对 清单 显示了如何使用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属性ref元素在 4.0 bean 中不再受支持 XSD 的 ID,因为它没有提供比常规bean参考。改变 您现有的ref local参考资料ref bean升级到 4.0 架构时。
内部 Bean

一个<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标记 创建,因为内部 bean 始终是匿名的,并且总是使用外部 bean 创建 豆。无法独立访问内部 bean 或将它们注入 将 bean 协作到封闭 bean 中。spring-doc.cadn.net.cn

作为一种极端情况,可以从自定义范围接收销毁回调 — 例如,对于包含在单例 bean 中的请求范围的内部 bean。创作 的 Bean 实例绑定到其包含的 Bean,但销毁回调允许它 参与请求范围的生命周期。这不是常见情况。内豆 通常只是共享其包含的 bean 的 scope。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/>元素继承和 覆盖父集合中的值。也就是说,子集合的值为 将父集合和子集合的元素与子集合的 collection 元素覆盖父集合中指定的值。spring-doc.cadn.net.cn

本节关于合并讨论了父子 Bean 机制。不熟悉的读者 with 父 Bean 和 Child 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集合,其中包含合并子adminEmailscollection 替换为父级的adminEmails收集。以下清单 显示结果:spring-doc.cadn.net.cn

孩子Propertiescollection 的值集继承了 父母<props/>的 API 的supportvalue 覆盖 父集合。spring-doc.cadn.net.cn

此合并行为类似于<list/>,<map/><set/>集合类型。在<list/>元素、语义 与List集合类型(即ordered值的集合)的 Collection。parent的值位于所有child列表的 值。在Map,SetProperties集合类型,无排序 存在。因此,对于作为基础的集合类型,没有排序语义 关联的Map,SetPropertiesimplementation types 中,容器 内部使用。spring-doc.cadn.net.cn

集合合并的限制

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

强类型集合

在 Java 5 中引入泛型类型后,您可以使用强类型集合。 也就是说,可以声明Collection类型,使其只能包含 (例如)String元素。如果您使用 Spring 依赖注入 强类型Collection添加到 bean 中,您可以利用 Spring 的 type-conversion 支持,以便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属性的somethingbean 准备注入,泛型 有关强类型的元素类型的信息Map<String, Float>是 由 Reflection 提供。因此,Spring 的类型转换基础结构识别 各种 value 元素为 类型Float和字符串值 (9.99,2.753.99) 转换为实际的Float类型。spring-doc.cadn.net.cn

Null 和空字符串值

Spring 将 properties 等的空参数视为空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 命名空间允许您使用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 没有 schema 定义,因此您可以设置属性的名称 添加到属性名称。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到 Beanjane,第二个 bean 定义使用p:spouse-ref="jane"作为 属性来执行完全相同的作。在这种情况下,spouse是属性名称, 而-refpart 表示这不是一个直接的值,而是一个 引用另一个 bean。spring-doc.cadn.net.cn

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

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

以下示例使用c:namespace 执行与 from Constructor-based Dependency Injection 相同的作: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:namespace 使用与p:one(尾随的-ref为 Bean 引用)来按名称设置构造函数参数。同样地 它需要在 XML 文件中声明,即使它未在 XSD 架构中定义 (它存在于 Spring 核心中)。spring-doc.cadn.net.cn

对于构造函数参数名称不可用的极少数情况(通常如果 字节码在没有调试信息的情况下编译),您可以使用 fallback 到 ARGUMENT 索引,如下所示: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>元素,但 不常用,因为 plain order of declaration 通常就足够了。

在实践中,构造函数解析机制在匹配方面非常有效 参数,因此,除非你真的需要,否则我们建议使用 name 表示法 在整个配置中。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属性,该属性具有sammyproperty 和最终的sammy属性设置为123.为了 this 要起作用,则fred的属性somethingbob的属性fred莫 是null在 bean 构造之后。否则,一个NullPointerException被抛出。spring-doc.cadn.net.cn

1.4.3. 使用depends-on

如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为 另一个人的财产。通常,您可以使用<ref/>元素在基于 XML 的配置元数据中。但是,有时 豆子就不那么直接了。例如,当类中的 static 初始值设定项需要 triggered,例如用于数据库驱动程序注册。这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属性(逗号、空格和分号有效 delimiters):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属性可以指定初始化时依赖项和 在仅 singleton bean 的情况下,相应的 销毁时间依赖性。定义depends-on关系 在给定的 bean 本身被销毁之前,首先销毁给定的 bean。 因此depends-on还可以控制 shutdown 顺序。

1.4.4. 延迟初始化的 Bean

默认情况下,ApplicationContext作为初始化的一部分,实现急切地创建和配置所有 singleton bean 过程。通常,这种预实例化是可取的,因为 配置或周围环境会立即被发现,而不是几个小时 甚至几天后。当此行为不可取时,您可以阻止 通过将 Bean 定义标记为 lazy-initialized 初始化的。延迟初始化的 bean 告诉 IoC 容器创建一个 bean instance,而不是在启动时。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.Autowiring 具有以下功能 优势:spring-doc.cadn.net.cn

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

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

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

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

nospring-doc.cadn.net.cn

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

byNamespring-doc.cadn.net.cn

按属性名称自动装配。Spring 查找与 需要自动装配的属性。例如,如果将 Bean 定义设置为 autowire 按名称进行,它包含一个master属性(即,它有一个setMaster(..)方法),Spring 会查找名为master和用途 it 来设置属性。spring-doc.cadn.net.cn

byTypespring-doc.cadn.net.cn

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

constructorspring-doc.cadn.net.cn

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

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

自动装配的局限性和缺点

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

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

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

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

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

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

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

从 Autowiring 中排除 Bean

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

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

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

这些技术对于您永远不想注入到其他 bean 中的 bean 非常有用 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")对容器的调用ask for (一个 通常是 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

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

查找方法注入

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

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

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

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

  • 另一个关键限制是 lookup 方法不能与工厂方法一起使用,并且 特别是 Not with@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);

如果 method 为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 作为原型,如果这确实是需要的。如果是 一个单例,则myCommandbean 的 bean 中。spring-doc.cadn.net.cn

或者,在基于注释的组件模型中,您可以声明一个查找 方法通过@Lookupannotation 中,如下例所示: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 与 Lookup 方法的 declared return 类型: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
}

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

访问不同范围的目标 bean 的另一种方法是ObjectFactory/ Provider注射点。请参见将范围限定的 Bean 作为依赖项spring-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/>元素来指示被覆盖的方法的方法签名。签名 仅当方法重载且有多个变体时,才需要参数 存在于类中。为方便起见,参数的类型字符串可以是 完全限定类型名称的 substring 的 substring 中。例如,以下 all matchjava.lang.String:spring-doc.cadn.net.cn

java.lang.String
String
Str

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

1.5. Bean 作用域

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

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

WebSocket 浏览器spring-doc.cadn.net.cn

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

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

1.5.1. 单例范围

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

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

单身 人士

Spring 的单例 bean 概念不同于 Gang of Four (GoF) 模式书。GoF 单例对 Object 中,以便每个 ClassLoader 的Spring 单例的范围最好描述为每个容器 和每 bean 的 bean 中。这意味着,如果您在 单个 Spring 容器,则 Spring 容器将创建一个且仅创建一个实例 由该 Bean 定义定义的类。singleton scope 是默认范围 在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 被注入到另一个 bean 中,或者你通过getBean()方法调用 容器。通常,您应该对所有有状态 bean 使用 prototype 范围,并且 singleton 作用域。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 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 Bean 后处理器,它包含对 需要清理的豆子。spring-doc.cadn.net.cn

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

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

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

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

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

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

初始 Web 配置

为了在request,session,applicationwebsocketlevels(Web 范围的 bean)中,一些次要的初始配置是 在定义 bean 之前是必需的。(此初始设置不是必需的 对于标准范围:singletonprototype.)spring-doc.cadn.net.cn

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

如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在 由 Spring 处理DispatcherServlet,无需特殊设置。DispatcherServletalready 公开所有相关状态。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 application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单 显示了 Web 应用程序的 Filter 部分: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 使用loginActionbean 定义。也就是说,loginActionbean 的范围限定在 HTTP 请求级别。您可以将内部的 state (状态),因为其他实例 从同一loginActionBean 定义在 state 中看不到这些更改。 它们特定于单个请求。当请求完成处理时, 范围限定为请求的 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.在其他 words、userPreferencesbean 的作用域实际上限定在 HTTPSession水平。如 使用请求范围的 bean,您可以更改实例的内部状态,即 根据需要创建尽可能多的 HTTPSession实例也是 使用从同一userPreferencesbean 定义看不到这些 状态更改,因为它们特定于单个 HTTPSession.当 HTTP 协议Session最终被丢弃,则范围限定为该特定 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 使用appPreferencesBean 定义。也就是说,appPreferencesbean 的作用域为ServletContext级别并存储为常规ServletContext属性。这有点类似于 Spring 单例 bean,但 在两个重要方面有所不同:它是ServletContext,而不是每个 SpringApplicationContext(任何给定的 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 {
    // ...
}
作为依赖项的作用域 Bean

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

您也可以使用<aop:scoped-proxy/>在 bean 之间,这些 bean 的范围限定为singleton, 然后,引用通过可序列化的中间代理 因此能够在反序列化时重新获取目标 singleton bean。spring-doc.cadn.net.cn

声明<aop:scoped-proxy/>针对范围prototype、每种方法 调用会导致创建一个新的目标实例,该实例的 然后,呼叫将被转发。spring-doc.cadn.net.cn

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

作为扩展变体,您可以声明ObjectProvider<MyTargetBean>它提供 几种其他访问变体,包括getIfAvailablegetIfUnique.spring-doc.cadn.net.cn

它的 JSR-330 变体称为Provider,并与Provider<MyTargetBean>声明和相应的get()call 进行检索。 有关 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/>元素放入 Scoped Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。 为什么 bean 的定义范围限定在request,session和 custom-scope 级别需要<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 是一个单例:它每 container 及其依赖项(在本例中只有一个userPreferencesbean) 为 也只注射了一次。这意味着userManagerbean 仅在 完全相同userPreferencesobject(即最初注入它的那个)。spring-doc.cadn.net.cn

当将生存期较短的作用域 bean 注入 生存期较长的作用域 Bean(例如,注入 HTTPSession-范围的协作 bean 作为单例 bean 的依赖项)。相反,您需要一个userManager对象,并且对于 HTTP 的生命周期Session,您需要一个userPreferences对象 特定于 HTTPSession.因此,容器会创建一个对象,该对象 公开与UserPreferences类(理想情况下是 对象,它是一个UserPreferences实例),该实例可以获取真实的UserPreferences对象(HTTP 请求、Session,等等 forth)。容器将此代理对象注入到userManagerbean,即 不知道这个UserPreferencesreference 是代理。在此示例中,当UserManager实例在 Dependency-InjectedUserPreferences对象,它实际上是在代理上调用一个方法。然后,代理会获取真实的UserPreferences对象Session并委派 方法调用到检索到的 realUserPreferences对象。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 基于接口的代理意味着您的 application classpath 来影响此类代理。但是,这也意味着 作用域 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

Scopeinterface 有四种方法可以从 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 或当 scope 中的指定对象被销毁时: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

使用自定义范围

编写和测试一个或多个自定义Scopeimplementations,您需要进行 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

假设您编写了自定义Scopeimplementation 的 Implementation,然后按如下所示注册它 在下一个示例中。spring-doc.cadn.net.cn

下一个示例使用SimpleThreadScope,它包含在 Spring 中,但不是 registered (默认)。对于您自己的自定义,说明是相同的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">

使用自定义Scopeimplementation 中,您不仅限于编程注册 的范围。您还可以执行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>声明FactoryBeanimplementation 时,是工厂 Bean 本身被限定为 Scope,而不是对象 返回自getObject().

1.6. 自定义 bean 的性质

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

1.6.1. 生命周期回调

要与容器对 bean 生命周期的 Management 进行交互,您可以实现 SpringInitializingBeanDisposableBean接口。容器调用afterPropertiesSet()对于前者和destroy()对于后者,让 bean 在初始化和销毁 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 框架使用BeanPostProcessorimplementations 来处理任何 callback 接口,它可以找到并调用适当的方法。如果您需要自定义 功能或其他生命周期行为 Spring 默认不提供,您可以 实现BeanPostProcessor你自己。有关更多信息,请参阅容器扩展点spring-doc.cadn.net.cn

除了初始化和销毁回调之外, Spring Management 的对象还可以 此外,实现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。或者,我们建议使用 这@PostConstructannotation 或 指定 POJO 初始化方法。对于基于 XML 的配置元数据, 您可以使用init-method属性来指定具有 void 的方法的名称 no-argument 签名。通过 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

销毁回调

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

void destroy() throws Exception;

我们建议您不要使用DisposableBeancallback 接口,因为它 不必要地将代码耦合到 Spring。或者,我们建议使用 这@PreDestroyannotation 或 指定 Bean 定义支持的泛型方法。使用基于 XML 的 configuration 元数据,您可以使用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>元素为特殊(inferred)值,它指示 Spring 自动检测closeshutdown方法。(任何实现java.lang.AutoCloseablejava.io.Closeable因此会匹配。您还可以设置 这个特别的(inferred)default-destroy-method属性<beans>元素将此行为应用于整个 bean 集(请参见默认初始化和销毁方法)。请注意,这是 default behavior 与 Java 配置一起使用。
默认 Initialization 和 Destroy 方法

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

你可以将 Spring 容器配置为“查找”命名初始化和销毁 回调方法名称。这意味着您作为应用程序 开发人员可以编写您的应用程序类并使用名为init(),而无需配置init-method="init"属性 定义。Spring IoC 容器在创建 Bean 时调用该方法(在 根据前面描述的标准生命周期回调协定)。此功能还对 initialization 和 destroy 方法回调。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在 Bean 上 class 作为初始化方法回调。在创建和组装 bean 时,如果 Bean 类具有这样一个方法,则会在适当的时间调用它。spring-doc.cadn.net.cn

您可以类似地(即在 XML 中)通过使用default-destroy-method属性<beans/>元素。spring-doc.cadn.net.cn

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

Spring 容器保证调用配置的初始化回调 紧接着为 bean 提供所有依赖项。因此,初始化 callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等 forth 尚未应用于 Bean。首先完全创建目标 Bean,然后 然后应用 AOP 代理(例如)及其拦截器链。如果目标 bean 和 proxy 是单独定义的,你的代码甚至可以与原始的 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

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 Spring 管理的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收开始和停止信号(例如,对于 STOP/RESTART 场景),它会将这些调用级联到所有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.Lifecycleinterface 是普通的 显式启动和停止通知的协定,并不意味着在上下文中自动启动 刷新时间。为了对特定 bean 的自动启动(包括启动阶段)进行细粒度控制, 考虑实施org.springframework.context.SmartLifecycle相反。spring-doc.cadn.net.cn

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

启动和关闭调用的顺序可能很重要。如果 “depends-on” 关系存在于任意两个对象之间,则依赖端在其 依赖项,并且它在依赖项之前停止。然而,有时,直接的 依赖项是未知的。您可能只知道特定类型的对象应该启动 在其他类型的对象之前。在这些情况下,SmartLifecycleinterface 定义 另一个选项,即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);
}

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

SmartLifecycle接受回调。任何 实现必须调用该回调的run()方法,在该实现的 shutdown 过程完成。这将在必要时启用异步关闭,因为 默认实现的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>

如前所述,LifecycleProcessorinterface 定义 刷新和关闭上下文。后者驱动关闭 process as ifstop()被显式调用,但当上下文为 关闭。另一方面,'refresh' 回调启用了SmartLifecycle豆。刷新上下文时(在所有对象都已 instantiated 和 initialized),则会调用该回调。此时, 默认生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法。如果true,则该对象为 启动,而不是等待显式调用上下文的 OR 自己start()方法(与上下文刷新不同,上下文启动不会发生 automatically 用于标准上下文实现)。这phasevalue 和任何 “depends-on” 关系确定 Startup Sequence,如前所述。spring-doc.cadn.net.cn

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

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

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

要注册 shutdown 钩子,请调用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.ApplicationContextAwareinterface 中,实例会提供 并引用ApplicationContext.下面的清单显示了定义 的ApplicationContextAware接口:spring-doc.cadn.net.cn

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

自动装配是获取对ApplicationContext.传统的 constructorbyType自动装配模式 (如 Autowiring Collaborators 中所述)可以提供ApplicationContext对于 constructor 参数或 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-method 的spring-doc.cadn.net.cn

1.6.3. 其他Aware接口

此外ApplicationContextAwareBeanNameAware前面讨论过)、 Spring 提供广泛的Aware让 bean 向容器指示的回调接口 它们需要一定的基础设施依赖性。作为一般规则,该名称表示 dependency 类型。下表总结了最重要的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

BeanFactoryspring-doc.cadn.net.cn

BeanNameAwarespring-doc.cadn.net.cn

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

ApplicationContextAwareBeanNameAwarespring-doc.cadn.net.cn

BootstrapContextAwarespring-doc.cadn.net.cn

资源适配器BootstrapContext容器运行。通常仅在 JCA 感知ApplicationContext实例。spring-doc.cadn.net.cn

JCA CCIspring-doc.cadn.net.cn

LoadTimeWeaverAwarespring-doc.cadn.net.cn

定义了 weaver,用于在加载时处理类定义。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

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

资源spring-doc.cadn.net.cn

ServletConfigAwarespring-doc.cadn.net.cn

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

Spring MVCspring-doc.cadn.net.cn

ServletContextAwarespring-doc.cadn.net.cn

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

Spring MVCspring-doc.cadn.net.cn

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

1.7. Bean 定义继承

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

如果您使用ApplicationContext以编程方式接口,子 Bean 定义由ChildBeanDefinition类。大多数用户不工作 与他们在这个层面上。相反,它们在类中以声明方式配置 bean 定义 例如ClassPathXmlApplicationContext.使用基于 XML 的配置时 元数据,您可以使用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 定义使用父定义中的 Bean 类(如果没有 指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须是 与 Parent 兼容(即,它必须接受 Parent的 Property 值)。spring-doc.cadn.net.cn

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

其余设置始终取自子定义:depends on, autowire 模式、依赖关系检查、Singleton 和 Lazy init。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()call 替换为 父 Bean ID 返回错误。同样,容器的内部preInstantiateSingletons()method 忽略定义为 抽象。spring-doc.cadn.net.cn

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

1.8. 容器扩展点

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

1.8.1. 使用BeanPostProcessor

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

您可以配置多个BeanPostProcessor实例,您可以控制 Order 其中这些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 的 Blueprint),请执行以下作: 您需要改用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor.spring-doc.cadn.net.cn

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

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

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

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

实现BeanPostProcessor接口是特殊的,并经过处理 容器不同。都BeanPostProcessor实例和 bean 中,它们 直接引用在启动时实例化,作为特殊启动阶段的一部分 的ApplicationContext.接下来,所有BeanPostProcessor实例已注册 以排序的方式,并应用于容器中的所有其他 bean。因为 AOP auto-proxying 实现为BeanPostProcessor本身,也不会BeanPostProcessor实例和它们直接引用的 bean 都不符合自动代理的条件,并且 因此,不要将 aspects 编织到其中。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通过使用 Autowiring 或@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()方法作为 它由容器创建,并将生成的字符串打印到系统控制台。spring-doc.cadn.net.cn

以下清单显示了自定义BeanPostProcessorimplementation 类定义: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,所以它可以像任何 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

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

1.8.2. 使用BeanFactoryPostProcessor

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

您可以配置多个BeanFactoryPostProcessor实例,您可以在 这些BeanFactoryPostProcessor实例运行,方法是将order财产。 但是,只有在BeanFactoryPostProcessor实现Ordered接口。如果您编写自己的BeanFactoryPostProcessor,您应该 考虑实施Ordered界面也是如此。请参阅BeanFactoryPostProcessorOrderedinterfaces 了解更多详情。spring-doc.cadn.net.cn

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

BeanFactoryPostProcessor实例的范围是按容器划分的。这仅相关 如果您使用容器层次结构。如果您定义了BeanFactoryPostProcessor合二为一 container,则它仅适用于该容器中的 bean 定义。Bean 定义 不由BeanFactoryPostProcessor实例 容器,即使两个容器属于同一层次结构。spring-doc.cadn.net.cn

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

ApplicationContext自动检测部署到其中的任何 bean 实现BeanFactoryPostProcessor接口。它将这些 bean 用作 bean factory 后处理器。您可以将这些后处理器 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 placeholder values 定义: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应用于元数据,该元数据将替换某些 属性。要替换的值指定为 形式${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' 和 这同样适用于与 Properties 文件中的键匹配的其他占位符值。 这PropertySourcesPlaceholderConfigurer检查大多数属性中的占位符,以及 bean 定义的属性。此外,您还可以自定义占位符前缀和后缀。spring-doc.cadn.net.cn

使用contextnamespace 中引入的,你可以配置属性占位符 替换为专用的配置元素。您可以将一个或多个位置作为 逗号分隔的列表location属性,如下例所示:spring-doc.cadn.net.cn

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

PropertySourcesPlaceholderConfigurer不仅在Properties文件。默认情况下,如果在指定的属性文件中找不到属性,则 它检查 SpringEnvironmentproperties 和常规 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()phase 的ApplicationContext对于非 lazy-init bean。spring-doc.cadn.net.cn

示例:PropertyOverrideConfigurer

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

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

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

beanName.property=value

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

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

此示例文件可以与容器定义一起使用,该容器定义包含名为dataSourcedriverurl性能。spring-doc.cadn.net.cn

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

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

使用contextnamespace 中引入的,则可以配置 属性替换为专用的 configuration 元素,如下例所示: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

FactoryBeaninterface 是 Spring IoC 容器的 实例化逻辑。如果你有复杂的初始化代码,最好用 与 Java 相比,您可以创建自己的 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

FactoryBeanconcept 和 interface 在 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 更好?

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

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

XML 设置的替代方案由基于 annotation 的配置提供,它依赖于 用于连接组件的字节码元数据,而不是尖括号声明。 开发人员没有使用 XML 来描述 Bean 连接,而是移动了配置 到组件类本身中,方法是在相关的类、方法或 field 声明。如示例:AutowiredAnnotationBeanPostProcessor用 一个BeanPostProcessor与 annotation 结合使用是扩展 Spring IoC 容器。例如,Spring 2.0 引入了强制执行 required 属性替换为@Required注解。Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖项成为可能 注射。从本质上讲,@Autowiredannotation 提供与 在 Autowiring Collaborators 中描述,但具有更精细的控制和更广泛的 适用性。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 中包含以下标记来隐式注册 配置(请注意,其中包含了contextnamespace) 的 URL: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/>仅在相同的 bean 上查找注释 定义它的应用程序上下文。这意味着,如果您将<context:annotation-config/>WebApplicationContext对于DispatcherServlet, 它只检查@Autowiredbean 在你的控制器中,而不是你的服务中。有关更多信息,请参阅 DispatcherServletspring-doc.cadn.net.cn

1.9.1. @Required

@Required注释适用于 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 {

    @Required
    lateinit var movieFinder: MovieFinder

    // ...
}

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

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

@Requiredannotation 和RequiredAnnotationBeanPostProcessor正式 从 Spring Framework 5.1 开始弃用,取而代之的是 使用 constructor injection for 必需设置(或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此类构造函数上的 annotation 不再是 如果目标 Bean 一开始只定义了一个构造函数,则为 necessary。但是,如果 有几个构造函数可用,至少没有主/默认构造函数 其中一个构造函数必须用@Autowired为了指示 container 使用哪一个。有关详细信息,请参阅有关构造函数解析的讨论。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 {

    @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-注释 注射点。否则,注入可能会因运行时的 “no type match found” 错误而失败。spring-doc.cadn.net.cn

对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器 通常预先知道混凝土类型。但是,对于@BeanFactory 方法,您需要 以确保 Declared 的 return 类型具有足够的表现力。对于组件 实现多个接口,或者对于可能由其 implementation 类型,请考虑在 Factory 上声明最具体的返回类型 方法(至少与引用 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或标准@Priorityannotation (如果您希望数组或列表中的项目) 以按特定顺序排序。否则,他们的顺序将遵循注册 容器中相应目标 bean 定义的顺序。spring-doc.cadn.net.cn

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

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

甚至打字Map只要预期的密钥类型为String. map 值包含预期类型的所有 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 没有匹配的候选 bean 可用时,自动装配将失败 注射点。对于声明的数组、集合或 map,至少有一个 匹配元素。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 的构造函数,其含义略有不同 Resolution 算法,该算法可能会处理多个构造函数。构造 函数 和工厂方法参数在默认情况下实际上是必需的,但有一些特殊的 单构造函数方案中的规则,例如多元素注入点(数组、 collections, map) 解析为空实例。这 允许使用通用的实现模式,其中所有依赖项都可以在 unique 多参数构造函数 — 例如,声明为单个公共构造函数 没有@Autowired注解。spring-doc.cadn.net.cn

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

required属性@Autowired推荐使用,而不是已弃用@Requiredsetter 方法上的注释。设置required属性设置为false表示 该属性对于自动装配不是必需的,如果该属性 不能自动装配。@Required,则更强大,因为它强制执行 属性,如果未定义值,则 引发相应的异常。spring-doc.cadn.net.cn

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

public class SimpleMovieLister {

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

从 Spring Framework 5.0 开始,您还可以使用@Nullable注释(任何类型的 在任何 package 中 — 例如,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是 自动解析,无需特殊设置。以下示例 autowires 一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. 微调基于 Annotation 的自动装配@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 { ... }

    // ...
}

在上述配置中,以下MovieRecommenderfirstMovieCatalog: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. 使用限定符微调基于 Annotation 的自动装配

@Primary是在多个实例中使用 autowiring by type 的有效方法,当一个实例 可以确定主要候选人。当您需要对选择过程进行更多控制时, 您可以使用 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

    // ...
}

您还可以指定@Qualifierannotation 在单个构造函数参数上或 method 参数,如以下示例所示: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 具有mainqualifier 值与 constructor 参数连接,该 使用相同的值进行限定。
2 具有actionqualifier 值与 constructor 参数连接,该 使用相同的值进行限定。

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

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

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

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

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

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

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

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

您可以创建自己的自定义限定符注释。为此,请定义一个 annotation 和 提供@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 中,您可以看到基于 Comments 的替代方法 在 XML 中提供限定符元数据。具体而言,请参阅提供带有注释的限定符元数据spring-doc.cadn.net.cn

在某些情况下,使用没有值的 Comments 可能就足够了。这可以是 当注释用于更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有 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 此元素指定限定符。

您还可以定义自定义限定符注释,这些注释接受 加法 or 代替简单的value属性。如果多个属性值为 然后在要自动装配的字段或参数上指定,则 Bean 定义必须匹配 所有此类属性值都被视为 Autowire 候选项。例如, 请考虑以下注释定义: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
}

要自动装配的字段使用 custom 限定符进行批注,并包含值 对于这两个属性: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>),您可以@AutowireStoreinterface 的 API 和 generic 是 用作限定符,如下例所示: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通过以下方式确定 AutoWire 候选项: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 Management 的对象使用这种模式。spring-doc.cadn.net.cn

@Resource接受 name 属性。默认情况下,Spring 将该值解释为 要注入的 bean 名称。换句话说,它遵循 by-name 语义, 如以下示例所示: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 属性 name。以下示例将具有 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 {

    @Resource
    private lateinit var movieFinder: MovieFinder

}
随 Comments 提供的名称由ApplicationContext其中,CommonAnnotationBeanPostProcessor是有意识的。 如果您配置 Spring 的SimpleJndiBeanFactory明确地。但是,我们建议您依赖默认行为和 使用 Spring 的 JNDI 查找功能来保持间接级别。

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

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

Java
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}
1 contextfield 根据已知的 Resolvable 依赖项类型注入:ApplicationContext.
Kotlin
class MovieRecommender {

    @Resource
    private lateinit var customerPreferenceDao: CustomerPreferenceDao


    @Resource
    private lateinit var context: ApplicationContext (1)

    // ...
}
1 contextfield 根据已知的 Resolvable 依赖项类型注入: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

在这种情况下,catalogparameter 和 field 将等于MovieCatalog价值。spring-doc.cadn.net.cn

Spring 提供了默认的宽松嵌入值解析器。它将尝试解析 属性值,如果无法解析,则为属性名称(例如${catalog.name}) 将作为值注入。如果您想对不存在的 值,您应该声明一个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 时,@Beanmethod 必须为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)

弹簧BeanPostProcessor使用ConversionService在幕后处理 转换String@Value添加到 Target 类型。如果您想 提供自家自定义类型的转换支持,可自家提供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表达该值将为 dynamic 在运行时计算,如下例所示: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 中引入,对这些 annotations 提供了初始化回调销毁回调中描述的生命周期回调机制的替代方案。前提是CommonAnnotationBeanPostProcessor在 Spring 中注册ApplicationContext, 带有这些 Comments 之一的方法在生命周期的同一点被调用 作为相应的 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注释类型是其中的一部分 的标准 Java 库从 JDK 6 到 8。然而,整个javax.annotation软件包与 JDK 9 中的核心 Java 模块分离,并最终在 JDK 11 的。如果需要,可以使用javax.annotation-apiartifact 需要通过 Maven 获取 Central 现在,只需像任何其他库一样添加到应用程序的 Classpath 中即可。spring-doc.cadn.net.cn

1.10. Classpath 扫描和托管组件

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

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

1.10.1.@Component和进一步的 Stereotype Annotations

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

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

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

Spring 提供的许多 Comments 都可以用作 自己的代码。元注释是可以应用于另一个注释的注释。 例如,@Service前面提到的 annotation 是元注释的@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-annotations 允许自定义。当您 希望仅公开 meta-annotation 属性的子集。例如, 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 Programming Model wiki 页面。spring-doc.cadn.net.cn

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

Spring 可以自动检测构造型 class 并注册相应的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>.

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

在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 但是,请确保您的组件类已导出到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的 API 中,或者使用自定义注释(本身使用@Component是 唯一检测到的候选组件。但是,您可以修改和扩展此行为 通过应用自定义筛选器。将它们添加为includeFiltersexcludeFilters的属性 这@ComponentScan注解(或 AS<context:include-filter /><context:exclude-filter /><context:component-scan>元素 XML 配置)。每个滤芯都需要typeexpression属性。 下表描述了筛选选项:spring-doc.cadn.net.cn

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

annotation (默认)spring-doc.cadn.net.cn

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

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

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

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

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

AspectJspring-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附注 并使用 “stub” 存储库: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/>元素。这实际上禁用了类的自动检测 annotated 或 meta-annotated with@Component,@Repository,@Service,@Controller,@RestController@Configuration.

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

Spring 组件还可以向容器提供 bean 定义元数据。你可以做 this 具有相同的@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 定义,该定义具有 factory method 引用 methodpublicInstance().这@Beanannotation 标识 Factory 方法和其他 Bean 定义属性,例如通过 这@Qualifier注解。可以指定的其他方法级 Comments 包括@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)
}

该示例自动装配Stringmethod 参数country设置为age属性privateInstance.Spring Expression Language 元素 通过表示法定义属性的值#{ <expression> }.为@Valueannotations,则表达式解析器会预先配置为在 解析表达式文本。spring-doc.cadn.net.cn

从 Spring Framework 4.3 开始,您还可以声明InjectionPoint(或其更具体的子类:DependencyDescriptor) 更改为 访问触发当前 Bean 创建的请求注入点。 请注意,这仅适用于 bean 实例的实际创建,而不适用于 注入现有实例。因此,此功能最适合 prototype 范围的 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方法 在@Configuration类 创建对协作对象的 Bean 元数据引用。 这些方法不是用普通的 Java 语义调用的,而是通过 container 来提供 Spring 的通常生命周期管理和代理 bean,即使通过对@Bean方法。 相反,在@Bean方法中的普通@Component类具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他 约束适用。spring-doc.cadn.net.cn

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

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

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

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

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

1.10.6. 命名自动检测到的分量

当组件在扫描过程中被自动检测时,其 Bean 名称为 由BeanNameGenerator该扫描仪已知的策略。默认情况下,任何 Spring 构造型注释 (@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 命名策略,则可以提供自定义的 bean 命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的 no-arg 构造函数。然后,提供完整的 限定的类名,如以下示例注释 和 bean 定义显示。spring-doc.cadn.net.cn

如果由于多个自动检测到的组件具有 相同的非限定类名(即,具有相同名称但驻留在 不同的软件包),则可能需要配置BeanNameGenerator默认为 生成的 Bean 名称的完全限定类名。从 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>

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

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

与一般的 Spring 管理组件一样,默认和最常见的 autodetected components 为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 类上被内省(对于带注解的 组件)或工厂方法(对于@Bean方法)。与 XML Bean 相比 定义,没有 bean 定义继承的概念,而继承 类级别的层次结构与元数据目的无关。

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

要为范围解析提供自定义策略,而不是依赖 基于注解的方法,您可以实现ScopeMetadataResolver接口。请务必包含默认的 no-arg 构造函数。然后,您可以提供 完全限定的类名,如以下示例所示 注释和 bean 定义显示:
Java
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

当使用某些非单例作用域时,可能需要为 scoped 对象。原因在 Scoped Bean as Dependencies 中进行了描述。 为此,component-scan 上提供了 scoped-proxy 属性 元素。三个可能的值是: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. 提供带有注解的限定符元数据

@Qualifierannotation 在 Fine-tuning Annotation-based Autowiring with Qualifiers中讨论。 该部分中的示例演示了@Qualifierannotation 和 自定义限定符注释,用于在解析 autowire 时提供精细控制 候选人。因为这些示例是基于 XML Bean 定义的,所以限定符 元数据是使用qualifiermetabean元素。当依赖 Classpath 扫描 自动检测组件,则可以提供类型级 Candidate 类的注释。以下三个示例演示了这一点 技术: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 {
    // ...
}
与大多数基于 Comments 的替代方案一样,请记住,Comments 元数据是 绑定到类定义本身,而 XML 的使用允许多个 bean 的 Bean Package,以便在其限定符元数据中提供变体,因为 元数据是按实例而不是按类提供的。

1.10.9. 生成候选组件的索引

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

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

要生成索引,请向包含 作为组件 Scan 指令目标的组件。以下示例显示了 如何使用 Maven 执行此作:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

1.11. 使用 JSR 330 标准注解

从 Spring 3.0 开始, Spring 提供对 JSR-330 标准注释的支持 (依赖关系注入)。这些 Comments 的扫描方式与 Spring 相同 附注。要使用它们,您需要在 Classpath 中包含相关的 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在字段级别、方法级别 和 constructor-argument 级别。此外,您可以将注入点声明为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: MovieFinder


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

如果您想为应该注入的依赖项使用限定名称, 您应该使用@Namedannotation 中,如下例所示: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-250ManagedBean注释是不可组合的。您应该使用 Spring 的 stereotype 模型来构建 自定义组件注释。

1.11.3. JSR-330 标准注解的限制

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

表 6.Spring 组件模型元素与 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 注释。 不过,这个 API 仅用于创建您自己的 Comments。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()method name 的它也可以与 Spring的@Autowired或者使用未注释的构造函数和 setter 方法。spring-doc.cadn.net.cn

1.12. 基于 Java 的容器配置

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

1.12.1. 基本概念:@Bean@Configuration

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

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

使用@Configuration表示其主要用途是作为 bean 定义的来源。此外@Configuration类 let inter-bean dependencies 通过调用其他@Bean方法。 尽可能简单@Configuration类读取如下:spring-doc.cadn.net.cn

Java
@Configuration
public class AppConfig {

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

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

前面的AppConfigclass 等价于下面的 Spring<beans/>XML:spring-doc.cadn.net.cn

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration vs “lite” @Bean模式?

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

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

在常见场景中,@Bean方法将在@Configuration类 确保始终使用 “full” 模式,因此跨方法引用 重定向到容器的生命周期管理。这可以防止相同的@Bean方法,这有助于 减少在 “Lite” 模式下运行时难以追踪的细微错误。spring-doc.cadn.net.cn

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

1.12.2. 使用 实例化 Spring 容器AnnotationConfigApplicationContext

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

什么时候@Configuration类作为输入提供,@Configuration类本身 注册为 Bean 定义,并且所有声明@Bean类中 methods 也注册为 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 注释的类 作为构造函数的 input,如下例所示: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通过使用 no-arg 构造函数 ,然后使用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()
}
启用 Component Scanningscan(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:namespace 中,如以下示例所示:spring-doc.cadn.net.cn

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

在前面的示例中,com.acme扫描包以查找任何@Component-annotated 类,并且这些类被注册为 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.acmepackage (或任何 package 在下面),它会在调用scan().后refresh(),则其所有@Bean方法在容器中被处理并注册为 Bean 定义。
支持 Web 应用程序AnnotationConfigWebApplicationContext

一个WebApplicationContext的变体AnnotationConfigApplicationContext可用 跟AnnotationConfigWebApplicationContext.在以下情况下,可以使用此实现 配置 SpringContextLoaderListenerservlet 侦听器, Spring MVCDispatcherServlet等。以下内容web.xmlsnippet 配置一个典型的 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>

1.12.3. 使用@Bean注解

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

您可以使用@Bean注解@Configuration-annotated 或@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具有接口(或基类)的方法 return 类型,如下例所示: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) 仅在实例化受影响的 singleton bean 后,容器才知道。 非惰性单例 bean 根据它们的声明顺序进行实例化, 因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间 尝试通过未声明的类型(例如@Autowired TransferServiceImpl, 它仅在transferServicebean 已被实例化)。spring-doc.cadn.net.cn

如果您始终通过声明的服务接口引用您的类型,则您的@Bean返回类型可以安全地加入该设计决策。但是,对于组件 实现多个接口,或者对于可能由其 implementation 类型,则声明最具体的返回类型会更安全 (至少与引用 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

接收生命周期回调

任何使用@Beanannotation 支持常规生命周期回调 ,并且可以使用@PostConstruct@PreDestroy来自 JSR-250 的注释。有关详细信息,请参阅 JSR-250 注释 详。spring-doc.cadn.net.cn

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

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

@Beanannotation 支持指定任意初始化和析构 回调方法,很像 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 具有公共closeshutdown方法会自动使用销毁回调登记。如果您有公共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帮助程序或直接 JNDIInitialContextusage 一起使用,但不是JndiObjectFactoryBean变体(这将强制 U 将返回类型声明为FactoryBeantype 而不是实际目标 type,使其更难用于其他@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 Scopes 部分中指定的任何标准范围。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/>元素。 在 Java 中使用@ScopeAnnotation 提供等效支持 使用proxyMode属性。默认值为ScopedProxyMode.DEFAULT哪 通常表示不应创建作用域代理,除非使用不同的默认值 已在 component-scan 指令级别进行配置。您可以指定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 命名

默认情况下,配置类使用@Beanmethod's name 作为 结果 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中,您可以使用@Descriptionannotation 中,如下例所示: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类。
查找方法注入

如前所述,查找方法注入是一个 您应该很少使用的高级功能。它在 singleton 范围的 bean 依赖于原型范围的 bean。使用 Java 实现此目的 type 配置为实现此模式提供了一种自然的方法。这 以下示例显示了如何使用 Lookup 方法注入: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 的配置如何在内部工作的更多信息

请考虑以下示例,该示例显示了@Bean带注释的方法被调用两次: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并返回它,您将 通常期望有两个实例(每个服务一个)。那肯定是 有问题的:在 Spring 中,实例化的 bean 有一个singleton范围。这是 魔力所在:全部@Configuration类在启动时被子类化 跟CGLIB.在子类中,子方法首先检查容器是否有任何 在调用 Parent 方法并创建新实例之前缓存 (作用域) bean。spring-doc.cadn.net.cn

根据 Bean 的范围,行为可能会有所不同。我们正在交谈 关于单例 这里.

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

由于 CGLIB 在 startup-time 的 Startup-time 中。特别是,配置类不能是 final。但是,由于 在 4.3 中,允许在配置类上使用任何构造函数,包括使用@Autowired或用于 default 注入的单个非 default 构造函数声明。spring-doc.cadn.net.cn

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

1.12.5. 编写基于 Java 的配置

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

使用@Import注解

就像<import/>元素在 Spring XML 文件中用于帮助模块化 配置、@Importannotation 允许加载@Bean定义来自 另一个 Configuration 类,如下例所示: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>()
}

这种方法简化了容器实例化,因为只需要处理一个类 替换为 Bean 的 Git,而不是要求您记住可能大量的@Configuration类。spring-doc.cadn.net.cn

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

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

幸运的是,解决这个问题很简单。正如我们已经讨论过的, 一个@Beanmethod 可以具有任意数量的描述 bean 的参数 依赖。请考虑以下更真实的场景,其中包含多个@Configuration类,每个类都依赖于其他类中声明的 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@Valueinjection 和其他功能与任何其他 bean 相同。spring-doc.cadn.net.cn

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

此外,要特别小心BeanPostProcessorBeanFactoryPostProcessor定义 通过@Bean.这些通常应声明为static @Bean方法,而不会触发 实例化其包含的配置类。否则@Autowired@Value不得 在 Configuration 类本身上工作,因为可以将其创建为 Bean 实例,早于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 被声明了吗?事实并非如此 explicit 的 intent 函数,这可能就好了。请记住,Spring Tools for Eclipse 提供了以下工具 可以渲染显示所有内容是如何连接的图表,这可能就是您所需要的。也 您的 Java IDE 可以轻松找到AccountRepository类型 并快速显示@Bean返回该类型的方法。spring-doc.cadn.net.cn

如果这种歧义是不可接受的,并且您希望进行直接导航 从 IDE 中从一个@Configuration类添加到另一个类中,请考虑自动将 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 之前创建,超出了后者的直接依赖关系所暗示的范围)。
有条件地包含@ConfigurationClasses 或@Bean方法

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

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

Conditioninterface 提供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的@ConfigurationClass Support 的目标不是成为 100% 完全的替代品 用于 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是 配置容器。在 XML 方便或必要的情况下,您有一个 choice:以“以 XML 为中心”的方式实例化容器,例如,ClassPathXmlApplicationContext,或者使用AnnotationConfigApplicationContext@ImportResource用于导入 XML 的注释 根据需要。spring-doc.cadn.net.cn

以 XML 为中心的使用@Configuration

最好从 XML 引导 Spring 容器并包含@Configuration类。例如,在大型现有代码库中 使用 Spring XML,则更容易创建@Configuration类 根据需要,并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍 使用选项@Configuration类。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-注释 类会自动成为组件扫描的候选对象。使用与 describe 中,我们可以重新定义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 分配给配置文件 无论是在 XML 中定义还是使用注释定义。的作用Environmentobject 替换为 relation to profiles 用于确定当前处于活动状态的用户档案(如果有), 以及默认情况下应处于活动状态的配置文件(如果有)。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}解析 添加到正确的配置文件路径,具体取决于环境的值 变量。Bean 定义配置文件是一个核心容器功能,它提供了一个 解决这个问题。spring-doc.cadn.net.cn

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

@Profile

@Profileannotation 允许您指示组件符合注册条件 当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们 可以重写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方法,您通常选择使用 Programmatic JNDI 查找,通过使用 Spring 的JndiTemplate/JndiLocatorDelegatehelpers 或 直 JNDIInitialContext用法,但未显示JndiObjectFactoryBeanvariant 的 Variant 中,这将强制您将返回类型声明为FactoryBean类型。

配置文件字符串可以包含一个简单的配置文件名称(例如production) 或 profile 表达式。配置文件表达式允许更复杂的配置文件逻辑 表示的(例如,production & us-east).支持以下运算符 配置文件表达式: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.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果@Configuration类标有@Profile、所有@Beanmethods 和@Import与该类关联的注释将被绕过,除非一个或多个 指定的配置文件处于活动状态。如果@Component@Configuration类被标记 跟@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方法(类似于构造函数 overloading)、一个@Profilecondition 需要对所有 重载方法。如果条件不一致,则仅 重载方法中的第一个声明很重要。因此@Profile能 不用于选择具有特定参数签名的重载方法 另一个。同一 bean 的所有工厂方法之间的解析遵循 Spring 的 构造函数解析算法。spring-doc.cadn.net.cn

如果要定义具有不同性能分析条件的替代 bean, 通过使用@Bean名字 属性,如前面的示例所示。如果参数签名全部为 相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的 首先在有效的 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>

也可以避免这种 split 和 nest<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 哪个 配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到 一个NoSuchBeanDefinitionExceptionthrown,因为容器找不到 名为dataSource.spring-doc.cadn.net.cn

可以通过多种方式激活配置文件,但最直接的是 它以编程方式针对EnvironmentAPI 的 API 可通过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,甚至作为 条目(参见PropertySource抽象化).在集成测试中,active 配置文件可以使用@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"
默认配置文件

default 配置文件表示默认启用的配置文件。考虑一下 以下示例: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 提供默认定义的一种方式。如果有 profile 时,默认配置文件不适用。spring-doc.cadn.net.cn

您可以使用setDefaultProfiles()上 这Environment或者,以声明方式使用spring.profiles.default财产。spring-doc.cadn.net.cn

1.13.2.PropertySource抽象化

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

这些默认属性源适用于StandardEnvironment,用于独立 应用。StandardServletEnvironment填充了其他默认属性源,包括 Servlet Config 和 Servlet context 参数。它可以选择启用JndiPropertySource. 有关详细信息,请参阅 javadoc。

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

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

对于常见的StandardServletEnvironment,则完整的层次结构如下所示,其中 topest-precedence 条目: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-propertyproperty,则会检测并返回该属性,以支持 任何my-propertyproperty 在任何其他PropertySource.这MutablePropertySourcesAPI 公开了许多方法,这些方法允许精确作 property 源。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")!!
    }
}

任何${…​}placeholder 存在于@PropertySourceresource location 为 针对已针对 environment,如下例所示: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已存在于其中一个属性源中 registered (例如,系统属性或环境变量),则占位符为 resolved 的值。如果不是,则default/path已使用 作为默认值。如果未指定 default 且无法解析属性,则IllegalArgumentException被抛出。spring-doc.cadn.net.cn

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

1.13.4. 语句中的占位符解析

从历史上看,元素中占位符的值只能针对 JVM 系统属性或环境变量。现在情况已不再如此。因为 这Environmentabstraction 集成在整个容器中,因此很容易 通过它的 route resolution of placeholders 进行路由解析。这意味着您可以配置 解决过程。您可以更改搜索的优先级 系统属性和环境变量,或者完全删除它们。您还可以添加 自己的属性源添加到组合中。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

LoadTimeWeaver被 Spring 用于按原样动态转换类 加载到 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、该ApplicationContext可以实施LoadTimeWeaverAware,从而获得对加载时间的引用 Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是 对于 JPA 类转换是必需的。 查阅LocalContainerEntityManagerFactoryBeanjavadoc 了解更多详情。有关 AspectJ 加载时编织的更多信息,请参见 Spring 框架中的使用 AspectJ 进行加载时编织spring-doc.cadn.net.cn

1.15. 其他功能ApplicationContext

本章介绍中所述,org.springframework.beans.factorypackage 提供了用于管理和作 bean 的基本功能,包括在 编程方式。这org.springframework.context软件包会添加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 effects 消息提供了基础 分辨率。在这些接口上定义的方法包括:spring-doc.cadn.net.cn

  • String getMessage(String code, Object[] args, String default, Locale loc): 基本 用于从MessageSource.未找到消息时 对于指定的区域设置,将使用 default message。传入的任何参数都将变为 replacement 值,使用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 中定义的 bean。该 bean 必须具有名称messageSource.如果这样的 bean ,则所有对上述方法的调用都会委托给消息源。如果没有 message 源,则ApplicationContext尝试查找包含 bean 的 bean 的 intent如果是这样,它将使用该 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功能性。 请记住,所有ApplicationContextimplementations 也是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哪 存在于 Classpath 的根目录中。这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) 区域设置中,您 将创建名为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实现MessageSourceAwareinterface 注入了 应用程序上下文的MessageSource创建和配置 Bean 时。spring-doc.cadn.net.cn

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

1.15.2. 标准事件和自定义事件

事件处理ApplicationContext通过ApplicationEvent类和ApplicationListener接口。如果实现ApplicationListenerinterface 部署到上下文中,则每次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接口)。 这里,“initialized” 表示加载所有 bean,检测到后处理器 bean 和 activated 时,单例会预先实例化,而ApplicationContextobject 为 随时可用。只要上下文尚未关闭,就可以触发刷新 多次,前提是所选的ApplicationContext其实支持这样的 “hot” 刷新。例如XmlWebApplicationContext支持热刷新,但GenericApplicationContext不。spring-doc.cadn.net.cn

ContextStartedEventspring-doc.cadn.net.cn

发布时ApplicationContext使用start()方法上的ConfigurableApplicationContext接口。在这里,“started” 表示所有Lifecyclebean 接收显式 start 信号。通常,此信号用于重新启动 bean ,但它也可用于启动尚未 配置为自动启动(例如,尚未在 初始化)。spring-doc.cadn.net.cn

ContextStoppedEventspring-doc.cadn.net.cn

发布时ApplicationContext通过使用stop()方法上的ConfigurableApplicationContext接口。在这里,“stopped” 表示所有Lifecyclebean 接收到显式的 stop 信号。已停止的上下文可以通过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

您还可以创建和发布自己的自定义事件。以下示例显示了 simple 类,该类扩展了 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。以下内容 example 显示了这样的类: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 notificationAddres: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意,ApplicationListener通常使用 your 自定义事件 (BlockedListEvent在前面的示例中)。这意味着onApplicationEvent()method 可以保持类型安全,无需进行 downcast。 您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,event 侦听器同步接收事件。这意味着publishEvent()方法 块,直到所有侦听器都处理完事件。这样做的一个优势 同步和单线程方法是,当侦听器接收到事件时,它会 在发布者的事务上下文中运行(如果事务上下文为 可用。如果需要其他事件发布策略,请参阅 javadoc 用于 Spring 的ApplicationEventMulticaster接口 和SimpleApplicationEventMulticasterimplementation 以获取配置选项。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 是 如果有任何应阻止的电子邮件消息,则调用一个BlockedListEvent发布。这blockedListNotifierbean 注册为ApplicationListener并接收BlockedListEvent,此时它可以 通知相关方。spring-doc.cadn.net.cn

Spring 的事件机制是为 Spring bean 之间的简单通信而设计的 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring 集成项目提供了 完全支持构建轻量级、面向模式、事件驱动的 构建在众所周知的 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...
    }
}

方法签名再次声明它监听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的侦听器接口。 事件类型也可以通过泛型缩小范围,只要实际的事件类型 在其 implementation hierarchy 中解析泛型参数。spring-doc.cadn.net.cn

如果你的方法应该监听多个事件,或者你想用 no 参数,也可以在 Annotation 本身上指定事件类型。这 以下示例显示了如何执行此作: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

Arguments 数组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>代表 argument index (从 0 开始)。spring-doc.cadn.net.cn

#blEvent#a0(您还可以使用#p0#p<#arg>parameter 表示法作为别名)spring-doc.cadn.net.cn

请注意,#root.event允许您访问底层事件,即使您的方法 signature 实际上是指已发布的任意对象。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

对侦听器进行排序

如果需要先调用一个侦听器,然后再调用另一个侦听器,则可以添加@Order注解添加到方法声明中,如下例所示: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 的Resourceabstraction 的 API 方法,如 参考资料 中所述。spring-doc.cadn.net.cn

应用程序上下文是一个ResourceLoader,可用于加载Resource对象。 一个Resource本质上是 JDK 的一个功能更丰富的版本java.net.URL类。 事实上,Resource包装java.net.URL哪里 适当。一个Resource可以从 透明方式,包括从 Classpath、文件系统位置、任何位置 decpreable 替换为标准 URL 和其他一些变体。如果资源位置 string 是一个没有任何特殊前缀的简单路径,这些资源的来源是 特定于实际的应用程序上下文类型。spring-doc.cadn.net.cn

您可以配置部署到应用程序上下文中的 Bean 来实现特殊的 callback 接口,ResourceLoaderAware,以自动回调 初始化时间,应用程序上下文本身作为ResourceLoader. 您还可以公开Resource,用于访问静态资源。 它们像任何其他属性一样被注入其中。您可以指定这些Resourceproperties as simpleStringpaths 并依赖于这些文本的自动转换 strings 到 actualResource对象。spring-doc.cadn.net.cn

提供给ApplicationContextconstructor 实际上是 资源字符串,并且以简单形式,根据特定的 context 实现。例如ClassPathXmlApplicationContext将简单的 location path 作为 Classpath 位置。您还可以使用位置路径(资源字符串) 替换为特殊前缀来强制从 Classpath 或 URL 加载定义, 无论实际的上下文类型如何。spring-doc.cadn.net.cn

1.15.4. 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参数。如果参数不 存在,则侦听器使用/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.5. 部署 SpringApplicationContext作为 Java EE RAR 文件

可以部署 SpringApplicationContext作为 RAR 文件,将 context 及其所有必需的 bean 类和库 JAR 在 Java EE RAR 部署中 单位。这相当于引导一个独立的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

请参阅SpringContextResourceAdapter类,了解 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 文件拖放到 Application Server 的部署目录。spring-doc.cadn.net.cn

此类 RAR 部署单元通常是独立的。它们不暴露组件 对外界,甚至对同一应用程序的其他模块也不例外。与 基于 RARApplicationContext通常通过与之共享的 JMS 目标发生 其他模块。基于 RAR 的ApplicationContext例如,还可以安排一些作业 或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步 从外部访问,它可以(例如)导出 RMI 端点,这些端点可以使用 通过同一台计算机上的其他应用程序模块。

1.16. 使用BeanFactory

BeanFactoryAPI 为 Spring 的 IoC 功能提供了基础。 它的特定 Contract 主要用于与 Spring 和 相关的第三方框架及其DefaultListableBeanFactory实现 是更高级别中的关键委托GenericApplicationContext容器。spring-doc.cadn.net.cn

BeanFactory和相关接口(如BeanFactoryAware,InitializingBean,DisposableBean) 是其他框架组件的重要集成点。 通过不需要任何注释甚至反射,它们允许非常高效 容器与其组件之间的交互。应用程序级 bean 可以 使用相同的回调接口,但通常更喜欢声明性依赖项 注入,而是通过 Comments 或编程配置。spring-doc.cadn.net.cn

请注意,核心BeanFactoryAPI 级别及其DefaultListableBeanFactoryimplementation 不要对配置格式或任何 要使用的组件注释。所有这些风格都来自扩展 (例如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor) 和 对共享作BeanDefinition对象作为核心元数据表示形式。 这就是 Spring 的容器如此灵活和可扩展的本质。spring-doc.cadn.net.cn

1.16.1.BeanFactoryApplicationContext?

本节介绍了BeanFactoryApplicationContext容器级别及其对引导程序的影响。spring-doc.cadn.net.cn

您应该使用ApplicationContext除非您有充分的理由不这样做,否则使用GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。这些是主要条目 指向 Spring 的核心容器,用于所有常见目的:加载配置 文件, 触发类路径扫描, 以编程方式注册 Bean 定义 和带 Comments 的类,以及(从 5.0 开始)注册函数式 bean 定义。spring-doc.cadn.net.cn

因为ApplicationContext包括BeanFactory是的 通常推荐于平原BeanFactory,但 full 需要控制 Bean 处理。在ApplicationContext(例如GenericApplicationContextimplementation 中),会检测到几种 bean 按约定(即按 Bean 名称或 Bean 类型 — 特别是后处理器), 虽然普通的DefaultListableBeanFactory对任何特殊 bean 都不可知。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

方便MessageSourceAccess(用于国际化)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 支持的应用程序中,尤其是当 依靠BeanFactoryPostProcessorBeanPostProcessorextended 的实例 典型企业设置中的容器功能。spring-doc.cadn.net.cn

AnnotationConfigApplicationContext具有所有常见的 Annotation 后处理器 已注册,并可能在 覆盖配置注释,例如@EnableTransactionManagement. 在 Spring 基于 Comments 的配置模型的抽象层, Bean 后处理器的概念变成了一个纯粹的内部容器细节。spring-doc.cadn.net.cn

2. 资源

本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:spring-doc.cadn.net.cn

2.1. 简介

Java 的标准java.net.URL类和标准处理程序,用于各种 URL 前缀, 不幸的是,对于所有对低级资源的访问来说,这还不够。为 示例,则没有标准化的URL可用于访问 需要从 Classpath 获取的 Resource 或相对于ServletContext.虽然可以为 specialized 注册新的处理程序URL前缀(类似于http:),这通常是 相当复杂,而URL界面仍然缺乏一些理想的功能, 例如,用于检查所指向的资源是否存在的方法。spring-doc.cadn.net.cn

2.2. 资源接口

Spring的Resourceinterface 旨在成为一个功能更强大的抽象接口 访问低级资源。下面的清单显示了Resource接口 定义:spring-doc.cadn.net.cn

Java
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
Kotlin
interface Resource : InputStreamSource {

    fun exists(): Boolean

    val isOpen: Boolean

    val url: URL

    val file: File

    @Throws(IOException::class)
    fun createRelative(relativePath: String): Resource

    val filename: String

    val description: String
}

作为Resourceinterface 显示,它扩展了InputStreamSource接口。下面的清单显示了InputStreamSource接口:spring-doc.cadn.net.cn

Java
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}
Kotlin
interface InputStreamSource {

    val inputStream: InputStream
}

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():返回此资源的说明,用于错误 output 来执行。这通常是完全限定的文件名或 资源的实际 URL。spring-doc.cadn.net.cn

其他方法允许您获取实际的URLFile对象表示 资源(如果底层实现兼容并支持 功能)。spring-doc.cadn.net.cn

Spring 本身使用Resourceabstraction 中,作为 需要资源时有许多方法签名。某些 Spring API 中的其他方法 (例如各种ApplicationContextimplementations) 采用String它以未经修饰或简单的形式用于创建Resource适合于 该上下文实现,或者通过Stringpath 中,让 caller 指定特定的Resource必须创建和使用 implementation。spring-doc.cadn.net.cn

虽然Resourceinterface 在 Spring 中被大量使用,而 Spring 实际上是 在您自己的代码中单独用作通用工具类非常有用,用于访问 资源,即使您的代码不知道或不关心 Spring 的任何其他部分。 虽然这会将您的代码耦合到 Spring,但它实际上只将其耦合到这一小群 Utility 类,可以作为URL并且可以是 被认为等同于您用于此目的的任何其他库。spring-doc.cadn.net.cn

Resourceabstraction 不会取代 functionality。 它会尽可能地包装它。例如,UrlResource包装 URL 并使用 包裹URL来完成它的工作。

2.3. 内置资源实现

Spring 包括以下内容Resource实现:spring-doc.cadn.net.cn

2.3.1.UrlResource

UrlResourcejava.net.URL,并可用于访问任何对象 通常可通过 URL 访问,例如文件、HTTP 目标、FTP 目标等。都 URL 具有标准化的String表示,以便适当的标准化 前缀用于指示一种 URL 类型与另一种 URL 类型。这包括file:为 访问文件系统路径,http:用于通过 HTTP 协议访问资源,ftp:用于通过 FTP 访问资源等。spring-doc.cadn.net.cn

一个UrlResource由 Java 代码显式使用UrlResource构造 函数 但通常在调用采用String参数来表示路径。对于后一种情况,JavaBeansPropertyEditor最终决定哪种类型的Resource以创建。如果路径 string 包含众所周知的(对它来说,即)前缀(例如classpath:)、它 创建适当的 specializedResource对于该前缀。但是,如果它没有 识别前缀,则假定该字符串是标准 URL 字符串,并且 创建一个UrlResource.spring-doc.cadn.net.cn

2.3.2.ClassPathResource

此类表示应从 Classpath 获取的资源。它使用 线程上下文类加载器、给定类加载器或 loading resources.spring-doc.cadn.net.cn

Resourceimplementation 支持解析为java.io.File如果类路径 资源驻留在文件系统中,但不适用于驻留在 jar 中,并且尚未扩展(通过 servlet 引擎或任何环境) 添加到文件系统中。为了解决这个问题,各种Resourceimplementations 始终支持 resolution 作为java.net.URL.spring-doc.cadn.net.cn

一个ClassPathResource由 Java 代码显式使用ClassPathResource构造函数,但通常在调用采用String参数来表示路径。对于后一种情况,JavaBeansPropertyEditor识别特殊前缀classpath:、字符串路径和 创建一个ClassPathResource在那种情况下。spring-doc.cadn.net.cn

2.3.3.FileSystemResource

这是一个Resourceimplementation forjava.io.Filejava.nio.file.Path处理。 它支持将分辨率解析为FileURL.spring-doc.cadn.net.cn

2.3.4.ServletContextResource

这是一个Resourceimplementation forServletContext解释 相关 Web 应用程序根目录中的相对路径。spring-doc.cadn.net.cn

它始终支持流访问和 URL 访问,但允许java.io.File仅限访问 当 Web 应用程序存档扩展并且资源物理位于 文件系统。无论它是否被扩展、在文件系统上或被访问 直接从 JAR 或其他位置(如数据库)实际上是 依赖于 Servlet 容器。spring-doc.cadn.net.cn

2.3.5.InputStreamResource

InputStreamResource是一个Resourceimplementation for a givenInputStream.只有在没有 特定Resourceimplementation 是适用的。特别是,首选ByteArrayResource或任何基于文件的Resourceimplementation 的 Implementations 中。spring-doc.cadn.net.cn

与其他Resourceimplementations,这是已打开的 资源。因此,它返回trueisOpen().如果需要,请勿使用 将资源描述符保留在某个位置,或者如果您需要读取多个流 次。spring-doc.cadn.net.cn

2.3.6.ByteArrayResource

这是一个Resource实现。它会创建一个ByteArrayInputStream对于给定的字节数组。spring-doc.cadn.net.cn

它对于从任何给定的字节数组加载内容非常有用,而不必求助于 一次性使用InputStreamResource.spring-doc.cadn.net.cn

2.4. 使用ResourceLoader

ResourceLoaderinterface 的 API 是由可以返回 (即 load)Resource实例。下面的清单显示了ResourceLoader接口定义:spring-doc.cadn.net.cn

Java
public interface ResourceLoader {

    Resource getResource(String location);
}
Kotlin
interface ResourceLoader {

    fun getResource(location: String): Resource
}

所有应用程序上下文都实现ResourceLoader接口。因此,所有 应用程序上下文可用于获取Resource实例。spring-doc.cadn.net.cn

当您调用getResource()在特定的应用程序上下文中,以及位置路径 specified 没有特定的前缀,则会返回一个Resourcetype (即 适合该特定应用程序上下文。例如,假设以下内容 代码段针对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,无论 application context 类型,通过指定特殊的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前缀。以下一对示例使用filehttp前缀: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

从 Classpath 加载。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

http: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. 使用ResourceLoaderAware接口

ResourceLoaderAwareinterface 是一个特殊的回调接口,用于标识 组件,这些组件希望提供ResourceLoader参考。以下内容 清单显示了ResourceLoaderAware接口:spring-doc.cadn.net.cn

Java
public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}
Kotlin
interface ResourceLoaderAware {

    fun setResourceLoader(resourceLoader: ResourceLoader)
}

当类实现ResourceLoaderAware并部署到应用程序上下文中 (作为 Spring 管理的 bean),它被识别为ResourceLoaderAware按应用程序 上下文。然后,应用程序上下文调用setResourceLoader(ResourceLoader), 将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现了 这ResourceLoader接口)。spring-doc.cadn.net.cn

由于ApplicationContext是一个ResourceLoader,该 bean 还可以实现ApplicationContextAware接口,并使用提供的 Application Context 直接 load 资源。但是,一般来说,最好使用专用的ResourceLoader界面。该代码将仅与资源加载耦合 interface(可以认为是一个 Util 接口),而不是整个 SpringApplicationContext接口。spring-doc.cadn.net.cn

在应用程序组件中,您还可以依赖ResourceLoader如 实现ResourceLoaderAware接口。“传统”constructorbyType自动装配模式(如 自动装配协作者中所述) 能够提供ResourceLoader对于构造函数参数或 setter 方法参数。为了获得更大的灵活性(包括 autowire fields 和多个参数方法),请考虑使用基于注释的 自动装配功能。在这种情况下,ResourceLoader自动连接到字段, constructor 参数或方法参数,该参数需要ResourceLoadertype 作为 long 由于有问题的字段、构造函数或方法带有@Autowired注解。 有关更多信息,请参阅@Autowired.spring-doc.cadn.net.cn

2.6. 资源作为依赖项

如果 Bean 本身将通过某种排序来确定和提供资源路径 的 Bean 使用ResourceLoaderinterface 加载资源。例如,考虑加载一些 sort,其中所需的特定资源取决于用户的角色。如果 资源是静态的,因此消除ResourceLoader接口中,让 bean 公开Resource属性, 并期望他们被注入其中。spring-doc.cadn.net.cn

然后注入这些属性变得微不足道的是,所有应用程序上下文 注册并使用特殊的 JavaBeansPropertyEditor,它可以将String路径 自Resource对象。因此,如果myBean具有 Template 类型的属性Resource,它可以 为该资源配置一个简单的字符串,如下例所示:spring-doc.cadn.net.cn

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

请注意,资源路径没有前缀。因此,因为应用程序上下文本身是 将用作ResourceLoader,则资源本身是通过ClassPathResource一个FileSystemResourceServletContextResource, 取决于上下文的确切类型。spring-doc.cadn.net.cn

如果您需要强制使用特定的Resourcetype,可以使用前缀。 以下两个示例展示了如何强制执行ClassPathResource以及UrlResource(后者用于访问文件系统文件):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"/>

2.7. 应用程序上下文和资源路径

本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 ,以及如何使用 XML、如何使用通配符和其他详细信息。spring-doc.cadn.net.cn

2.7.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 定义是从 classpath 加载的,因为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

请注意,在 location path 会覆盖默认类型Resourcecreated 来加载 定义。请考虑以下示例:spring-doc.cadn.net.cn

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")

FileSystemXmlApplicationContext从 Classpath 中加载 bean 定义。但是,它仍然是一个FileSystemXmlApplicationContext.如果它随后被用作ResourceLoader任何 无前缀的路径仍被视为文件系统路径。spring-doc.cadn.net.cn

构建ClassPathXmlApplicationContext实例 — 快捷方式

ClassPathXmlApplicationContext公开了许多构造函数以启用 方便的实例化。基本思想是,您可以只提供一个字符串数组 ,仅包含 XML 文件本身的文件名(没有前导路径 信息),并且还提供了一个Class.这ClassPathXmlApplicationContext然后从提供的类中派生路径信息。spring-doc.cadn.net.cn

请考虑以下目录布局:spring-doc.cadn.net.cn

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

以下示例显示了ClassPathXmlApplicationContext实例由 名为services.xmldaos.xml(在 Classpath 上)可以实例化:spring-doc.cadn.net.cn

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);
Kotlin
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java)

请参阅ClassPathXmlApplicationContextjavadoc 了解有关各种构造函数的详细信息。spring-doc.cadn.net.cn

2.7.2. 应用程序上下文构造函数资源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径(如 前面所示),每个 API 都有一个到目标的一对一映射Resource或者,也可以选择 包含特殊的 “classpath*:” 前缀或内部 Ant 样式的正则表达式 (使用 Spring 的PathMatcher实用程序)。后者两者都是有效的 通配符。spring-doc.cadn.net.cn

此机制的一个用途是当您需要执行组件样式的应用程序组装时。都 组件可以将上下文定义片段“发布”到已知的位置路径,并且 当使用前缀为classpath*:,则会自动选取所有组件片段。spring-doc.cadn.net.cn

请注意,此通配符特定于应用程序上下文中资源路径的使用 构造函数(或者当您使用PathMatcherUtility 类层次结构),并且是 在构建时解决。它与Resource键入自身。 您不能使用classpath*:前缀来构造实际的Resource如 一个资源一次只指向一个资源。spring-doc.cadn.net.cn

Ant-style 模式

路径位置可以包含 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从 it 或 手动解析 jar URL,然后遍历 jar 文件的内容以解析 通配符。spring-doc.cadn.net.cn

对可移植性的影响

如果指定的路径已经是一个文件 URL(要么隐式,因为基ResourceLoader是文件系统 One 或显式的),则通配符保证为 以完全便携的方式工作。spring-doc.cadn.net.cn

如果指定的路径是 Classpath 位置,则解析程序必须获取最后一个 非通配符路径段 URL,方法是将Classloader.getResource()叫。由于这个 只是路径的一个节点(不是末尾的文件),它实际上是 undefined 的(在ClassLoaderjavadoc) 中返回的 URL 类型。在实践中, 它始终是一个java.io.File表示目录(其中 Classpath 资源 解析为文件系统位置)或某种类型的 jar URL(其中 Classpath 资源 解析为 jar 位置)。尽管如此,此作仍然存在可移植性问题。spring-doc.cadn.net.cn

如果获取了最后一个非通配符段的 jar URL,则解析程序必须能够 获取java.net.JarURLConnection或手动解析 jar URL,以便能够 遍历 jar 的内容并解析通配符。这在大多数环境中都有效 但在其他 API 中失败,我们强烈建议 resources 的通配符解析 来自 jars 在您依赖它之前,请在您的特定环境中对其进行全面测试。spring-doc.cadn.net.cn

classpath*:前缀

在构建基于 XML 的应用程序上下文时,位置字符串可以使用 特殊classpath*:前缀,如下例所示:spring-doc.cadn.net.cn

Java
ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")

此特殊前缀指定与给定名称匹配的所有 Classpath 资源 必须获取(在内部,这基本上是通过调用ClassLoader.getResources(…​)),然后合并形成最终应用程序 context 定义。spring-doc.cadn.net.cn

通配符类路径依赖于getResources()底层 classloader 中。由于现在大多数应用程序服务器都提供自己的类加载器 实现时,行为可能会有所不同,尤其是在处理 JAR 文件时。一个 简单测试以检查classpath*works 是使用 classloader 从 在 Classpath 上的 jar 中:getClass().getClassLoader().getResources("<someFileInsideTheJar>").尝试此测试 具有相同名称但放置在两个不同位置的文件。如果 不适当的结果,请查看 Application Server 文档以获取 可能影响 ClassLoader 行为的设置。

您还可以将classpath*:前缀替换为PathMatcherpattern 中 位置路径的其余部分(例如classpath*:META-INF/*-beans.xml).在这个 的情况下,解决策略相当简单:AClassLoader.getResources()call 为 用于获取 类加载器层次结构,然后从每个资源中,相同的PathMatcher分辨率 前面描述的策略用于通配符子路径。spring-doc.cadn.net.cn

与通配符相关的其他说明

请注意,classpath*:,当与 Ant 样式模式结合使用时,仅有效 可靠地使用至少一个根目录,除非实际的 目标文件驻留在文件系统中。这意味着classpath*:*.xml可能不会从 jar 文件的根目录中检索文件,而只能检索 从扩展目录的根目录。spring-doc.cadn.net.cn

Spring 检索 Classpath 条目的能力源自 JDK 的ClassLoader.getResources()方法,该方法仅返回 空字符串(指示要搜索的潜在根)。Spring 计算URLClassLoader运行时配置和java.class.path在 jar 文件中的清单 ,但不能保证这会导致可移植行为。spring-doc.cadn.net.cn

扫描 classpath 包需要存在相应的目录 条目。使用 Ant 构建 JAR 时,不要仅激活文件 switch 的 jar 任务。此外,Classpath 目录可能不会根据安全性公开 某些环境中的策略 — 例如,JDK 1.7.0_45 上的独立应用程序 和更高级别(这需要在您的清单中设置 'Trusted-Library')。请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。spring-doc.cadn.net.cn

在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 在这里,强烈建议将资源放入专用目录, 避免了上述搜索 jar 文件根级别的可移植性问题。spring-doc.cadn.net.cn

Ant 样式的模式,其中classpath:不保证 resources 能找到匹配项 resources(如果要搜索的根包在多个类路径位置中可用)。 请考虑以下资源位置示例:spring-doc.cadn.net.cn

com/mycompany/package1/service-context.xml

现在考虑一个 Ant 样式的路径,有人可能会使用它来尝试查找该文件:spring-doc.cadn.net.cn

classpath:com/mycompany/**/service-context.xml

此类资源可能只位于一个位置,但是当路径(如前面的示例) 用于尝试解析它,则解析器会处理由getResource("com/mycompany");.如果此基础包节点存在于多个 ClassLoader 位置,则实际的最终资源可能不存在。因此,在这种情况下 您应该更喜欢使用classpath*:具有相同的 Ant 样式模式,其中 搜索包含根包的所有类路径位置。spring-doc.cadn.net.cn

2.7.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实现来解析属性值并设置其格式。这PropertyEditorPropertyEditorSupporttypes 是 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 的 Validator 接口进行验证

Spring 具有Validator可用于验证对象的接口。这Validatorinterface 通过使用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相当简单,尤其是当您知道ValidationUtilshelper 类。以下内容 示例 implementsValidatorPerson实例: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 用于 reject 的nameproperty (如果是null或空字符串。看看ValidationUtilsJavadoc 以查看除了前面显示的示例之外,它还提供了哪些功能。spring-doc.cadn.net.cn

虽然当然可以实现单个Validator类来验证每个 的嵌套对象中,最好将验证 每个嵌套类的对象在其自己的 logicValidator实现。一个简单的 “rich” 对象的示例是Customer它由两个String属性(名字和第二个名称)和复杂Address对象。Address对象 可以独立使用Customer对象,因此AddressValidator已实施。如果您希望您的CustomerValidator以重用包含的 logic 在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 中,您可以使用<spring:bind/>标签来检查错误消息,但 您还可以检查Errors反对自己。有关 它提供的方法可以在 Javadoc 中找到。spring-doc.cadn.net.cn

3.2. 将代码解析为错误消息

我们介绍了数据绑定和验证。本节介绍如何输出对应的消息 验证错误。在上一节所示的示例中, 我们拒绝了nameage领域。如果我们想使用MessageSource,我们可以使用在拒绝字段时提供的错误代码来执行此作 (在本例中为 'name' 和 'age')。当您调用 (直接或间接, 通过使用 例如,ValidationUtils类)rejectValue或其他reject方法 从Errors接口,底层实现不仅会注册你 传入,但还会注册许多其他错误代码。这MessageCodesResolver确定Errorsinterface 寄存器。默认情况下,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,并且可以找到 default 策略 在 Javadoc 的MessageCodesResolverDefaultMessageCodesResolver, 分别。spring-doc.cadn.net.cn

3.3. Bean作和BeanWrapper

org.springframework.beanspackage 遵循 JavaBeans 标准。 JavaBean 是一个具有默认无参数构造函数的类,它遵循 命名约定,其中(例如)名为bingoMadness愿意 具有 setter 方法setBingoMadness(..)和 getter 方法getBingoMadness().为 有关 JavaBeans 和规范的更多信息,请参阅 JavaBeansspring-doc.cadn.net.cn

beans 包中一个非常重要的类是BeanWrapperinterface 及其 相应的实现 (BeanWrapperImpl).引用自 javadoc,BeanWrapper提供设置和获取属性值(单独或在 bulk)、获取属性描述符和查询属性以确定它们是否为 readable 或 writable。此外,BeanWrapper提供对嵌套属性的支持, 将 子属性 的 属性设置为 无限深度 。这BeanWrapper还支持添加标准 JavaBeans 的功能PropertyChangeListenersVetoableChangeListeners,而无需 target 类中的支持代码。 最后但并非最不重要的一点是,BeanWrapper支持设置索引属性。 这BeanWrapper通常不直接由应用程序代码使用,但由DataBinderBeanFactory.spring-doc.cadn.net.cn

方式BeanWrapperworks 部分由它的名称表示:它将一个 bean 包装到 对该 Bean 执行作,例如设置和检索属性。spring-doc.cadn.net.cn

3.3.1. 设置和获取 Basic 和 Nested 属性

设置和获取属性是通过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

指示 indexed 属性的第三个元素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
}

以下代码片段显示了如何检索和作某些 instantiated 的属性CompaniesEmployees: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要在Object以及String.它可以很方便 以不同于对象本身的方式表示属性。例如,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

将表示类的 String 解析为实际的类,反之亦然。当 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.不 registered (默认)。必须根据需要使用适当的格式进行用户注册。spring-doc.cadn.net.cn

CustomNumberEditorspring-doc.cadn.net.cn

可定制的属性编辑器Number子类,例如Integer,Long,FloatDouble.默认情况下,由BeanWrapperImpl但可以被 将其自定义实例注册为 Custom Editor。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因此InputStreamproperties 可以直接设置为 strings。请注意,默认用法不会关闭 这InputStream给你的。默认情况下,由BeanWrapperImpl.spring-doc.cadn.net.cn

LocaleEditorspring-doc.cadn.net.cn

可以将字符串解析为Locale对象,反之亦然(字符串格式为[country][variant],与toString()method 的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.Properties类)设置为Properties对象。默认情况下,已注册 由BeanWrapperImpl.spring-doc.cadn.net.cn

StringTrimmerEditorspring-doc.cadn.net.cn

修剪字符串的 Property editor。(可选)允许转换空字符串 转换为null价值。默认情况下未注册 — 必须由用户注册。spring-doc.cadn.net.cn

URLEditorspring-doc.cadn.net.cn

可以将 URL 的字符串表示形式解析为实际的URL对象。 默认情况下,由BeanWrapperImpl.spring-doc.cadn.net.cn

Spring 使用java.beans.PropertyEditorManager设置 Search path for 属性 可能需要的编辑器。搜索路径还包括sun.bean.editors哪 包括PropertyEditor类型的实现,例如Font,Color和大部分 原始类型。另请注意,标准的 JavaBeans 基础结构 自动发现PropertyEditor类(无需注册它们 显式地)如果它们与它们处理的类位于同一 package 中,并且具有相同的 name 作为该类,使用Editor附加。例如,可以有以下内容 class 和 package 结构,这对于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) {
                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实现将这些字符串转换为 Complex 类型的 财产。Spring 预先注册了一些自定义PropertyEditor实现(例如,更改为 将表示为字符串的类名转换为Class对象)。此外 Java 的标准 JavaBeansPropertyEditorlookup 机制允许PropertyEditor,请适当命名,并将其放置在与类相同的包中 为此,它提供支持,以便可以自动找到它。spring-doc.cadn.net.cn

如有需要注册其他定制PropertyEditors,几种机制是 可用。最手动的方法,通常不方便或 推荐使用registerCustomEditor()方法ConfigurableBeanFactory接口,假设您有一个BeanFactory参考。 另一种(稍微方便一点)机制是使用特殊的咖啡豆工厂 后处理器称为CustomEditorConfigurer.虽然您可以使用 Bean Factory 后处理器 跟BeanFactoryimplementations、CustomEditorConfigurer具有 nested 属性设置,因此我们强烈建议您将它与ApplicationContext,您可以在其中以与任何其他 bean 类似的方式部署它,并且 可以自动检测和应用。spring-doc.cadn.net.cn

请注意,所有 bean 工厂和应用程序上下文都会自动使用一些 内置的属性编辑器,通过使用BeanWrapper自 处理属性转换。标准属性编辑器,其中BeanWrapper寄存器在上一节中列出。 此外ApplicationContexts还要覆盖或添加其他编辑器来处理 资源查找。spring-doc.cadn.net.cn

标准 JavaBeansPropertyEditor实例用于转换属性值 表示为属性的实际复杂类型的字符串。您可以使用CustomEditorConfigurer,一个 Bean Factory 后处理器,以方便地添加 支持其他PropertyEditor实例复制到ApplicationContext.spring-doc.cadn.net.cn

请考虑以下示例,该示例定义了一个名为ExoticType和 另一个名为DependsOnExoticType,它需要ExoticTypeset as a property: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
}

当事情设置正确时,我们希望能够将 type 属性分配为 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实例。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 框架),使用PropertyEditorRegistrars在 与 data-binding 结合使用Controllers(例如SimpleFormController) 可以非常 方便。以下示例使用PropertyEditorRegistrar在 实现initBinder(..)方法:spring-doc.cadn.net.cn

Java
public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}
Kotlin
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {

    protected fun initBinder(request: HttpServletRequest,
                            binder: ServletRequestDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }

    // other methods to do with registering a User
}

这种风格的PropertyEditor注册可以导致简洁的代码(实现 之initBinder(..)只有一行长),并让 commonPropertyEditorregistration code 封装在一个类中,然后在尽可能多的Controllers根据需要。spring-doc.cadn.net.cn

3.4. Spring Type Conversion

Spring 3 引入了一个core.convert提供常规类型转换的软件包 系统。系统定义了一个 SPI 来实现类型转换逻辑和一个 API 在运行时执行类型转换。在 Spring 容器中,您可以使用此系统 作为PropertyEditor转换外部化 Bean 属性值的实现 strings 设置为所需的属性类型。您还可以在 需要类型转换的应用程序。spring-doc.cadn.net.cn

3.4.1. 转换器 SPI

实现类型转换逻辑的 SPI 简单且强类型,如下所示 接口定义显示:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}
Kotlin
package org.springframework.core.convert.converter

interface Converter<S, T> {

    fun convert(source: S): T
}

要创建您自己的转换器,请实现Converterinterface 和 parameterizeS作为您要转换的 type 并将T作为您要转换为的类型。您还可以透明地应用这样的 converter 如果是S需要 转换为T,前提是委托数组或集合 converter 也被注册了(它DefaultConversionService执行)。spring-doc.cadn.net.cn

对于对convert(S),则保证 source 参数不为 null。你Converter如果转换失败,可能会引发任何未经检查的异常。具体来说,它应该抛出一个IllegalArgumentException报告无效的源值。 请注意确保您的Converter实现是线程安全的。spring-doc.cadn.net.cn

中提供了几种转换器实现core.convert.supportpackage 设置为 一种便利。其中包括从字符串到数字和其他常见类型的转换器。 下面的清单显示了StringToInteger类,这是一个典型的Converter实现:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}
Kotlin
package org.springframework.core.convert.support

import org.springframework.core.convert.converter.Converter

internal class StringToInteger : Converter<String, Int> {

    override fun convert(source: String): Int? {
        return Integer.valueOf(source)
    }
}

3.4.2. 使用ConverterFactory

当您需要集中整个类层次结构的转换逻辑时 (例如,从StringEnum对象),您可以实现ConverterFactory,如下例所示:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
Kotlin
package org.springframework.core.convert.converter

interface ConverterFactory<S, R> {

    fun <T : R> getConverter(targetType: Class<T>): Converter<S, T>
}

将 S 参数化为要转换的起始类型,将 R 参数化为定义基本类型 您可以转换为的类的范围。然后实施getConverter(Class<T>), 其中 T 是 R 的子类。spring-doc.cadn.net.cn

考虑一下StringToEnumConverterFactory例如:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnumConverter(targetType);
    }

    private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}

3.4.3. 使用GenericConverter

当您需要复杂的Converter实现中,请考虑使用GenericConverter接口。使用更灵活但类型不太强的签名 比Converter一个GenericConverter支持在多个源和 目标类型。此外,一个GenericConverter使源字段和目标字段可用 context 中,您可以在实施转化逻辑时使用。这样的上下文允许 类型转换由字段注释或在 字段签名。以下清单显示了GenericConverter:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
Kotlin
package org.springframework.core.convert.converter

interface GenericConverter {

    fun getConvertibleTypes(): Set<ConvertiblePair>?

    fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any?
}

要实现GenericConvertergetConvertibleTypes()返回支持的 source→target 类型对。然后实施convert(Object, TypeDescriptor, TypeDescriptor)以包含您的转化逻辑。来源TypeDescriptor提供 访问保存要转换的值的 source 字段。目标TypeDescriptor提供对要设置转换值的目标字段的访问。spring-doc.cadn.net.cn

一个很好的例子GenericConverter是在 Java 数组之间进行转换的转换器 和一个集合。这样的ArrayToCollectionConverter内省声明 目标集合类型,用于解析集合的元素类型。这样,每个 元素转换为 collection 元素类型,然后再将 collection 在 target 字段上设置。spring-doc.cadn.net.cn

因为GenericConverter是一个更复杂的 SPI 接口,您应该使用 它只在你需要的时候。喜爱ConverterConverterFactory用于基本型 转换需求。
ConditionalGenericConverter

有时,您需要一个Converter仅在特定条件为 true 时运行。为 示例中,您可能希望运行Converter仅当存在特定注释时 在 target 字段上,或者您可能希望运行Converter仅当特定方法 (例如static valueOf方法)在 Target 类上定义。ConditionalGenericConverterGenericConverterConditionalConverter接口,用于定义此类自定义匹配条件:spring-doc.cadn.net.cn

Java
public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
Kotlin
interface ConditionalConverter {

    fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
}

interface ConditionalGenericConverter : GenericConverter, ConditionalConverter

一个很好的例子ConditionalGenericConverter是一个IdToEntityConverter转换 在持久实体标识符和实体引用之间。这样的IdToEntityConverter仅当目标实体类型声明静态查找器方法(例如,findAccount(Long)).您可以在matches(TypeDescriptor, TypeDescriptor).spring-doc.cadn.net.cn

3.4.4. 使用ConversionService应用程序接口

ConversionService定义一个统一的 API,用于在 运行。转换器通常在以下 Facade 接口后面运行:spring-doc.cadn.net.cn

Java
package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}
Kotlin
package org.springframework.core.convert

interface ConversionService {

    fun canConvert(sourceType: Class<*>, targetType: Class<*>): Boolean

    fun <T> convert(source: Any, targetType: Class<T>): T

    fun canConvert(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean

    fun convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any

}

ConversionServiceimplementations 还实现ConverterRegistry哪 提供用于注册转换器的 SPI。在内部,一个ConversionServiceimplementation 委托其注册的 converters 来执行类型转换逻辑。spring-doc.cadn.net.cn

一个强大的ConversionServiceimplementation 在core.convert.support包。GenericConversionService通用实现是否适合 在大多数环境中使用。ConversionServiceFactory提供便捷的工厂 创建通用ConversionService配置。spring-doc.cadn.net.cn

3.4.5. 配置ConversionService

一个ConversionService是一个无状态对象,旨在在 application 中实例化 startup 的 URL,然后在多个线程之间共享。在 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自动注册 适用于大多数环境。这包括集合转换器、标量 转换器和 BasicObject-自-String变换 器。您可以注册相同的转换器 与任何ConverterRegistry通过使用静态addDefaultConverters方法上的DefaultConversionService类。spring-doc.cadn.net.cn

值类型的转换器被重新用于数组和集合,因此有 无需创建特定的转换器即可从CollectionS更改为CollectionT,假设标准集合处理是合适的。spring-doc.cadn.net.cn

3.5. Spring Field 格式化

如上一节所述,core.convert是一个 通用型转换系统。它提供了一个统一的ConversionServiceAPI 作为 以及强类型ConverterSPI 用于实现一种类型的转换逻辑 到另一个。Spring 容器使用此系统来绑定 bean 属性值。在 此外,Spring 表达式语言 (SpEL) 和DataBinder使用此系统可以 bind 字段值。例如,当 SPEL 需要强制Short更改为Long自 完成expression.setValue(Object bean, Object value)尝试,则core.convert系统执行强制转换。spring-doc.cadn.net.cn

现在考虑典型客户端环境的类型转换要求,例如 Web 或桌面应用程序。在此类环境中,您通常从String支持客户端回发过程,以及返回到String以支持 View 渲染过程。此外,您经常需要本地化String值。越多 常规core.convert ConverterSPI 不满足此类格式要求 径直。为了直接解决这些问题,Spring 3 引入了一个方便的FormatterSPI 的 提供了一种简单而强大的替代方案PropertyEditor客户端环境的实现。spring-doc.cadn.net.cn

通常,您可以使用ConverterSPI 当您需要实现通用型 conversion logic — 例如,用于在java.util.Date以及Long. 您可以使用Formatter当您在客户端环境(例如 Web application),并且需要解析和打印本地化的字段值。这ConversionService为这两个 SPI 提供统一的类型转换 API。spring-doc.cadn.net.cn

3.5.1. 使用FormatterSPI 系列

Formatter用于实现字段格式化逻辑的 SPI 简单且强类型。这 下面的清单显示了Formatter接口定义:spring-doc.cadn.net.cn

Java
package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

FormatterPrinterParser构建块接口。这 下面的清单显示了这两个接口的定义:spring-doc.cadn.net.cn

Java
public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
Kotlin
interface Printer<T> {

    fun print(fieldValue: T, locale: Locale): String
}
Java
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}
Kotlin
interface Parser<T> {

    @Throws(ParseException::class)
    fun parse(clientValue: String, locale: Locale): T
}

要创建自己的Formatter中,实现Formatter界面。 参数化T设置为要格式化的对象类型,例如,java.util.Date.实现print()作以打印T为 display in client locale 中。实现parse()作来解析T从客户端区域设置返回的格式化表示形式。你Formatter应该抛出一个ParseExceptionIllegalArgumentException如果解析尝试失败。拿 确保您的Formatter实现是线程安全的。spring-doc.cadn.net.cn

format子包提供了多个Formatter实现以方便使用。 这numberpackage 提供NumberStyleFormatter,CurrencyStyleFormatterPercentStyleFormatter格式化Number使用java.text.NumberFormat. 这datetime包提供了一个DateFormatter格式化java.util.Date对象替换为 一个java.text.DateFormat.这datetime.joda包提供全面的 datetime 基于 Joda-Time 库的格式设置支持。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 Issues to contribute。spring-doc.cadn.net.cn

3.5.2. 注解驱动的格式化

字段格式可以按字段类型或注释进行配置。绑定 对Formatter实现AnnotationFormatterFactory.以下内容 清单显示了AnnotationFormatterFactory接口:spring-doc.cadn.net.cn

Java
package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}
Kotlin
package org.springframework.format

interface AnnotationFormatterFactory<A : Annotation> {

    val fieldTypes: Set<Class<*>>

    fun getPrinter(annotation: A, fieldType: Class<*>): Printer<*>

    fun getParser(annotation: A, fieldType: Class<*>): Parser<*>
}

要创建实施: .将 A 参数化为字段annotationType您希望与之关联的 格式化逻辑 — 例如org.springframework.format.annotation.DateTimeFormat. .有getFieldTypes()返回可以使用注释的字段类型。 .有getPrinter()返回一个Printer以打印带注释的字段的值。 .有getParser()返回一个Parser要解析clientValue对于带注释的字段。spring-doc.cadn.net.cn

以下示例AnnotationFormatterFactoryimplementation 将@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和 Joda-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.因为这个 implementation 还实现ConversionService,可以直接配置 用于 Spring 的DataBinder和 Spring 表达式语言 (SpEL)。spring-doc.cadn.net.cn

下面的清单显示了FormatterRegistrySPI:spring-doc.cadn.net.cn

Java
package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory);
}
Kotlin
package org.springframework.format

interface FormatterRegistry : ConverterRegistry {

    fun addFormatterForFieldType(fieldType: Class<*>, printer: Printer<*>, parser: Parser<*>)

    fun addFormatterForFieldType(fieldType: Class<*>, formatter: Formatter<*>)

    fun addFormatterForFieldType(formatter: Formatter<*>)

    fun addFormatterForAnnotation(factory: AnnotationFormatterFactory<*>)
}

如前面的清单所示,您可以按字段类型或注释注册格式化程序。spring-doc.cadn.net.cn

FormatterRegistrySPI 允许您集中配置格式规则,而不是 在您的控制器之间复制此类配置。例如,您可能希望 强制所有日期字段都以某种方式格式化,或者强制字段具有特定的 annotation 以某种方式格式化。使用共享的FormatterRegistry,您可以定义 这些规则一次,每当需要格式化时都会应用它们。spring-doc.cadn.net.cn

3.5.4. 使用FormatterRegistrarSPI 系列

FormatterRegistrar是一个 SPI,用于通过 FormatterRegistry 的下面的清单显示了它的接口定义:spring-doc.cadn.net.cn

Java
package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
Kotlin
package org.springframework.format

interface FormatterRegistrar {

    fun registerFormatters(registry: FormatterRegistry)
}

一个FormatterRegistrar在注册多个相关转换器时很有用,并且 给定格式类别的格式化程序,例如日期格式。它也可以是 在声明式注册不足时很有用 — 例如,当格式化程序 需要在与自身不同的特定字段类型下编制索引<T>或者 注册一个Printer/Parser双。下一节提供了有关 转换器和格式化程序注册。spring-doc.cadn.net.cn

3.5.5. 在 Spring MVC 中配置格式化

参见 Spring MVC 一章中的转换和格式化spring-doc.cadn.net.cn

3.6. 配置全局日期和时间格式

默认情况下,日期和时间字段未使用@DateTimeFormat从 strings 结合使用DateFormat.SHORT风格。如果您愿意,可以通过以下方式更改此设置 定义您自己的全局格式。spring-doc.cadn.net.cn

为此,请确保 Spring 不注册默认格式化程序。相反,请注册 格式化程序:spring-doc.cadn.net.cn

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrarspring-doc.cadn.net.cn

  • org.springframework.format.datetime.DateFormatterRegistrarorg.springframework.format.datetime.joda.JodaTimeFormatterRegistrar为 Joda-Time。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 registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): FormattingConversionService {
        // Use the DefaultFormattingConversionService but do not register defaults
        return DefaultFormattingConversionService(false).apply {

            // Ensure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())

            // Register JSR-310 date conversion with a specific global format
            val registrar = DateTimeFormatterRegistrar()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)

            // Register date conversion with a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters(this)
        }
    }
}

如果您更喜欢基于 XML 的配置,可以使用FormattingConversionServiceFactoryBean.以下示例显示了如何执行此作(这次使用 Joda 时间):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.joda.JodaTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>

请注意,在 Web 中配置日期和时间格式时,还需要注意一些额外的注意事项 应用。请参阅 WebMVC 转换和格式化WebFlux 转换和格式化spring-doc.cadn.net.cn

3.7. Java Bean 验证

Spring Framework 提供了对 Java Bean 验证 API 的支持。spring-doc.cadn.net.cn

3.7.1. Bean 验证概述

Bean Validation 提供了一种通用的验证方法,通过 constraint declaration 和 元数据。要使用它,您可以使用 声明性验证约束,然后由运行时强制执行。有 built-in constraints,您还可以定义自己的自定义 constraints。spring-doc.cadn.net.cn

请考虑以下示例,它显示了一个简单的PersonFormmodel 具有两个属性: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 验证允许您声明约束,如下例所示: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 验证器根据声明的 约束。有关 API 的 API 创建。请参阅 Hibernate Validator 文档 特定约束。了解如何将 Bean 验证提供程序设置为 Spring Bean,请继续阅读。spring-doc.cadn.net.cn

3.7.2. 配置 Bean 验证提供程序

Spring 提供了对 Bean 验证 API 的全面支持,包括 Bean Validation 提供程序作为 Spring Bean。这样,您就可以在邮件中注入javax.validation.ValidatorFactoryjavax.validation.Validator验证位于何处 在您的应用程序中需要。spring-doc.cadn.net.cn

您可以使用LocalValidatorFactoryBean将默认 Validator 配置为 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 Validation 提供程序,例如 Hibernate Validator 应存在于 Classpath 中,并被自动检测。spring-doc.cadn.net.cn

注入验证器

LocalValidatorFactoryBean同时实现javax.validation.ValidatorFactoryjavax.validation.Validator以及 Spring 的org.springframework.validation.Validator. 您可以将对这些接口中任一接口的引用注入到需要调用 验证逻辑。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如果您的 bean 需要 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注解 引用相应的ConstraintValidatorimplementation 类。在运行时,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 {

    // ...
}

如前面的示例所示,ConstraintValidatorimplementation 可以有其依赖项@Autowired就像任何其他 Spring bean 一样。spring-doc.cadn.net.cn

Spring 驱动的方法验证

您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为 自定义扩展(也是由 Hibernate Validator 4.3 提供的)通过MethodValidationPostProcessorbean 定义: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会自动添加到 Binder 的BindingResult.spring-doc.cadn.net.cn

以下示例演示如何使用DataBinder以编程方式调用 validation 绑定到目标对象后的逻辑: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”)是一种强大的表达式语言,它 支持在运行时查询和作对象图。语言语法为 类似于 Unified EL,但提供了额外的功能,最明显的是方法调用和 基本的字符串模板功能。spring-doc.cadn.net.cn

虽然还有其他几种可用的 Java 表达式语言 — OGNL、MVEL 和 JBoss EL,仅举几例 — Spring 表达式语言的创建是为了提供 Spring 社区,该社区具有一种受支持的表达式语言,可用于所有 Spring 产品组合中的产品。它的语言功能由 Spring 产品组合中项目的要求,包括工具要求 以获取 Spring Tools for Eclipse 中的代码完成支持。 也就是说,SPEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,可以集成 implementations。spring-doc.cadn.net.cn

虽然 SpEL 是 Spring 中表达式评估的基础 portfolio 中,它不直接与 Spring 绑定,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是一个 独立的表达式语言。这需要创建一些引导 infrastructure 类,例如 parser 的 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 message 变量的值为'Hello World'.
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 message 变量的值为'Hello World'.

您最有可能使用的 SPEL 类和接口位于org.springframework.expressionpackage 及其子软件包,例如spel.support.spring-doc.cadn.net.cn

ExpressionParserinterface 负责解析表达式字符串。在 前面的示例,表达式 String 是由周围的 single 表示的字符串文本 引号。这Expressioninterface 负责评估之前定义的 expression 字符串。可以引发的两个异常ParseExceptionEvaluationException,调用parser.parseExpressionexp.getValue, 分别。spring-doc.cadn.net.cn

SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。spring-doc.cadn.net.cn

在下面的方法调用示例中,我们调用concatmethod 的字符串文本: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 此行将 Literals 转换为字节数组。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 此行将 Literals 转换为字节数组。

SPEL 还通过使用标准点表示法(例如prop1.prop2.prop3) 以及相应的属性值设置。 还可以访问 Public 字段。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从 Literal 中将其设为大写。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
1 构造一个新的String从 Literal 中将其设为大写。

请注意 generic 方法的使用:public <T> T getValue(Class<T> desiredResultType). 使用此方法无需将表达式的值强制转换为所需的值 result 类型。一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接口在计算表达式以解析 属性、方法或字段,并帮助执行类型转换。Spring 提供两个 实现。spring-doc.cadn.net.cn

  • SimpleEvaluationContext:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。spring-doc.cadn.net.cn

  • StandardEvaluationContext:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。spring-doc.cadn.net.cn

SimpleEvaluationContext旨在仅支持 SPEL 语言语法的子集。 它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求 U 显式选择对表达式中属性和方法的支持级别。 默认情况下,create()static factory method 仅允许对属性进行读取访问。 您还可以获取构建器来配置所需的确切支持级别,并针对 以下一项或多项组合:spring-doc.cadn.net.cn

类型转换

默认情况下,SPEL 使用 Spring 核心中提供的转换服务 (org.springframework.core.convert.ConversionService).此转换服务随之而来 具有许多用于常见转换的内置转换器,但也完全可扩展,因此 您可以在类型之间添加自定义转换。此外,它是 generics-aware。这意味着,当您在 表达式中,SPEL 会尝试转换以保持任何对象的类型正确性 它相遇。spring-doc.cadn.net.cn

这在实践中意味着什么?假设赋值,使用setValue()正在使用 要设置List财产。属性的类型实际上是List<Boolean>.斯佩尔 识别出需要将 List 的元素转换为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).配置 object 控制某些表达式组件的行为。例如,如果你 index 添加到数组或集合中,并且指定索引处的元素为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 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。spring-doc.cadn.net.cn

请考虑以下基本表达式:spring-doc.cadn.net.cn

someArray[0].someProperty.someOtherProperty < 0.1

因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。spring-doc.cadn.net.cn

编译器配置

默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用系统 当 SPEL 用法嵌入到另一个组件中时,属性。本节 讨论这两个选项。spring-doc.cadn.net.cn

编译器可以在以下三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerModeenum 中。模式如下:spring-doc.cadn.net.cn

  • OFF(默认):编译器已关闭。spring-doc.cadn.net.cn

  • IMMEDIATE:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。spring-doc.cadn.net.cn

  • MIXED:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户进入的异常IMMEDIATEmode 则在内部处理。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)。 编译的表达式在提供的 any 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。spring-doc.cadn.net.cn

配置编译器的第二种方法是在 SpEL 嵌入到其他一些 组件,并且可能无法通过配置对象对其进行配置。在这些 情况下,可以使用 system 属性。您可以设置spring.expression.compiler.modeproperty 添加到SpelCompilerMode枚举值 (off,immediatemixed).spring-doc.cadn.net.cn

编译器限制

从 Spring Framework 4.1 开始,基本的编译框架就位了。但是,框架 尚不支持编译各种表达式。最初的重点是 可能在性能关键型上下文中使用的常用表达式。以下内容 kinds of expression 目前无法编译: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 名称。这包括标准上下文 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>

请注意,您不必在此处为预定义变量加上 symbol 前缀。#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
}

以下示例显示了等效的 but on a property 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
}

自动装配的方法和构造函数也可以使用@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. 文字表达式

支持的文字表达式类型包括字符串、数值(int、real、hex)、 boolean 和 null。字符串由单引号分隔。放置单引号本身 在字符串中,使用两个单引号字符。spring-doc.cadn.net.cn

下面的清单显示了 Literals 的简单用法。通常,它们不会被使用 像这样孤立地进行,而是作为更复杂的表达式的一部分——例如, 在逻辑比较运算符的一侧使用 Literals。spring-doc.cadn.net.cn

Java
ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();
Kotlin
val parser = SpelExpressionParser()

// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String

val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double

// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int

val trueValue = parser.parseExpression("true").value as Boolean

val nullValue = parser.parseExpression("null").value

数字支持使用负号、指数表示法和小数点。 默认情况下,使用 Double.parseDouble() 解析实数。spring-doc.cadn.net.cn

4.3.2. 属性、数组、列表、映射和索引器

使用属性引用进行导航非常简单。为此,请使用句点来表示嵌套的 property 值。的Inventorpupintesla中填充了 数据列在 Classes used in the examples 部分中。 要导航“向下”并获取 Tesla 的出生年份和 Pupin 的出生城市,我们使用以下命令 表达 式:spring-doc.cadn.net.cn

Java
// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
Kotlin
// evals to 1856
val year = parser.parseExpression("Birthdate.Year + 1900").getValue(context) as Int

val city = parser.parseExpression("placeOfBirth.City").getValue(context) as String

属性名称的首字母允许不区分大小写。的内容 数组和列表是使用方括号表示法获取的,如下例所示 显示:spring-doc.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<*>

{}本身意味着一个空列表。出于性能原因,如果列表本身是 完全由固定文本组成,创建一个常量列表来表示 expression (而不是在每次评估时构建一个新列表)。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<, *>

{:}本身意味着一张空地图。出于性能原因,如果映射本身是由 的固定文本或其他嵌套常量结构(列表或映射)中,将创建一个常量映射 来表示表达式(而不是在每次求值时构建新 map)。引用 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>

当前,在构造 Multi-dimensional 数组。spring-doc.cadn.net.cn

4.3.6. 方法

您可以使用典型的 Java 编程语法来调用方法。您还可以调用方法 在 Literals 上。还支持变量参数。以下示例说明如何 invoke 方法: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

关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 下面的清单显示了一些 Operators 示例: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),并且没有其他值永远小于 0 (X < null总是false).spring-doc.cadn.net.cn

如果您更喜欢数字比较,请避免使用基于数字的比较null比较 支持与 Zero 进行比较(例如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)
小心原始类型,因为它们会立即被装箱到 wrapper 类型。 所以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
赋值运算符

要设置属性,请使用赋值运算符 ()。这通常是 done 在对=setValue但也可以在对getValue.这 下面的清单显示了使用 ASSIGNMENT 运算符的两种方法: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( type) 的 intent 的 intent静态方法也是使用此运算符调用的。这StandardEvaluationContext使用TypeLocator查找类型,并使用StandardTypeLocator(可以替换)是在理解java.lang包。这意味着T()对 中的类型的引用java.lang不需要 完全限定,但所有其他类型的引用都必须是。以下示例显示了如何作 要使用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算子。您应该使用完全限定的类名 对于除基元类型 (int,float等)和 String 的 String。以下内容 示例演示如何使用newoperator 调用构造函数: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 add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);
Kotlin
val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)

//create new inventor instance within add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

4.3.10. 变量

您可以使用#variableName语法。变量 使用setVariablemethod 开启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变量

#thisvariable 始终被定义并引用当前评估对象 (根据这些引用解析不合格的引用)。这#rootvariable 始终 定义并引用根上下文对象。虽然#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. 函数

您可以通过注册可在 expression 字符串。该函数通过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 解析器,则可以 使用 symbol 从表达式中查找 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)

您可以使用三元运算符在 表达式。下面的清单显示了一个最小示例: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返回 String 值'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 运算符

Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须将变量重复两次,因为 以下示例显示:spring-doc.cadn.net.cn

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,您可以使用 Elvis 运算符(因与 Elvis 的发型相似而命名)。 以下示例演示如何使用 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}")

这将注入一个 system 属性pop3.port如果已定义,则为 25,如果未定义。spring-doc.cadn.net.cn

4.3.15. Safe Navigation作符

安全导航运算符用于避免NullPointerException并来自 Groovy 语言。通常,当您引用某个对象时,可能需要验证 在访问对象的方法或属性之前,它不为 null。为避免这种情况, Safe Navigation 运算符返回 null,而不是引发异常。以下内容 示例演示如何使用 Safe Navigation 运算符: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>

可以在列表和地图上进行选择。对于列表,选择 criteria 根据每个单独的 list 元素进行评估。对于地图, 根据每个映射条目(Java 类型的对象Map.Entry).每个映射条目都有其键和值,可作为属性访问 选择。spring-doc.cadn.net.cn

以下表达式返回一个由原始 map 的那些元素组成的新 map 其中 entry 值小于 27:spring-doc.cadn.net.cn

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

除了返回所有选定的元素外,您还可以仅检索 first 或 last 值。要获取与所选内容匹配的第一个条目,语法为.^[selectionExpression].要获取最后一个匹配的选择,语法为.$[selectionExpression].spring-doc.cadn.net.cn

4.3.17. 集合投影

Projection 允许集合驱动子表达式的计算,而 result 是一个新集合。projection 的语法是.![projectionExpression].为 例如,假设我们有一个发明人列表,但希望 他们出生的城市。实际上,我们想评估 'placeOfBirth.city' Inventor 列表中的每个条目。以下示例使用 projection 来执行此作: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<*>

您还可以使用地图来驱动投影,在这种情况下,投影表达式为 根据 Map 中的每个条目(表示为 JavaMap.Entry).结果 的 of a projection across a map 是一个列表,其中包含对投影的评估 expression 来触发每个 Map 条目。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.这ParserContextinterface 用于影响 解析表达式以支持表达式模板化功能。 的定义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

发明家.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 容器不依赖于 AOP(这意味着如果您不想,则不需要使用 AOP to),AOP 补充了 Spring IoC,提供了一个非常强大的中间件解决方案。spring-doc.cadn.net.cn

Spring AOP 与 AspectJ 切入点

Spring 通过使用基于 schema 的方法或 @AspectJ Comments 样式,提供了编写自定义切面的简单而强大的方法。 这两种样式都提供了完全类型的建议和 AspectJ 切入点语言的使用 同时仍然使用 Spring AOP 进行编织。spring-doc.cadn.net.cn

本章讨论基于 Schema 和 @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

  • Aspect:跨多个类的关注点的模块化。 事务管理是企业 Java 中横切关注点的一个很好的例子 应用。在 Spring AOP 中,切面是使用常规类实现的 (基于模式的方法)或用@Aspectannotation (@AspectJ 样式)。spring-doc.cadn.net.cn

  • 连接点:程序执行过程中的一个点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点始终是 表示方法执行。spring-doc.cadn.net.cn

  • Advice:一个 aspect 在特定连接点采取的行动。不同类型的 建议包括 “around”、“before” 和 “after” 建议。(讨论了建议类型 稍后。许多 AOP 框架,包括 Spring,将通知建模为拦截器,并且 在 Join Point 周围维护一个拦截器链。spring-doc.cadn.net.cn

  • 切入点:与连接点匹配的谓词。建议与 pointcut 表达式并在与该 pointcut 匹配的任何连接点(例如 执行具有特定名称的方法)。匹配连接点的概念 by pointcut 表达式是 AOP 的核心,而 Spring 使用 AspectJ pointcut 表达式语言。spring-doc.cadn.net.cn

  • 简介:代表类型声明其他方法或字段。Spring AOP 允许您将新接口(和相应的实现)引入到任何 advised 对象。例如,你可以使用 introduction 使 bean 实现一个IsModified接口,以简化缓存。(引言称为 inter-type 声明。spring-doc.cadn.net.cn

  • Target object:由一个或多个方面通知的对象。也称为 “Advised Object” 的 Visd 对象。由于 Spring AOP 是使用运行时代理实现的,因此 object 始终是代理对象。spring-doc.cadn.net.cn

  • AOP proxy:由 AOP 框架创建的对象,用于实现 aspect 合约(通知、方法执行等)。在 Spring 框架中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。spring-doc.cadn.net.cn

  • 编织:将方面与其他应用程序类型或对象链接起来,创建一个 advised 对象。这可以在编译时完成(使用 AspectJ 编译器,用于 example)、load time 或 runtime 中。Spring AOP 与其他纯 Java AOP 框架一样, 在运行时执行 weaving。spring-doc.cadn.net.cn

Spring AOP 包括以下类型的建议:spring-doc.cadn.net.cn

  • Before advice:在连接点之前运行但没有 防止执行流继续到连接点的能力(除非它抛出 一个例外)。spring-doc.cadn.net.cn

  • After returning advice:在连接点完成后运行的通知 通常(例如,如果方法返回而不引发异常)。spring-doc.cadn.net.cn

  • 抛出后通知:如果方法通过抛出 例外。spring-doc.cadn.net.cn

  • After (finally) advice:无论 连接点退出 (正常或异常返回)。spring-doc.cadn.net.cn

  • Around advice:围绕连接点的建议,例如方法调用。 这是最有力的建议。Around advice 可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续执行连接点,或者通过返回其 自己的返回值或引发异常。spring-doc.cadn.net.cn

围绕建议是最普遍的建议。从 Spring AOP 开始,就像 AspectJ 一样, 提供了全系列的建议类型,我们建议你使用最弱的 advice 类型。例如,如果您只需要 使用方法的返回值更新 Cache,则最好实现 返回 advice 后比 around 建议,虽然 around 建议可以完成 同样的事情。使用最具体的 Advice 类型提供更简单的编程模型 出错的可能性较小。例如,您不需要调用proceed()方法上的JoinPoint用于 around 建议,因此,您不能不调用它。spring-doc.cadn.net.cn

所有通知参数都是静态类型的,因此您可以使用 适当的类型(例如,方法执行的返回值的类型),而不是 比Object阵 列。spring-doc.cadn.net.cn

由切入点匹配的连接点的概念是 AOP 的关键,它区分了 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象的层次结构进行定位。例如,您可以应用 around advice 为一组跨 多个对象(例如服务层中的所有业务作)。spring-doc.cadn.net.cn

5.2. Spring AOP 的功能和目标

Spring AOP 是用纯 Java 实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适合在 Servlet 容器或应用程序服务器中使用。spring-doc.cadn.net.cn

Spring AOP 目前只支持方法执行连接点(通知执行 of 方法)。字段拦截未实现,尽管支持 可以在不破坏核心 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 容器结合使用。Aspect 是使用普通 bean 配置的 定义语法(尽管这允许强大的 “自动代理” 功能)。这是一个 与其他 AOP 实现的关键区别。你不能做一些事情 轻松或高效地使用 Spring AOP,例如 Advise 非常细粒度的对象(通常为 domain 对象)。在这种情况下,AspectJ 是最好的选择。但是,我们的 经验表明,Spring AOP 为大多数 适合 AOP 的 enterprise Java 应用程序。spring-doc.cadn.net.cn

Spring AOP 从不努力与 AspectJ 竞争以提供全面的 AOP 溶液。我们相信,基于代理的框架(如 Spring AOP)和成熟的 像 AspectJ 这样的框架很有价值,而且它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有使用都在一个一致的基于 Spring 的应用程序中的应用 架构。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参见下一章spring-doc.cadn.net.cn

Spring Framework 的核心原则之一是非侵入性。这 是不应强制引入特定于框架的类的想法, 接口连接到您的业务或域模型。但是,在某些地方, Spring 框架 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是因为,在某些情况下,它 可能只是更容易阅读或编写此类 一种方法。但是,Spring Framework(几乎)始终为您提供选择:您有 自由地决定哪种选项最适合您的特定用途 案例或场景。spring-doc.cadn.net.cn

与本章相关的一个选择是 AOP 框架(以及 which AOP style) 进行选择。您可以选择 AspectJ 和/或 Spring AOP。你 还可以选择 @AspectJ 注解样式方法或 Spring XML configuration-style 方法。本章选择介绍 @AspectJ 风格 First 的方法不应被视为 Spring 团队 更喜欢 @AspectJ Comments 样式的方法,而不是 Spring XML 配置样式。spring-doc.cadn.net.cn

有关“为什么和为什么”的更完整讨论,请参见选择要使用的 AOP 声明样式 每种样式。spring-doc.cadn.net.cn

5.3. AOP 代理

Spring AOP 默认使用标准 JDK 动态代理作为 AOP 代理。这 允许代理任何接口(或一组接口)。spring-doc.cadn.net.cn

Spring AOP 也可以使用 CGLIB 代理。这对于代理类而不是 接口。默认情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见的)情况下,您可以强制使用 CGLIB 需要通知未在接口上声明的方法,或者需要通知 将代理对象作为具体类型传递给方法。spring-doc.cadn.net.cn

掌握 Spring AOP 是基于代理的事实是很重要的。请参阅 Understanding AOP Proxies 以彻底检查它的具体内容 implementation detail 实际上是 Implementation。spring-doc.cadn.net.cn

5.4. @AspectJ 支持

@AspectJ 指的是一种将方面声明为常规 Java 类的样式,这些类带有 附注。@AspectJ 样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注解 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或 weaver。spring-doc.cadn.net.cn

使用 AspectJ 编译器和 weaver 可以使用完整的 AspectJ 语言和 在 Using AspectJ with Spring Applications中进行了讨论。

5.4.1. 启用 @AspectJ 支持

要在 Spring 配置中使用 @AspectJ 方面,您需要启用 Spring 对 基于 @AspectJ 方面配置 Spring AOP 并基于 无论他们是否受到这些方面的建议。自动代理是指,如果 Spring 确定 bean 由一个或多个 aspect 通知,则它会自动生成 该 bean 的代理,用于拦截方法调用并确保 Advice 运行 根据需要。spring-doc.cadn.net.cn

可以通过 XML 或 Java 样式的配置来启用 @AspectJ 支持。在任一 case 中,您还需要确保 AspectJ 的aspectjweaver.jarlibrary 位于 classpath 的 classpath (版本 1.8 或更高版本)。此库位于lib目录中的 Alpha 发行版的 Alpha 发行版中,或者从 Maven Central 存储库中。spring-doc.cadn.net.cn

使用 Java 配置启用 @AspectJ 支持

使用 Java 启用 @AspectJ 支持@Configuration,请添加@EnableAspectJAutoProxyannotation 中,如下例所示: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. 声明一个 Aspect

启用 @AspectJ 支持后,在应用程序上下文中定义的任何 bean 都会显示 类@AspectJ(具有@Aspectannotation) 会自动 由 Spring 检测到,并用于配置 Spring AOP。接下来的两个示例显示了 不是很有用的 aspect 所需的最小定义。spring-doc.cadn.net.cn

两个示例中的第一个示例显示了应用程序中的常规 bean 定义 context 指向具有@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

Aspects(用@Aspect) 可以具有方法和字段,与任何 其他类。它们还可以包含切入点、建议和引言(类型间) 声明。spring-doc.cadn.net.cn

通过组件扫描自动检测各个方面
你可以在 Spring XML 配置中将 aspect 类注册为常规 bean,或者 通过 Classpath 扫描自动检测它们——与任何其他 Spring 管理的 bean 相同。 但是,请注意,@Aspectannotation 不足以进行 类路径。为此,您需要添加单独的@Component注解 (或者,根据 Spring 的组件扫描仪)。
用其他方面提供建议?
在 Spring AOP 中,方面本身不能成为 advice 的目标 从其他方面来看。这@Aspect注解将其标记为一个 Aspect,并且 因此,将其排除在自动代理之外。

5.4.3. 声明切入点

切入点确定感兴趣的连接点,从而使我们能够控制 当 Advice 运行时。Spring AOP 仅支持 Spring 的方法执行连接点 beans,因此你可以将切入点视为与 Spring 上方法的执行相匹配 豆。切入点声明有两个部分:由 name 和 any 组成的签名 参数和精确确定方法的切入点表达式 我们感兴趣的执行。在 AOP 的 @AspectJ 注解样式中,一个切入点 signature 由常规方法定义提供,切入点表达式为 通过使用@Pointcut注释(用作切入点签名的方法 必须具有voidreturn 类型)。spring-doc.cadn.net.cn

一个示例可能有助于区分切入点签名和切入点 表达式 clear。以下示例定义了一个名为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

形成@Pointcutannotation 是常规的 AspectJ 5 切入点表达式。有关 AspectJ 的切入点语言的完整讨论,请参阅 AspectJ 编程指南(对于扩展,还有 AspectJ 5 Developer's Notebook)或关于 AspectJ 的书籍之一(例如 Colyer 编写的 Eclipse AspectJ 等。al.,或 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),其中执行对象的类具有给定类型的 Comments。spring-doc.cadn.net.cn

  • @args:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。spring-doc.cadn.net.cn

  • @within:限制匹配到具有给定 annotation(执行在具有给定注解的类型中声明的方法,当 使用 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 时,对 public 和 protected 方法调用 代理被拦截(如有必要,甚至是包可见的方法)。然而 通过代理的常见交互应始终通过公共签名进行设计。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 的名称。有限通配符 提供了使用该字符的支持,因此,如果您建立一些命名 约定,您可以编写一个*beanPCD 表达 以选择它们。与其他切入点指示符一样,beanPCD 罐 与 (and) 一起使用,&&||(或)和!(否定)运算符。spring-doc.cadn.net.cn

beanPCD 仅在 Spring AOP 中受支持,在 原生 AspectJ 编织。它是标准 PCD 的 Spring 特定扩展,它 AspectJ 定义了 并且 is not available for the aspect 声明的@Aspect型。spring-doc.cadn.net.cn

beanPCD 在实例级别运行(基于 Spring bean 名称 概念),而不仅仅是在类型级别(基于编织的 AOP 仅限于类型级别)。 基于实例的切入点指示符是 Spring 的 基于代理的 AOP 框架及其与 Spring bean 工厂的紧密集成,其中 按名称识别特定 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如果 Method Execution 连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在 Trading 模块中,则匹配。
3 tradingOperation如果方法执行表示 trading 模块。
Kotlin
@Pointcut("execution(public * *(..))")
private fun anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private fun inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} (3)
1 anyPublicOperation如果 Method Execution 连接点表示执行,则匹配 任何公共方法。
2 inTrading如果方法执行在 Trading 模块中,则匹配。
3 tradingOperation如果方法执行表示 trading 模块。

最佳实践是从较小的命名 组件,如前所述。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以看到相同类型的私有切入点,受保护的切入点位于 层次结构、任意位置的公共切入点等)。可见性不会影响切入点 匹配。spring-doc.cadn.net.cn

共享公共切入点定义

在使用企业应用程序时,开发人员通常希望引用 应用程序和特定作集从几个方面进行。我们 建议定义CommonPointcuts捕获常见切入点表达式的 aspect 为此目的。此类方面通常类似于以下示例: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() {
    }

}

你可以在任何需要 切入点表达式。例如,要使服务层具有事务性,您可以 写下以下内容: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>元素在基于 Schema 的 AOP 支持中进行了讨论。这 事务 Management 中讨论了事务元素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在前面的代码段中), Name pattern 和 parameters pattern 是可选的。返回的类型模式确定 方法的返回类型必须是什么才能匹配 Join Point。 最常用作返回类型模式。它与任何 return 匹配 类型。仅当方法返回给定的 类型。名称模式与方法名称匹配。您可以将通配符用作 all 或 名称模式的一部分。如果指定声明类型 pattern, 包括尾随**.将其连接到 Name pattern 组件。 parameters 模式稍微复杂一些:匹配 方法,而()(..)匹配任意数量的 (零个或多个) 参数。 该模式与采用任意类型一个参数的方法匹配。(*)(*,String)匹配采用两个参数的方法。第一个可以是任何类型,而 second 必须是String.查阅语言 Semantics 部分。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' 更常用于 binding 形式。有关如何在通知正文中使 proxy 对象可用的信息,请参见 Declaring Advice 部分。
  • 目标对象 实现AccountService接口:spring-doc.cadn.net.cn

        target(com.xyz.service.AccountService)
    'target' 更常用于 binding 形式。参见 Declaring Advice 部分 了解如何使目标对象在通知正文中可用。
  • 任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 其中,在运行时传递的参数为Serializable:spring-doc.cadn.net.cn

        args(java.io.Serializable)
    'args' 更常以绑定形式使用。参见 Declaring Advice 部分 了解如何使方法参数在 Advice Body 中可用。

    请注意,此示例中给出的切入点与execution(* *(java.io.Serializable)).如果在运行时传递的参数为Serializable,并且如果方法签名声明了单个 type 为Serializable.spring-doc.cadn.net.cn

  • 目标对象具有@Transactional注解:spring-doc.cadn.net.cn

        @target(org.springframework.transaction.annotation.Transactional)
    您也可以在装订形式中使用 '@target' 。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中 target 对象具有@Transactional注解:spring-doc.cadn.net.cn

        @within(org.springframework.transaction.annotation.Transactional)
    您还可以在装订形式中使用 '@within'。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法),其中执行方法具有@Transactional注解:spring-doc.cadn.net.cn

        @annotation(org.springframework.transaction.annotation.Transactional)
    您还可以在装订形式中使用 '@annotation'。参见 Declaring Advice 部分 了解如何使 Annotation 对象在通知正文中可用。
  • 任何连接点(仅在 Spring AOP 中执行方法)采用单个参数 其中,传递的参数的运行时类型具有@Classified注解:spring-doc.cadn.net.cn

        @args(com.xyz.security.Classified)
    您还可以在绑定形式中使用 '@args'。参见 Declaring Advice 部分 如何使 Annotation 对象在 Advice Body 中可用。
  • 名为tradeService:spring-doc.cadn.net.cn

        bean(tradeService)
  • Spring bean 上任何名称为 匹配通配符表达式*Service:spring-doc.cadn.net.cn

        bean(*Service)
编写好的切入点

在编译期间,AspectJ 处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态地)给定的切入点是一个昂贵的过程。(动态匹配是指匹配 无法从静态分析中完全确定,并且测试放置在代码中以 确定代码运行时是否存在实际匹配项)。首次遇到 pointcut 声明时,AspectJ 会将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用 DNF 重写的(析取 Normal Form) 和切入点的组件进行排序,以便这些组件 首先检查评估成本较低的 URL。这意味着您不必担心 关于了解各种切入点指示符的性能并可能提供它们 在切入点声明中以任何顺序。spring-doc.cadn.net.cn

然而,AspectJ 只能与它被告知的内容一起工作。为了获得最佳性能 匹配,您应该考虑他们想要实现的目标并缩小搜索范围 space for 在定义中尽可能匹配。现有标号 自然属于以下三组之一:kinded、scope 和 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 和 范围界定)。您可以包含上下文指示符以根据 join point context 或 bind 该上下文以在通知中使用。仅提供 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),由于额外的处理和分析。范围 标号的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地 关闭不应进一步处理的联接点组。一个好的 如果可能,切入点应始终包含一个。spring-doc.cadn.net.cn

5.4.4. 声明通知

Advice 与切入点表达式相关联,并在之前、之后或周围运行 与切入点匹配的方法执行。切入点表达式可以是 对命名切入点或就地声明的切入点表达式的简单引用。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() {
        // ...
    }
}
您可以有多个 advice 声明(以及其他成员), 都在同一个方面。在这些 examples 来集中每个 API 的效果。

有时,您需要在通知正文中访问返回的实际值。 您可以使用@AfterReturning,该 API 绑定返回值以获取 access,如下例所示: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) {
        // ...
    }
}

returningattribute 必须与参数的名称相对应 在 Advice 方法中。当方法执行返回时,返回值将传递给 将 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() {
        // ...
    }
}

通常,您希望通知仅在引发给定类型的异常时运行。 而且你还经常需要访问 Advice Body 中引发的异常。您可以 使用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) {
        // ...
    }
}

throwingattribute 必须与 建议方法。当方法执行通过引发异常退出时,异常 作为相应的参数值传递给通知方法。一个throwing第 还将匹配限制为仅那些抛出 指定类型 (DataAccessException,在本例中)。spring-doc.cadn.net.cn

请注意,@AfterThrowing不表示常规异常处理回调。 具体来说,@AfterThrowing通知方法只应该接收异常 从连接点(用户声明的 target 方法)本身,而不是从附带的@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() {
        // ...
    }
}

请注意,@After在 AspectJ 中,advice 被定义为“after finally advice”,类似于 添加到 try-catch 语句中的 finally 块。它将针对任何结果调用 从连接点(用户声明的目标方法)引发的正常返回或异常, 与@AfterReturning仅适用于成功的正常退货。spring-doc.cadn.net.cn

周边建议

最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行(例如,启动和停止计时器)。 始终使用满足您要求的最弱的建议形式(即 如果 Before advice 可以,请不要使用 around advice)。spring-doc.cadn.net.cn

Around 建议是使用@Around注解。第一个参数 advice method 的类型必须为ProceedingJoinPoint.在建议的正文中, 叫proceed()ProceedingJoinPoint导致底层方法运行。 这proceedmethod 也可以传入Object[].使用数组中的值 作为方法继续执行时的参数。spring-doc.cadn.net.cn

的行为proceed当使用Object[]与 的行为proceedfor around advice 由 AspectJ 编译器编译。对于周围 advice,传递给proceed必须与传递给 around 通知的参数数量匹配(而不是数量 的参数),并将传递给 continue 的值以 given argument position 替换实体联接点处的原始值 该值已绑定到 (如果现在没有意义,请不要担心)。方法 taken by Spring 更简单,并且更匹配其基于代理的、仅执行的 语义学。只有在编译 @AspectJ 时,您才需要注意这种差异 为 Spring 编写的方面和使用proceedwith arguments 替换为 AspectJ 编译器 和 weaver 一起。有一种方法可以编写这样的方面,它在两者之间 100% 兼容 Spring AOP 和 AspectJ,这将在下面关于通知参数的部分中讨论。

下面的示例展示了如何使用 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
    }
}

around 通知返回的值是 方法。例如,如果 有一个并且 invokeproceed()如果不是。请注意,proceed可以调用一次, 很多时候,或者根本不在周围的建议体内。所有这些都是合法的。spring-doc.cadn.net.cn

Advice 参数

Spring 提供了完全类型化的通知,这意味着您可以在 advice 签名(正如我们之前看到的 return 和 throw 示例)而不是 使用Object[]数组。我们了解如何进行论证和其他上下文 值可用于本节后面的通知正文。首先,我们来看看如何 编写 generic advice,可以了解该 Advice 当前建议的方法。spring-doc.cadn.net.cn

访问当前JoinPoint

任何通知方法都可以将 type 为org.aspectj.lang.JoinPoint(请注意,需要 Around Advice 才能声明第一个 type 为ProceedingJoinPoint,它是JoinPoint. 这JoinPointinterface 提供了许多有用的方法:spring-doc.cadn.net.cn

有关更多详细信息,请参阅 javadocspring-doc.cadn.net.cn

将参数传递给 Advice

我们已经看到了如何绑定返回值或异常值(使用 after 返回和抛出建议后)。使参数值可用于通知 body 中,你可以使用args.如果使用参数名称代替 type name 时,相应参数的值将作为 调用通知时的 parameter 值。一个例子应该更清楚地说明这一点。 假设您要建议执行采用Accountobject 作为第一个参数,您需要在 Advice Body 中访问该账户。 您可以编写以下内容: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,..)切入点表达式的一部分有两个用途。首先,它 将匹配限制为仅该方法至少采用一个 parameter 的 Alpha 参数,并且传递给该参数的参数是Account. 其次,它使实际的Account对象可通过account参数。spring-doc.cadn.net.cn

另一种写法是声明一个切入点,它 “提供”Accountobject 值,然后引用命名的切入点 来自建议。这将如下所示: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 参数键入要拦截方法的 parameter type: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

确定参数名称

通知调用中的参数绑定依赖于切入点中使用的匹配名称 Advice 和 PointCut 方法签名中声明的参数名称的表达式。 参数名称不能通过 Java 反射获得,因此 Spring AOP 使用 以下策略来确定参数名称:spring-doc.cadn.net.cn

  • 如果用户已显式指定参数名称,则指定的 参数名称。建议和切入点注释都有 可选的argNames属性,可用于指定 带注释的方法。这些参数名称在运行时可用。以下示例 演示如何使用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.StaticParttype 中,您可以从值 的argNames属性。例如,如果您将前面的通知修改为接收 连接点对象、argNamesattribute 不需要包含它: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.StaticParttypes 特别方便 不收集任何其他 Join Point Context 的 Advice 实例。在这种情况下,您可以 省略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
}
  • 使用'argNames'属性有点笨拙,所以如果'argNames'属性 未指定,则 Spring AOP 会查看 类,并尝试从局部变量表中确定参数名称。这 只要类已使用 debug 编译,信息就存在 信息 ('-g:vars'至少)。使用此标志进行编译的后果 上是:(1) 你的代码稍微更容易理解(逆向工程),(2) 类文件大小略大(通常无关紧要),(3) 编译器不会应用用于删除未使用的局部变量的优化。在 换句话说,使用此标志进行构建应该不会遇到任何困难。spring-doc.cadn.net.cn

    如果 @AspectJ 方面已经由 AspectJ 编译器 (ajc) 编译,即使没有 debug 信息,则无需添加argNames属性作为编译器 保留所需的信息。
  • 如果代码在编译时没有必要的调试信息,则 Spring AOP 尝试推断绑定变量与参数的配对(例如,如果 切入点表达式中只绑定了一个变量,而 Advice 方法 只接受一个参数,配对是显而易见的)。如果变量的绑定是 ambiguous 给定可用信息,则AmbiguousBindingException是 扔。spring-doc.cadn.net.cn

  • 如果上述策略都失败了,则IllegalArgumentException被抛出。spring-doc.cadn.net.cn

继续参数

我们之前说过,我们将描述如何编写proceed调用 在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是 以确保 Advice 签名按顺序绑定每个 Method 参数。 以下示例显示了如何执行此作: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 相同的优先规则来确定通知的顺序 执行。最高优先级的建议首先运行“在途中”(因此,给定两个部分 of before 建议,则优先级最高的那个先运行)。“On the way out” 从 join point 时,最高优先级 advice 将运行在 last 之后(因此,给定两个 after advice 的 Advice 中,优先级最高的 ID 将排在第二位)。spring-doc.cadn.net.cn

当在不同方面定义的两条通知都需要在同一条下运行时 join point 的执行顺序,除非你另有指定,否则执行顺序是 undefined。您可以 通过指定 Precedence 来控制执行顺序。这是在正常情况下完成的 Spring 方式,方法是实现org.springframework.core.Ordered接口输入 aspect 类或使用@Order注解。给定两个方面, aspect 返回Ordered.getOrder()(或 annotation 值)具有 优先级更高。spring-doc.cadn.net.cn

特定方面的每种不同的建议类型在概念上都是要适用的 直接连接到连接点。因此,@AfterThrowing建议方法不是 应该从随附的@After/@AfterReturning方法。spring-doc.cadn.net.cn

从 Spring Framework 5.2.7 开始,在相同的@Aspect类 需要在同一连接点运行,则根据它们在 以下顺序,从最高优先级到最低优先级:@Around,@Before,@After,@AfterReturning,@AfterThrowing.但是请注意,一个@Afteradvice 方法将 有效地在任何@AfterReturning@AfterThrowing建议方法 在同一个方面,遵循 AspectJ 的 “after finally advice” 语义@After.spring-doc.cadn.net.cn

当两条相同类型的建议(例如,两个@After建议方法) 定义在@Aspectclass 都需要在同一个连接点运行,则 Ordering 未定义(因为无法通过 Reflection for JavaC 编译的类)。考虑将这些 advice 方法合并为一个 每个 join point 中每个 join 点的通知方法@Aspect类或将建议重构为 分开@Aspect您可以通过Ordered@Order.spring-doc.cadn.net.cn

5.4.5. 简介

介绍(在 AspectJ 中称为类型间声明)使一个 aspect 能够声明 ,建议对象实现给定的接口,并且要提供 该接口代表这些对象。spring-doc.cadn.net.cn

您可以使用@DeclareParents注解。此批注 用于声明匹配类型具有新的父级(因此得名)。例如 给定一个名为UsageTracked以及名为DefaultUsageTracked,以下方面声明 service 的所有 implementationrs 接口还实现了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属性的@DeclareParentsannotation 是一种 AspectJ 类型的模式。任何 bean 的 bean 实现UsageTracked接口。请注意,在 在前面的示例的 advice 之前,服务 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. Aspect 实例化模型

这是一个高级主题。如果您刚开始使用 AOP,则可以安全地跳过 它直到以后。

默认情况下,应用程序中的每个方面都有一个实例 上下文。AspectJ 称其为单例实例化模型。可以定义 具有替代生命周期的 aspects。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子句是 1 个 aspect 实例 为执行业务服务的每个唯一服务对象创建(每个唯一的 对象绑定到this在与切入点表达式匹配的连接点处)。坡向 实例是在第一次对 Service 对象调用方法时创建的。这 当 Service 对象超出范围时,aspect 超出范围。aspect 之前 实例,则其中的任何 Advice 都不会运行。一旦 aspect 实例 已创建,则其中声明的通知将在匹配的连接点运行,但只有 当 Service 对象是与此 aspect 关联的对象时。请参阅 AspectJ 编程指南,了解更多信息per第。spring-doc.cadn.net.cn

pertarget实例化模型的工作方式与perthis,但它 在匹配的连接点处为每个唯一的目标对象创建一个 aspect 实例。spring-doc.cadn.net.cn

5.4.7. AOP 示例

现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起进行 一些有用的东西。spring-doc.cadn.net.cn

业务服务的执行有时会由于并发问题(对于 例如,一个死锁失败者)。如果重试作,则可能会成功 下次尝试时。对于适合在此类 条件(无需因冲突而返回给用户的幂等作 resolution)中,我们希望透明地重试该作,以避免客户端看到PessimisticLockingFailureException.这是一个明确贯穿的要求 服务层中的多个服务,因此非常适合通过 方面。spring-doc.cadn.net.cn

因为我们想要重试作,所以我们需要使用 around advice,以便我们可以 叫proceed多次。下面的清单显示了基本的 aspect 实现: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
    }
}

请注意,该 aspect 实现了Ordered接口,以便我们可以设置 方面高于 Transaction Advice(我们希望每次 retry)。这maxRetriesorderproperties 都是由 Spring 配置的。这 main作发生在doConcurrentOperation周围建议。请注意,对于 矩,我们将重试逻辑应用于每个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>

为了优化 aspect 以便它仅重试幂等作,我们可以定义以下内容Idempotent注解:spring-doc.cadn.net.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation

然后,我们可以使用 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. 基于 Schema 的 AOP 支持

如果您更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持 使用aopnamespace 标签。完全相同的切入点表达式和通知类型 与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注 该语法,并让读者参考上一节中的讨论 (@AspectJ支持)用于理解编写切入点表达式和绑定 的建议参数。spring-doc.cadn.net.cn

要使用本节中描述的 aop 命名空间标签,您需要导入spring-aopschema,如 XML 基于 Schema-based configuration 中所述。请参阅 AOP 架构,了解如何在aopNamespace。spring-doc.cadn.net.cn

在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在 一<aop:config>元素(您可以有多个<aop:config>元素中 应用程序上下文配置)。一<aop:config>元素可以包含 pointcut、 advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。spring-doc.cadn.net.cn

<aop:config>样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议 not being woven)如果您已经通过使用BeanNameAutoProxyCreator或类似的东西。建议的使用模式是 仅使用<aop:config>样式或仅AutoProxyCreatorstyle 和 切勿混合使用它们。

5.5.1. 声明一个 Aspect

当您使用 schema 支持时,aspect 是定义为 您的 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>

支持 aspect (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>

假设您有一个CommonPointcutsaspect 中的定义,如共享公共切入点定义中所述。spring-doc.cadn.net.cn

那么在一个 aspect 中声明一个切入点与声明一个顶级切入点非常相似。 如下例所示: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 方面大致相同,使用基于 schema 的 schema 声明的切入点 定义样式可以收集连接点上下文。例如,以下切入点 收集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. 声明 Advice

基于 schema 的 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),它提供 建议。必须为 aspect 元素引用的 bean 定义此方法 其中包含建议。在执行数据访问作之前(方法执行 连接点匹配),则doAccessCheckaspect 上的 method 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 样式一样,您可以在 Advice Body 中获取返回值。 为此,请使用returning属性来指定 应传递 return 值,如下例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...
</aop:aspect>

doAccessCheckmethod 必须声明一个名为retVal.此 type of this parameter 约束匹配,其方式与@AfterReturning.为 example,您可以按如下方式声明 Method Signature: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 样式一样,您可以在通知正文中获取 thrown 异常。 为此,请使用throwingattribute 来指定参数的名称 应传递异常,如下例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

doRecoveryActionsmethod 必须声明一个名为dataAccessEx. 此参数的类型以与@AfterThrowing.例如,方法签名可以按如下方式声明:spring-doc.cadn.net.cn

Java
public void doRecoveryActions(DataAccessException dataAccessEx) {...
Kotlin
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议

After (finally) 通知运行,无论匹配的方法执行如何退出。 您可以使用after元素,如下例所示:spring-doc.cadn.net.cn

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...
</aop:aspect>
周边建议

最后一种建议是围绕建议。Around 建议 “around” 匹配的方法 执行。它有机会在方法运行之前和之后执行工作 并确定该方法何时、如何甚至是否真正开始运行。 Around 通知通常用于在 线程安全的方式(例如,启动和停止计时器)。始终使用最少的 满足您要求的强大建议形式。如果出现以下情况,请勿在建议周围使用 之前 建议 可以 完成 这项工作。spring-doc.cadn.net.cn

你可以使用aop:around元素。的第一个参数 通知方法的类型必须为ProceedingJoinPoint.在建议的正文中, 叫proceed()ProceedingJoinPoint导致底层方法运行。 这proceed方法也可以使用Object[].数组中的值 在方法执行过程中用作方法执行的参数。 参见 Around Advice 了解打电话的注意事项proceed替换为Object[]. 下面的示例展示了如何在 XML 中声明 around advice:spring-doc.cadn.net.cn

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...
</aop:aspect>

doBasicProfilingadvice 可以与 @AspectJ示例(当然不包括 Annotation),如下例所示: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()
}
Advice 参数

基于 schema 的声明样式支持完全类型化的通知,其方式与 针对 @AspectJ 支持进行描述 — 通过按名称将切入点参数与 通知方法参数。有关详细信息,请参阅 Advice Parameters 。如果您愿意 要显式指定通知方法的参数名称(不依赖于 检测策略),您可以使用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-namesattribute 接受以逗号分隔的参数名称列表。spring-doc.cadn.net.cn

以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用: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(..)method 接受多个 强类型参数,其中第一个参数恰好是用于 继续执行 method 调用。此参数的存在表明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)
建议订购

当多个通知需要在同一个连接点运行时(执行方法) 排序规则如 Advice Ordering 中所述。优先权 aspect 之间的order属性中的<aop:aspect>元素或 通过添加@Order注解添加到支持该 aspect 的 bean 中,或者通过使用 Bean 实现Ordered接口。spring-doc.cadn.net.cn

与同一中定义的通知方法的优先规则相反@Aspect类中,当两条建议定义在同一个<aop:aspect>元素都需要 run 相同的 join point 时,优先级由通知的顺序决定 元素在 enclosing 中声明<aop:aspect>元素,从最高到最低 优先。spring-doc.cadn.net.cn

例如,给定一个aroundadvice 和before建议<aop:aspect>元素,以确保aroundadvice 的优先级高于before建议、<aop:around>元素必须为 在<aop:before>元素。spring-doc.cadn.net.cn

作为一般的经验法则,如果您发现您定义了多条建议 在同一<aop:aspect>元素,请考虑折叠 此类 Advice 方法转换为每个连接点的 1 个 Advice Method<aop:aspect>元素 或将建议重构为单独的<aop:aspect>您可以订购的元素 在 aspect 级别。spring-doc.cadn.net.cn

5.5.4. 简介

介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。spring-doc.cadn.net.cn

您可以使用aop:declare-parents元素中aop:aspect. 您可以使用aop:declare-parents元素来声明匹配的类型具有新的父级(因此得名)。 例如,给定一个名为UsageTracked以及名为DefaultUsageTracked,以下方面声明 service 的所有 implementationrs 接口还实现了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-matchingattribute 是一个 AspectJ 类型的模式。任何 bean 的 matching 类型实现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.5.5. 方面实例化模型

模式定义的方面唯一支持的实例化模型是 singleton 型。将来的版本可能支持其他实例化模型。spring-doc.cadn.net.cn

5.5.6. 顾问

“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。spring-doc.cadn.net.cn

Spring 通过<aop:advisor>元素。你最 通常看到它与 transactional advice 结合使用,后者也有自己的 namespace 支持。以下示例显示了一个 advisor: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

要定义 advisor 的优先级,以便 advice 可以参与排序, 使用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 advice,以便我们可以 叫proceed多次。下面的清单显示了基本的 aspect 实现 (这是一个使用 schema 支持的常规 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
    }
}

请注意,该 aspect 实现了Ordered接口,以便我们可以设置 方面高于 Transaction Advice(我们希望每次 retry)。这maxRetriesorderproperties 都是由 Spring 配置的。这 main作发生在doConcurrentOperation围绕 Advice 方法。我们尝试 进行。如果我们失败时出现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>

请注意,目前,我们假设所有业务服务都是幂等的。如果 事实并非如此,我们可以优化 Aspect 以便它只真正重试 幂等作,通过引入Idempotentannotation 和使用注释 对 Service作的实现进行注释,如下例所示:spring-doc.cadn.net.cn

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // marker annotation
}

这 将 aspect 更改为仅重试幂等作涉及优化 pointcut 表达式,以便仅@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 声明样式

一旦您确定某个 aspect 是实现给定 需求,你如何决定是使用 Spring AOP 还是 AspectJ 以及 方面语言(代码)样式、@AspectJ 注释样式还是 Spring XML 样式?这些 决策受多种因素影响,包括应用程序要求、 开发工具,以及团队对 AOP 的熟悉程度。spring-doc.cadn.net.cn

5.6.1. Spring AOP 还是完整的 AspectJ?

使用最简单的方法。Spring AOP 比使用完整的 AspectJ 更简单,因为 不需要将 AspectJ 编译器/编织器引入到你的开发中 并构建流程。如果只需要在 Spring 上通知作的执行 beans 中,Spring AOP 是正确的选择。如果您需要通知 不受 管理 的对象 Spring 容器(通常例如域对象)中,您需要使用 AspectJ 的如果你希望通知 join points 而不是 简单的方法执行(例如,字段 Get 或 Set 连接点等)。spring-doc.cadn.net.cn

当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,则已为您做出选择:使用代码样式。如果 aspect 播放 large 角色,并且您可以使用 AspectJ Development Tools (AJDT) 插件,AspectJ 语言语法是 首选选项。它更简洁、更简单,因为该语言是有意为之的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中没有主要作用,您可能需要考虑使用 @AspectJ样式,在 IDE 中坚持使用常规 Java 编译,并在 IDE 中添加 构建脚本的 aspect 编织阶段。spring-doc.cadn.net.cn

5.6.2. Spring AOP 的 @AspectJ 还是 XML?

如果您选择使用 Spring AOP,则可以选择 @AspectJ 或 XML 样式。 需要考虑各种权衡。spring-doc.cadn.net.cn

XML 样式对于现有的 Spring 用户来说可能是最熟悉的,并且它由真正的 POJO 的。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个很好的 choice(一个很好的测试是,您是否认为 pointcut 表达式是 配置)。对于 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。spring-doc.cadn.net.cn

XML 样式有两个缺点。首先,它没有完全封装 它在一个地方实现它所解决的需求。DRY 原则说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解需求如何 的实现被拆分到支持 Bean 类的声明和 XML 中 配置文件。当您使用 @AspectJ 样式时,此信息将被封装 在单个模块中:Aspect。其次,XML 样式在哪些方面稍受限制 它能表达的比@AspectJ风格:只有 “singleton” 方面实例化模型 ,并且无法组合在 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 风格来自定义 aspects,而不仅仅是简单的 企业服务的配置。spring-doc.cadn.net.cn

5.7. 混合 aspect 类型

通过使用自动代理支持,完全可以混合@AspectJ样式方面, 架构定义<aop:aspect>方面<aop:advisor>声明的顾问,甚至代理 以及相同配置中其他样式的拦截器。所有这些都已实现 通过使用相同的底层支持机制,可以毫无困难地共存。spring-doc.cadn.net.cn

5.8. 代理机制

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的 target 对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是一种常见的 开源类定义库(重新打包为spring-core).spring-doc.cadn.net.cn

如果要代理的目标对象实现了至少一个接口,则 JDK 动态 proxy 的 Proxy 的 S S S T目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则会创建一个 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 执行 不允许绕过构造函数,则可能会看到 double invocations 和 来自 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/>部分折叠到单个统一的 Auto-Proxy Creator 中 在运行时,它会应用任何<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 框架提供的基于 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 Call
Java
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}
Kotlin
fun main() {
    val pojo = SimplePojo()
    // this is a direct method call on the 'pojo' reference
    pojo.foo()
}

当客户端代码具有的引用是代理时,情况会略有变化。考虑一下 下图和代码片段如下:spring-doc.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(..)方法 的Mainclass 具有对代理的引用。这意味着该方法调用 对象引用是对代理的调用。因此,代理可以将 与该特定方法调用相关的拦截器 (通知)。然而 调用最终到达目标对象(SimplePojo引用 )中,它可能对自身进行的任何方法调用,例如this.bar()this.foo()将针对this引用,而不是代理。 这具有重要意义。这意味着 self-invocation 不会产生 在与方法调用关联的建议中,获得运行的机会。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. 在 Spring 应用程序中使用 AspectJ

到目前为止,我们在本章中介绍的所有内容都是纯粹的 Spring AOP。在本节中, 我们看看如何使用 AspectJ 编译器或 weaver 来代替 OR IN 如果您的需求超出了 Spring AOP 提供的功能,则可添加到 Spring AOP 中 独自。spring-doc.cadn.net.cn

Spring 附带了一个小型的 AspectJ 方面库,该库可在 distribution 为spring-aspects.jar.您需要按顺序将其添加到 Classpath 中 以使用其中的 aspects。使用 AspectJ 对 Spring 和 Spring 的其他 Spring 方面进行依赖注入 Domain Objects 和 AspectJ 讨论 此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面 讨论了如何 dependency 注入使用 AspectJ 编译器编织的 AspectJ 切面。最后,Spring 框架中使用 AspectJ 的加载时编织 提供了对 Spring 应用程序的加载时编织的介绍 使用 AspectJ。spring-doc.cadn.net.cn

5.10.1. 使用 AspectJ 对 Spring 的域对象进行依赖注入

Spring 容器实例化并配置应用程序中定义的 bean 上下文。也可以要求 bean factory 配置预先存在的 object,给定包含要应用的配置的 bean 定义的名称。spring-aspects.jar包含一个 annotation-driven 切面,该 aspect 利用此 允许对任何对象进行依赖关系注入的能力。该支持旨在 用于在任何容器的控制之外创建的对象。域对象 通常属于这一类,因为它们通常是使用new运算符,或者通过 ORM 工具作为数据库查询的结果。spring-doc.cadn.net.cn

@Configurableannotation 将类标记为符合 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 定义的名称,则 可以直接在 Comments 中执行此作,如下例所示: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并将其用作 定义以配置新的Account实例。spring-doc.cadn.net.cn

您还可以使用自动装配来避免在 都。要让 Spring 应用自动装配,请使用autowire属性的@Configurable注解。您可以指定@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME对于 按类型或按名称自动装配, 分别。作为替代方案,最好指定显式的、注解驱动的 依赖项注入@Configurablebeans 通过@Autowired@Inject在字段或方法级别(有关更多详细信息,请参阅基于注释的容器配置)。spring-doc.cadn.net.cn

最后,您可以为新的 created 和 configured 对象dependencyCheck属性(例如@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)).如果此属性为 设置为true,Spring 在配置后验证所有属性(其 不是基元或集合)的spring-doc.cadn.net.cn

请注意,单独使用 Comments 不会执行任何作。它是AnnotationBeanConfigurerAspectspring-aspects.jar作用于 注释。从本质上讲,该 aspect 表示,“从 一个带有 Comments 的类型的新对象@Configurable中,配置新创建的对象 根据注释的属性使用 Spring”。在此上下文中, “initialization” 是指新实例化的对象(例如,实例化的对象 使用new运算符)以及Serializable正在经历的对象 反序列化(例如,通过 readResolve())。spring-doc.cadn.net.cn

上一段中的关键词之一是“本质上”。在大多数情况下, “After returning from the initialization of a new object” 的确切语义是 好。在这种情况下,“初始化后”意味着依赖项是 在构造对象后注入。这意味着依赖项 不能在类的构造函数主体中使用。如果您希望 依赖项,以便在构造函数主体运行之前注入,因此 ,您需要在@Configurable声明,如下所示:spring-doc.cadn.net.cn

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

您可以找到有关各种切入点的语言语义的更多信息 类型 AspectJ 的附录 编程指南spring-doc.cadn.net.cn

为此,必须用 AspectJ weaver 编织带注释的类型。您可以 使用构建时 Ant 或 Maven 任务来执行此作(例如,参见 AspectJ 开发 Environment Guide)或加载时编织(参见 Spring 框架中使用 AspectJ 的加载时编织)。这AnnotationBeanConfigurerAspect本身需要由 Spring 进行配置(为了获得 对用于配置新对象的 Bean Factory 的引用)。如果你 使用基于 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在配置 aspect 之前创建的对象 导致向调试日志发出一条消息,并且没有配置 对象正在发生。一个例子可能是 Spring 配置中的一个 bean,它创建 domain 对象。在这种情况下,您可以使用depends-onbean 属性手动指定 bean 依赖于 configuration 方面。以下示例演示如何使用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 编织了, 您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告 message@Configurable对象,表示它具有 未由 Spring 配置。spring-doc.cadn.net.cn

使用多个应用程序上下文

AnnotationBeanConfigurerAspect用于实现@Configurable支持 是 AspectJ 单例 aspect。单例 aspect 的 scope 与 scope 相同 之staticmembers:每个 classloader 都有一个定义类型的 aspect 实例。 这意味着,如果在同一类加载器中定义多个应用程序上下文 层次结构,您需要考虑在何处定义@EnableSpringConfiguredbean 和 放置位置spring-aspects.jar在 Classpath 上。spring-doc.cadn.net.cn

考虑一个典型的 Spring Web 应用程序配置,该配置具有共享的父应用程序 定义常见业务服务的上下文,支持这些服务所需的一切, 以及每个 servlet 的一个子应用程序上下文(其中包含特定于 添加到该 Servlet 中)。所有这些上下文都共存于同一个 classloader 层次结构中, 因此,AnnotationBeanConfigurerAspect只能保存对其中一个的引用。 在这种情况下,我们建议定义@EnableSpringConfigured共享的 bean (父)应用程序上下文。这定义了你可能想要的服务 inject 到 domain objects 中。结果是您无法配置域对象 引用子(特定于 servlet)上下文中定义的 bean,方法是使用 @Configurable机制(这可能不是你想做的事情)。spring-doc.cadn.net.cn

在同一容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序将类型加载到spring-aspects.jar通过使用自己的 Classloader (例如,通过将spring-aspects.jar'WEB-INF/lib').如果spring-aspects.jar仅添加到容器范围的 Classpath 中(因此由共享父级加载 classloader),所有 Web 应用程序共享相同的 aspect 实例(可能是 不是你想要的)。spring-doc.cadn.net.cn

5.10.2. AspectJ 的其他 Spring 方面

除了@Configurable方面spring-aspects.jar包含一个 AspectJ 方面,您可以使用它来驱动 Spring 的类型和方法的事务管理 用@Transactional注解。这主要适用于满足以下条件的用户 希望在 Spring 容器之外使用 Spring Framework 的事务支持。spring-doc.cadn.net.cn

解释@Transactionalannotations 是AnnotationTransactionAspect.当您使用此 aspect 时,您必须注释 implementation 类(或该类中的方法,或两者),而不是接口(如果 any) 实现。AspectJ 遵循 Java 的规则,即 接口不是继承的。spring-doc.cadn.net.cn

一个@Transactional类的注解指定了 类中任何公共作的执行。spring-doc.cadn.net.cn

一个@Transactional类中方法的注释将覆盖默认值 类注解(如果存在)给出的交易语义。任何 visibility 可以被注释,包括 private methods。对非公共方法进行注解 directly 是获取执行此类方法的事务划分的唯一方法。spring-doc.cadn.net.cn

从 Spring Framework 4.2 开始,spring-aspects提供了一个类似的方面,该方面提供了 与标准完全相同的功能javax.transaction.Transactional注解。检查JtaAnnotationTransactionAspect了解更多详情。

对于想要使用 Spring 配置和事务的 AspectJ 程序员 管理支持,但不想(或不能)使用注解,spring-aspects.jar还包含abstract您可以扩展的 aspects 以提供您自己的切入点 定义。请参阅AbstractBeanConfigurerAspectAbstractTransactionAspectaspects 了解更多信息。例如,以下 Excerpt 展示了如何编写一个 aspect 来配置对象的所有实例 使用与 完全限定的类名: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 方面

当你在 Spring 应用程序中使用 AspectJ 方面时,很自然地需要和 期望能够使用 Spring 配置这些方面。AspectJ 运行时本身是 负责 aspect 的创建,以及配置 AspectJ 创建的 方面依赖于 AspectJ 实例化模型(per-xxx子句) 由 aspect 使用。spring-doc.cadn.net.cn

大多数 AspectJ 方面都是单例方面。这些的配置 方面很容易。您可以创建一个引用 aspect 类型的 bean 定义 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配置 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 代理。aspect 的 @AspectJ 样式 声明,但不涉及 AspectJ 运行时。

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

加载时编织 (LTW) 是指将 AspectJ 方面编织成 应用程序的类文件,因为它们正在加载到 Java 虚拟机 (JVM) 中。 本节的重点是在 Spring 框架。本节不是 LTW 的一般介绍。有关完整详细信息 LTW 的细节和仅使用 AspectJ 配置 LTW(Spring 不是 involved 的 lw),参见 AspectJ 的 LTW 部分 开发环境指南spring-doc.cadn.net.cn

Spring 框架为 AspectJ LTW 带来的价值在于支持很多 对编织过程进行更精细的控制。'Vanilla' AspectJ LTW 通过使用 Java (5+) 代理,在启动 JVM 的 JVM 中。因此,它是一个 JVM 范围的设置,在某些情况下可能很好,但通常是一个 有点太粗糙了。启用 Spring 的 LTW 允许您在 每-ClassLoader基础,它更细粒度,并且可以使 在“single-JVM-multiple-application”环境中(例如在典型的 应用程序服务器环境)。spring-doc.cadn.net.cn

此外,在某些环境中,此支持支持 加载时编织,而无需对 Application Server 的启动进行任何修改 需要添加的脚本-javaagent:path/to/aspectjweaver.jar或(正如我们所描述的 本节稍后部分)-javaagent:path/to/spring-instrument.jar.开发人员配置 应用程序上下文,用于启用加载时编织,而不是依赖管理员 通常负责部署配置(如 Launch Script)的人员。spring-doc.cadn.net.cn

现在销售宣传已经结束,让我们首先看一个 AspectJ 的快速示例 LTW,后跟有关 例。有关完整示例,请参阅 Petclinic 示例应用程序spring-doc.cadn.net.cn

第一个例子

假设您是一名应用程序开发人员,其任务是诊断 系统中某些性能问题的原因。而不是分解 profiling 工具中,我们将打开一个简单的性能分析方面,它允许我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具复制到该特定区域。spring-doc.cadn.net.cn

此处显示的示例使用 XML 配置。您还可以配置和 将 @AspectJ 与 Java 配置结合使用。具体来说,您可以使用@EnableLoadTimeWeaving注解作为<context:load-time-weaver/>(有关详细信息,请参见下文)。

下面的示例显示了性能分析方面,这并不花哨。 它是一个基于时间的分析器,它使用 @AspectJ 样式的 aspect 声明: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 weaver 我们想编织我们的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(稍后解释)。此加载时 weaver 是 essential 组件,负责将 aspect 配置编织到一个 OR 更多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 配置)就位,我们可以创建以下内容 driver 类替换为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()
}

我们还有最后一件事要做。本节的引言确实说过,人们可以 有选择地在 per- 上打开 LTWClassLoaderbasis 替换为 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作为-javaagentargument 在 前面的示例。spring-doc.cadn.net.cn

执行Mainprogram 类似于下一个示例。 (我引入了一个Thread.sleep(..)语句添加到calculateEntitlement()实现,以便分析器实际捕获 0 以外的内容 毫秒(01234milliseconds 不是 AOP 引入的开销)。 下面的清单显示了我们在运行 profiler 时得到的输出:spring-doc.cadn.net.cn

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于这个 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于提供建议 春豆。以下对Mainprogram 生成相同的 结果: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 中使用的 aspect 必须是 AspectJ 方面。您可以将它们写入 要么是 AspectJ 语言本身,要么你可以用 @AspectJ 风格编写你的 Aspects。 因此,你的 aspect 都是有效的 AspectJ 和 Spring AOP 方面。 此外,编译后的 aspect 类需要在 Classpath 上可用。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

必需的库 (JAR)

至少,您需要以下库才能使用 Spring Framework 的支持 对于 AspectJ LTW:spring-doc.cadn.net.cn

Spring 配置

Spring 的 LTW 支持中的关键组件是LoadTimeWeaver界面(在org.springframework.instrument.classloading包)和众多实现 的 Spring 发行版。一个LoadTimeWeaver负责 添加一个或多个java.lang.instrument.ClassFileTransformers更改为ClassLoader在 runtime 的 runtime,它为各种有趣的应用程序打开了大门,其中之一就是 恰好是 aspects 的 LTW。spring-doc.cadn.net.cn

如果您不熟悉运行时类文件转换的概念,请参阅 javadoc API 文档java.lang.instrument包,然后再继续。 虽然该文档并不全面,但至少您可以看到关键界面 和 classes (供你阅读本节时参考)。

配置LoadTimeWeaver对于特定的ApplicationContext可以像 添加一行。(请注意,您几乎肯定需要使用ApplicationContext作为您的 Spring 容器 — 通常为BeanFactory莫 足够了,因为 LTW 支持使用BeanFactoryPostProcessors.)spring-doc.cadn.net.cn

要启用 Spring 框架的 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,例如LoadTimeWeaver以及一个AspectJWeavingEnabler给你的。 默认的LoadTimeWeaverDefaultContextLoadTimeWeaver类,该类会尝试 以装饰自动检测到的LoadTimeWeaver.的确切类型LoadTimeWeaver即 “Automatically detected” 取决于您的运行时环境。 下表总结了各种LoadTimeWeaver实现:spring-doc.cadn.net.cn

表 13.DefaultContextLoadTimeWeaver LoadTimeWeaver
运行时环境 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 始于 SpringInstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)spring-doc.cadn.net.cn

InstrumentationLoadTimeWeaverspring-doc.cadn.net.cn

Fallback,期望底层 ClassLoader 遵循通用约定 (即addTransformer和可选的getThrowawayClassLoader方法)spring-doc.cadn.net.cn

ReflectiveLoadTimeWeaverspring-doc.cadn.net.cn

请注意,该表仅列出了LoadTimeWeavers当您 使用DefaultContextLoadTimeWeaver.您可以准确指定LoadTimeWeaverimplementation 来使用。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 的机制存在 infrastructure 添加一个或多个ClassFileTransformers.实际的ClassFileTransformerLTW 是ClassPreProcessorAgentAdapter(来自 这org.aspectj.weaver.loadtimepackage) 类。请参阅ClassPreProcessorAgentAdapter类了解更多详细信息,因为具体作 编织实际发生超出了本文档的范围。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 会根据需要在 load 时进行编织。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 weaving 开启。否则,它处于关闭状态。这是默认值。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的 native LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。 如前所述,您只需启用 load-time weaving 具体来说,您无需修改 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、自动检测 按普通@EnableLoadTimeWeaving<context:load-time-weaver/>设置。spring-doc.cadn.net.cn

要使用它,您必须通过提供 Spring 代理来启动虚拟机 以下 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

Eclipse AspectJ 作者:Adrian Colyer etal. (Addison-Wesley, 2005) 提供了一个 AspectJ 语言的全面介绍和参考。spring-doc.cadn.net.cn

AspectJ in Action, Second Edition 作者:Ramnivas Laddad(Manning,2009 年)高度评价 推荐。本书的重点是 AspectJ,但许多通用的 AOP 主题是 探索(在一定程度上)。spring-doc.cadn.net.cn

6. Spring AOP API

上一章介绍了 Spring 对 AOP 的支持,包括基于 @AspectJ 和 schema aspect 定义。在本章中,我们将讨论较低级别的 Spring AOP API。对于普通 应用程序,我们建议将 Spring AOP 与 AspectJ 切入点一起使用,如 上一章。spring-doc.cadn.net.cn

6.1. Spring 中的 Pointcut API

本节描述了 Spring 如何处理关键的切入点概念。spring-doc.cadn.net.cn

6.1.1. 概念

Spring 的切入点模型支持独立于通知类型的切入点重用。您可以 使用相同的切入点定位不同的建议。spring-doc.cadn.net.cn

org.springframework.aop.Pointcutinterface 是中央接口,用于 将 Advice 定位到特定的类和方法。完整的界面如下:spring-doc.cadn.net.cn

Java
public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}
Kotlin
interface Pointcut {

    fun getClassFilter(): ClassFilter

    fun getMethodMatcher(): MethodMatcher

}

拆分Pointcut接口分为两部分,允许重用类和方法 匹配部分和精细组合作(例如执行 “union” 替换为另一个方法 matcher)。spring-doc.cadn.net.cn

ClassFilterinterface 用于将切入点限制为给定的目标集 类。如果matches()method 始终返回 true,则所有目标类都是 匹配。下面的清单显示了ClassFilter接口定义:spring-doc.cadn.net.cn

Java
public interface ClassFilter {

    boolean matches(Class clazz);
}
Kotlin
interface ClassFilter {

    fun matches(clazz: Class<*>): Boolean
}

MethodMatcherinterface 通常更重要。完整的界面如下:spring-doc.cadn.net.cn

Java
public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}
Kotlin
interface MethodMatcher {

    val isRuntime: Boolean

    fun matches(m: Method, targetClass: Class<*>): Boolean

    fun matches(m: Method, targetClass: Class<*>, args: Array<Any>): Boolean
}

matches(Method, Class)method 用于测试此切入点是否曾经 匹配目标类上的给定方法。当 AOP 创建 proxy 是为了避免在每次方法调用时都需要进行测试。如果 双参数matchesmethod 返回true对于给定方法,并且isRuntime()method 的 MethodMatcher 返回true,则三个参数 matches 方法是 在每次方法调用时调用。这允许切入点查看传递的参数 添加到紧接 Target 通知开始之前的方法调用。spring-doc.cadn.net.cn

MethodMatcher实现是静态的,这意味着它们的isRuntime()方法 返回false.在这种情况下,三个参数matchesmethod 永远不会被调用。spring-doc.cadn.net.cn

如果可能,请尝试将切入点设为静态,允许 AOP 框架缓存 创建 AOP 代理时的切入点评估结果。

6.1.2. 对切入点的作

Spring 支持对切入点进行作(特别是 union 和 intersection)。spring-doc.cadn.net.cn

Union 表示任一切入点匹配的方法。 Intersection 表示两个切入点匹配的方法。 Union 通常更有用。 你可以使用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

静态切入点

静态切入点基于方法和目标类,不能考虑 方法的参数。对于大多数用法,静态切入点就足够了,而且是最好的。 Spring 只能在首次调用方法时对静态切入点进行一次求值。 之后,无需在每次方法调用时再次评估切入点。spring-doc.cadn.net.cn

本节的其余部分描述了一些静态切入点实现,这些实现是 包含在 Spring 中。spring-doc.cadn.net.cn

正则表达式切入点

指定静态切入点的一种明显方法是正则表达式。多个 AOP 除了 Spring 之外的框架使这成为可能。org.springframework.aop.support.JdkRegexpMethodPointcut是泛型常规 expression 切入点,该切入点使用 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可以是拦截器,在 advice 之前, throws advice 等)。在幕后, Spring 使用JdkRegexpMethodPointcut. 用RegexpMethodPointcutAdvisor简化了布线,因为一个 bean 封装了 pointcut 和 advice 一起创建,如下例所示: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

控制流切入点

Spring 控制流切入点在概念上类似于 AspectJcflow切入点 / 虽然威力较弱。(目前无法指定切入点运行 在与另一个切入点匹配的连接点下方。控制流切入点与 当前调用堆栈。例如,如果连接点由方法 在com.mycompany.web软件包或通过SomeCaller类。控制流切入点 通过使用org.springframework.aop.support.ControlFlowPointcut类。spring-doc.cadn.net.cn

控制流切入点在运行时的评估成本甚至比 其他动态切入点。在 Java 1.4 中,成本大约是其他动态的 5 倍 切入点。

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 中的 Advice API

现在我们可以研究一下 Spring AOP 如何处理建议。spring-doc.cadn.net.cn

6.2.1. 建议生命周期

每个建议都是一个 Spring bean。一个通知实例可以在所有被通知之间共享 对象,或者对于每个被通知的对象是唯一的。这对应于 per-class 或 per-instance 建议。spring-doc.cadn.net.cn

每类建议最常使用。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。它们仅作用于方法和参数。spring-doc.cadn.net.cn

per-instance advice 适合用于 introduction,以支持 mixin。在这种情况下, 该通知将 state 添加到代理对象。spring-doc.cadn.net.cn

您可以在同一个 AOP 代理中混合使用共享建议和每个实例的建议。spring-doc.cadn.net.cn

6.2.2. Spring 中的通知类型

Spring 提供了多种建议类型,并且可扩展以支持 arbitrary advice 类型。本节介绍基本概念和标准通知类型。spring-doc.cadn.net.cn

Interception Around 建议

Spring 中最基本的 advice 类型是 catchion around advice。spring-doc.cadn.net.cn

Spring 与 AOP 兼容Alliance使用 Method 的 Around 通知的接口 拦截。实现MethodInterceptor并且 implementation around advice 也应该实现 以下接口:spring-doc.cadn.net.cn

Java
public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}
Kotlin
interface MethodInterceptor : Interceptor {

    fun invoke(invocation: MethodInvocation) : Any
}

MethodInvocation参数传递给invoke()method 公开了 method 为 invoked、目标连接点、AOP 代理和方法的参数。这invoke()method 应返回调用的结果:join 的返回值 点。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()method 的MethodInvocation.这将沿着 interceptor 链。大多数拦截器调用此方法,并且 返回其返回值。但是,MethodInterceptor,就像任何周围的建议一样,可以 返回不同的值或引发异常,而不是调用 proceed 方法。 但是,您不想在没有充分理由的情况下这样做。spring-doc.cadn.net.cn

MethodInterceptor实现提供与其他符合 AOP Alliance 的 AOP 的互作性 实现。本节其余部分讨论的其他建议类型 实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势 在使用最具体的建议类型时,请坚持使用MethodInterceptor如果 around 建议 您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点 目前无法在框架之间互作,并且 AOP 联盟不支持 当前定义切入点接口。
建议前

更简单的 advice 类型是 before advice。这不需要MethodInvocationobject,因为它仅在进入方法之前调用。spring-doc.cadn.net.cn

before 通知的主要优点是不需要调用proceed()方法,因此,不可能无意中无法继续执行 拦截器链。spring-doc.cadn.net.cn

下面的清单显示了MethodBeforeAdvice接口:spring-doc.cadn.net.cn

Java
public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}
Kotlin
interface MethodBeforeAdvice : BeforeAdvice {

    fun before(m: Method, args: Array<Any>, target: Any)
}

(Spring 的 API 设计将允许 field 之前,尽管通常的对象适用于字段拦截,并且它是 Spring 不太可能实现它。spring-doc.cadn.net.cn

请注意,返回类型为void.Before advice 可以在 join 之前插入自定义行为 point 运行,但无法更改返回值。如果 before 通知抛出 exception,它会停止拦截器链的进一步执行。异常 沿拦截器链向上传播。如果未选中或在 调用的方法,它将直接传递给客户端。否则,它是 包装在 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
    }
}
Before 建议可以与任何切入点一起使用。
抛出建议

如果 join point 抛出 异常。Spring 提供 typed throws 建议。请注意,这意味着org.springframework.aop.ThrowsAdviceinterface 不包含任何方法。它是一个 标记接口,用于标识给定对象实现一个或多个类型化 throw 建议方法。这些应采用以下形式:spring-doc.cadn.net.cn

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于通知方法是否对该方法感兴趣,以及 参数。接下来的两个清单显示了作为 throws advice 示例的类。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
    }
}

与前面的 advice 中,下一个示例声明了四个参数,以便它可以访问被调用的方法 Method arguments 和 target 对象。如果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 方法引发 checked 异常,则它必须 匹配 Target 方法的声明的异常,因此,在某种程度上是 与特定的 Target 方法签名耦合。不要抛出未声明的 checked 与 Target 方法的签名不兼容的 exception!
投掷建议可用于任何切入点。
退货后通知

Spring 中的 after returning 通知必须实现org.springframework.aop.AfterReturningAdvice接口,下面的清单显示了:spring-doc.cadn.net.cn

Java
public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}
Kotlin
interface AfterReturningAdvice : Advice {

    fun afterReturning(returnValue: Any, m: Method, args: Array<Any>, target: Any)
}

返回后通知可以访问返回值(它无法修改), 调用的方法、方法的参数和 Target。spring-doc.cadn.net.cn

以下返回 advice 后,将计算所有具有 not throwown 异常: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 将 introduction advice 视为一种特殊的拦截 advice。spring-doc.cadn.net.cn

Introduction 需要一个IntroductionAdvisor以及一个IntroductionInterceptor那 实现以下接口:spring-doc.cadn.net.cn

Java
public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
Kotlin
interface IntroductionInterceptor : MethodInterceptor {

    fun implementsInterface(intf: Class<*>): Boolean
}

invoke()从 AOP 联盟继承的方法MethodInterceptor接口必须 实施 Introduction。也就是说,如果调用的方法位于引入的 interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它 无法调用proceed().spring-doc.cadn.net.cn

Introduction advice 不能与任何切入点一起使用,因为它仅适用于类 而不是 method, level.您只能将 introduction advice 与IntroductionAdvisor,该方法包括以下方法:spring-doc.cadn.net.cn

Java
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
Kotlin
interface IntroductionAdvisor : Advisor, IntroductionInfo {

    val classFilter: ClassFilter

    @Throws(IllegalArgumentException::class)
    fun validateInterfaces()
}

interface IntroductionInfo {

    val interfaces: Array<Class<*>>
}

没有MethodMatcher因此,没有Pointcut关联 引言 建议。只有类过滤是合乎逻辑的。spring-doc.cadn.net.cn

getInterfaces()method 返回此 advisor 引入的接口。spring-doc.cadn.net.cn

validateInterfaces()method 来查看 引入的接口可以通过配置的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
}

这说明了一个 mixin。我们希望能够将 Suggested 对象强制转换为Lockable, 无论它们的类型和调用 lock 和 unlock 方法。如果我们调用lock()方法,我们 希望所有 setter 方法都抛出一个LockedException.因此,我们可以添加一个 aspect 提供了使对象不可变的能力,而无需他们知道它: AOP 的一个很好的例子。spring-doc.cadn.net.cn

首先,我们需要一个IntroductionInterceptor这完成了繁重的工作。在这个 case 中,我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor便利类。我们可以实施IntroductionInterceptor直接使用,但使用DelegatingIntroductionInterceptor最适合大多数情况。spring-doc.cadn.net.cn

DelegatingIntroductionInterceptor旨在将简介委托给 实际实现引入的接口,隐藏使用拦截 执行此作。您可以使用 constructor 参数将委托设置为任何对象。这 default delegate (当使用无参数构造函数时) 为this.因此,在下一个示例中, 委托是LockMixin子类DelegatingIntroductionInterceptor. 给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor实例 查找委托实现的所有接口(除了IntroductionInterceptor) 并支持针对其中任何一个的 Introduction。 子类,例如LockMixin可以调用suppressInterface(Class intf)方法来抑制不应公开的接口。然而,无论多少 接口和IntroductionInterceptor已准备好支持IntroductionAdvisorused 控制实际公开的接口。一 introduced interface 隐藏了目标对同一接口的任何实现。spring-doc.cadn.net.cn

因此LockMixin延伸DelegatingIntroductionInterceptor并实现Lockable本身。超类会自动拾取该Lockable可以支持 introduction,所以我们不需要指定。我们可以引入任意数量的 接口。spring-doc.cadn.net.cn

请注意lockedinstance 变量。这有效地添加了额外的状态 附加到目标对象中持有的 ID。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实现(调用delegatemethod 如果 该方法,否则继续向连接点前进)通常 够。在当前情况下,我们需要添加一个检查:不能调用 setter 方法 如果处于锁定模式。spring-doc.cadn.net.cn

所需的介绍只需要持有一个不同的LockMixin实例并指定引入的接口(在本例中,仅Lockable).更复杂的示例可能会引用 introduction interceptor (将被定义为原型)。在这种情况下,没有 与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)

我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它 不能使用IntroductionInterceptor没有IntroductionAdvisor.)与通常的 introduction 一样,顾问程序必须是每个实例的, 因为它是有状态的。我们需要一个不同的LockMixinAdvisor,因此LockMixin,对于每个被建议的对象。顾问程序包含被建议对象的 州。spring-doc.cadn.net.cn

我们可以使用Advised.addAdvisor()method 或 (推荐的方法)在 XML 配置中,就像任何其他 advisor 一样。所有代理创建 下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍 和有状态的 mixin 中。spring-doc.cadn.net.cn

6.3. Spring 中的 Advisor API

在 Spring 中,Advisor 是一个只包含单个关联 advice 对象的方面 替换为切入点表达式。spring-doc.cadn.net.cn

除了介绍的特殊情况外,任何顾问都可以与任何建议一起使用。org.springframework.aop.support.DefaultPointcutAdvisor是最常用的 advisor 类。它可以与MethodInterceptor,BeforeAdviceThrowsAdvice.spring-doc.cadn.net.cn

可以在同一个 AOP 代理中混合使用 Spring 中的 advisor 和 advice 类型。为 例如,您可以在 Advice 中使用 Interception Around、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,就像其他 Spring 一样FactoryBeanimplementations,引入了 间接级别。如果您定义了ProxyFactoryBeanfoo、对象 参考foo看不到ProxyFactoryBean实例本身,但是一个对象 由getObject()方法中的ProxyFactoryBean.这 方法创建包装目标对象的 AOP 代理。spring-doc.cadn.net.cn

使用ProxyFactoryBean或其他 IoC 感知 类来创建 AOP 代理,则 advice 和 pointcuts 也可以是 由 IoC 管理。这是一个强大的功能,支持某些难以 实现。例如,通知本身可以引用 application 对象(除了目标,它应该在任何 AOP 中都可用 框架),受益于 Dependency Injection 提供的所有可插拔性。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如果要代理目标类,而不是 Target 类的接口。如果此属性值设置为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、interceptor 或其他通知名称 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。spring-doc.cadn.net.cn

    这些名称是当前工厂中的 bean 名称,包括来自祖先的 bean 名称 工厂。您不能在此处提及 bean 引用,因为这样做会导致ProxyFactoryBean忽略 ADVICE 的 singleton 设置。spring-doc.cadn.net.cn

    您可以在侦听器名称后附加星号 ()。这样做会导致 应用程序名称以星号前部分开头的所有 advisor bean 以应用。您可以在使用 “Global” Advisors 中找到使用此功能的示例。*spring-doc.cadn.net.cn

  • singleton:工厂是否应该返回单个对象,无论如何 通常getObject()方法。几个FactoryBean实施优惠 这样的方法。默认值为true.如果你想使用有状态通知 - 对于 示例,对于有状态 Mixin - 使用 prototype 通知以及 singleton 值false.spring-doc.cadn.net.cn

6.4.3. 基于 JDK 和 CGLIB 的代理

本节是有关如何使用ProxyFactoryBean选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理 object (要代理的)。spring-doc.cadn.net.cn

的行为ProxyFactoryBean关于创建基于 JDK 或 CGLIB 的 代理在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。这ProxyFactoryBean现在 在自动检测接口方面表现出与TransactionProxyFactoryBean类。

如果要代理的目标对象的类(以下简称为 目标类)不实现任何接口,则基于 CGLIB 的代理是 创建。这是最简单的方案,因为 JDK 代理是基于接口的,没有 interfaces 意味着 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已设置为 1 个或多个 完全限定的接口名称,则会创建一个基于 JDK 的代理。创建的 proxy 实现proxyInterfaces财产。如果目标类恰好实现了比 在proxyInterfaces财产,这一切都很好,但是那些 返回的代理不会实现其他接口。spring-doc.cadn.net.cn

如果proxyInterfaces属性的ProxyFactoryBean尚未设置,但 Target 类确实实现了一个(或多个)接口,即ProxyFactoryBean自动检测 Target 类实际上 实现至少一个接口,并创建基于 JDK 的代理。接口 实际上是 Target 类 实现。实际上,这与提供每个 接口,该接口实现到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>

请注意,interceptorNamesproperty 接受String,它保存了 当前工厂中的 interceptor 或 advisor。您可以使用 advisors、interceptor、before、after returning,并抛出 Advice 对象。顾问的排序很重要。spring-doc.cadn.net.cn

您可能想知道为什么该列表不包含 bean 引用。这样做的原因是 那么,如果ProxyFactoryBean设置为false,它必须能够 返回独立的代理实例。如果任何 advisor 本身就是一个原型,则 需要返回独立实例,因此需要能够获取 工厂中的原型实例。持有参考是不够的。

person前面显示的 bean 定义可以代替Personimplementation 中,作为 遵循: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>

PersonUserclass 公开 type 为Person.就 值得一提的是,AOP 代理可以透明地代替“真实”人使用 实现。但是,它的类将是动态代理类。这是可能的 将其转换为Advised接口(稍后讨论)。spring-doc.cadn.net.cn

您可以通过使用匿名 内 Bean 的 Bean 中。只有ProxyFactoryBean定义不同。这 包含建议只是为了完整性。以下示例演示如何使用 匿名内部 Bean: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定义是自包含的。 但是,有时能够从 Factory 实际上可能是一个优势(例如,在某些测试场景中)。spring-doc.cadn.net.cn

6.4.5. 代理类

如果您需要代理一个类,而不是一个或多个接口,该怎么办?spring-doc.cadn.net.cn

想象一下,在我们前面的示例中,没有Person接口。我们需要提供建议 一个名为Person没有实现任何业务接口。在这种情况下,您 可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass属性ProxyFactoryBean前面显示给true.虽然最好 program 添加到接口而不是类,能够通知没有 在处理遗留代码时,实现接口可能很有用。(一般来说,Spring 不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制 特定方法。spring-doc.cadn.net.cn

如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你有 接口。spring-doc.cadn.net.cn

CGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 配置此生成的子类以将方法调用委托给原始目标。这 subclass 用于实现 Decorator 模式,在 Advice 中编织。spring-doc.cadn.net.cn

CGLIB 代理通常应该对用户透明。但是,存在一些问题 考虑:spring-doc.cadn.net.cn

  • Final不能建议方法,因为它们不能被覆盖。spring-doc.cadn.net.cn

  • 无需将 CGLIB 添加到您的 Classpath 中。从 Spring 3.2 开始,CGLIB 被重新打包 并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 在“外部 the box“,JDK 动态代理也是如此。spring-doc.cadn.net.cn

CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应该是一个决定性的考虑因素。spring-doc.cadn.net.cn

6.4.6. 使用 “global” Advisor

通过在拦截器名称后附加星号,所有 bean 名称匹配的 advisor 星号前面的部分将添加到 advisor 链中。这可以派上用场 如果您需要添加一组标准的 “global” 顾问。以下示例定义了 两个 Global Advisors: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 工厂)中, 预先实例化所有单例。因此,它很重要(至少对于 singleton bean) 也就是说,如果你有一个(父)Bean 定义,你只打算用作模板, 并且这个定义指定了一个类,你必须确保将abstract属性设置为true.否则,应用程序上下文实际上会尝试 pre-instantiate 它。spring-doc.cadn.net.cn

6.6. 使用ProxyFactory

使用 Spring 以编程方式创建 AOP 代理很容易。这样,您就可以使用 不依赖于 Spring IoC 的 Spring AOP。spring-doc.cadn.net.cn

目标对象实现的接口包括 自动代理。下面的清单显示了为目标对象创建代理,其中有一个 Interceptor 和一个 Advisor: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.您可以使用目标 object,或指定要在备用 构造 函数。spring-doc.cadn.net.cn

您可以添加 advice(拦截器作为一种专门的 advice),advisor 或两者兼而有之 并在ProxyFactory.如果您添加了IntroductionInterceptionAroundAdvisor中,您可以使代理实现额外的 接口。spring-doc.cadn.net.cn

还有一些方便的方法ProxyFactory(继承自AdvisedSupport) 允许您添加其他 Advice 类型,例如 before 和 throws advice。AdvisedSupport是两者的超类ProxyFactoryProxyFactoryBean.spring-doc.cadn.net.cn

将 AOP 代理创建与 IoC 框架集成是大多数 应用。我们建议您使用 AOP 从 Java 代码外部化配置。 一般来说,你应该这样做。

6.7.作 Suggested Objects

无论您如何创建 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()method 返回一个Advisor对于每个 advisor、interceptor 或 其他已添加到 Factory 的通知类型。如果您添加了Advisor这 在此索引返回的 advisor 是您添加的对象。如果您添加了 interceptor 或其他 advice 类型,Spring 将其包装在 advisor 中,并带有 始终返回的切入点true.因此,如果您添加了MethodInterceptor、顾问 为此索引返回的是一个DefaultPointcutAdvisor这会返回您的MethodInterceptor以及匹配所有类和方法的切入点。spring-doc.cadn.net.cn

addAdvisor()methods 可用于添加任何Advisor.通常,顾问持有 切入点和建议是通用的DefaultPointcutAdvisor,您可以将其与 任何建议或切入点(但不包括介绍)。spring-doc.cadn.net.cn

默认情况下,可以添加或删除 advisor 或拦截器,即使一个 proxy 已创建。唯一的限制是无法添加或删除 Introduction Advisor,因为工厂的现有代理不显示界面 改变。(您可以从工厂获取新的 proxy 以避免此问题。spring-doc.cadn.net.cn

以下示例显示了将 AOP 代理转换为Advised接口和 examine 以及 纵其建议: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)
修改 business 对象,尽管毫无疑问存在合法的用例。 但是,它在开发中可能非常有用(例如,在测试中)。我们有时 发现能够以拦截器或其他 建议,进入我们想要测试的方法调用。(例如,建议可以 进入为该方法创建的事务中,也许是为了运行 SQL 来检查它 在将事务标记为回滚之前,已正确更新了数据库。

根据您创建代理的方式,您通常可以将frozen旗。在那个 case 中,使用Advised isFrozen()method 返回true以及任何修改 建议AopConfigException.能力 冻结被通知对象的 state 在某些情况下很有用(例如,冻结 防止调用代码删除安全拦截器)。spring-doc.cadn.net.cn

6.8. 使用 “auto-proxy” 工具

到目前为止,我们已经考虑使用ProxyFactoryBean或 类似的工厂豆。spring-doc.cadn.net.cn

Spring 还允许我们使用 “auto-proxy” bean 定义,它可以自动 代理选定的 Bean 定义。这是建立在 Spring 的“bean post processor”之上的 基础结构,它允许在容器加载时修改任何 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,这会自动创建 名称与 Literals 值或通配符匹配的 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属性而不是列表 的拦截器,以允许原型 advisor 的正确行为。命名 “interceptors” 可以是顾问或任何建议类型。spring-doc.cadn.net.cn

与一般的自动代理一样,使用BeanNameAutoProxyCreator是 将相同的配置一致地应用于多个对象,并且最小体积为 配置。它是将声明式事务应用于多个 对象。spring-doc.cadn.net.cn

名称匹配的 Bean 定义,例如jdkMyBeanonlyJdk在前面的 例如,是具有 Target 类的普通旧 bean 定义。AOP 代理是 由BeanNameAutoProxyCreator.同样的建议也适用 添加到所有匹配的 bean 中。请注意,如果使用 advisors(而不是 前面的示例),切入点可能以不同的方式应用于不同的 bean。spring-doc.cadn.net.cn

DefaultAdvisorAutoProxyCreator

一个更通用且极其强大的自动代理创建者是DefaultAdvisorAutoProxyCreator.这会自动将符合条件的顾问程序应用于 当前上下文,而无需在 auto-proxy 中包含特定的 bean 名称 顾问的 bean 定义。它提供了一致的配置和 避免重复 asBeanNameAutoProxyCreator.spring-doc.cadn.net.cn

使用此机制涉及:spring-doc.cadn.net.cn

  • 指定DefaultAdvisorAutoProxyCreatorbean 定义。spring-doc.cadn.net.cn

  • 在相同或相关上下文中指定任意数量的 advisor。请注意,这些 必须是顾问,而不是拦截器或其他建议。这是必要的, 因为必须有一个切入点进行评估,来检查每个建议的资格 添加到候选 bean 定义中。spring-doc.cadn.net.cn

DefaultAdvisorAutoProxyCreator自动计算包含的切入点 在每个 advisor 中,查看它应该适用于每个业务对象的建议(如果有) (例如businessObject1businessObject2在示例中)。spring-doc.cadn.net.cn

这意味着可以自动将任意数量的顾问应用于每个业务 对象。如果任何 advisor 中都没有切入点与业务对象中的任何方法匹配,则 对象未被代理。当为新的业务对象添加 bean 定义时, 如有必要,它们会自动代理。spring-doc.cadn.net.cn

自动代理通常具有使调用者无法或 dependencies 获取 un-advised 对象。叫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提供对筛选的支持(通过使用命名 约定,以便仅评估某些 advisor,这允许使用多个 配置不同,AdvisorAutoProxyCreators 在同一个工厂中)和排序。 顾问可以实现org.springframework.core.Ordered接口以确保 如果这是一个问题,请正确排序。这TransactionAttributeSourceAdvisor用于 前面的示例具有可配置的 order 值。默认设置为 unordered。spring-doc.cadn.net.cn

6.9. 使用TargetSource实现

Spring 提供了TargetSource,以org.springframework.aop.TargetSource接口。此接口负责 返回实现连接点的 “target object”。这TargetSourceimplementation 时,每次 AOP 代理处理 method 时,都会请求目标实例 调用。spring-doc.cadn.net.cn

使用 Spring AOP 的开发人员通常不需要直接使用TargetSourceimplementations,但 这提供了一种强大的方法来支持池化、热插拔和其他 复杂的目标。例如,池化TargetSource可以返回不同的目标 instance 的实例,方法是使用池来管理实例。spring-doc.cadn.net.cn

如果未指定TargetSource中,默认实现用于包装 local 对象。每次调用都会返回相同的目标(如您所料)。spring-doc.cadn.net.cn

本节的其余部分介绍了 Spring 提供的标准目标源以及如何使用它们。spring-doc.cadn.net.cn

使用自定义目标源时,您的目标通常需要是原型 而不是单例 bean 定义。这允许 Spring 创建一个新目标 实例。

6.9.1. 热插拔 Target 源

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()call 更改可交换 bean 的目标。持有 引用时,该 bean 不知道更改,但立即开始点击 新目标。spring-doc.cadn.net.cn

虽然这个例子没有添加任何 advice(没有必要在 使用TargetSource)、任何TargetSource可与 武断的建议。spring-doc.cadn.net.cn

6.9.2. 池化 Target 源

使用池化目标源提供与无状态会话类似的编程模型 EJB,其中维护了一个相同实例的池,具有方法调用 正在释放池中的对象。spring-doc.cadn.net.cn

Spring 池和 SLSB 池之间的一个关键区别是 Spring 池可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。spring-doc.cadn.net.cn

Spring 提供了对 Commons Pool 2.2 的支持,它提供了一个 相当有效的池实现。您需要commons-poolJar 在您的 application 的 Classpath 来使用此功能。你也可以子类化org.springframework.aop.target.AbstractPoolingTargetSource以支持任何其他 pooling API 的 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)必须是 原型。这样就可以PoolingTargetSourceimplementation 创建新实例 的目标以根据需要增加池。请参阅javadoc 的AbstractPoolingTargetSource以及您希望用于信息的具体子类 关于其属性。maxSize是最基本的,并且始终保证存在。spring-doc.cadn.net.cn

在这种情况下,myInterceptor是需要 在相同的 IoC 上下文中定义。但是,您无需指定拦截器来 使用池化。如果您只想池化而不需要其他建议,请不要设置interceptorNames属性。spring-doc.cadn.net.cn

你可以配置 Spring 以便能够将任何池化对象强制转换为org.springframework.aop.target.PoolingConfig接口,用于公开信息 通过简介介绍池的配置和当前大小。你 需要定义一个类似于以下内容的 advisor: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>

此 advisor 是通过在AbstractPoolingTargetSource类,因此使用MethodInvokingFactoryBean.这 顾问姓名 (poolConfigAdvisor,此处)必须在 这ProxyFactoryBean,这将公开 pooled 对象。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实现 由任何 Auto-Proxy Creator 使用。spring-doc.cadn.net.cn

6.9.3. 原型 Target 源

设置“原型”目标源类似于设置池化TargetSource.在这个 case,则在每次方法调用时都会创建一个 Target 的新实例。虽然 在现代 JVM 中,创建新对象的成本并不高,将 新对象(满足其 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 的名称。继承用于TargetSourceimplementation 来确保命名一致。与池化目标一样 source,则目标 Bean 必须是原型 Bean 定义。spring-doc.cadn.net.cn

6.9.4.ThreadLocal目标源

ThreadLocal如果需要为每个源创建一个对象,则 target 源非常有用 传入请求(即每个线程)。a 的概念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本身(包装器类中除外)。此外,您应该 请始终记住正确设置 和 unset(其中后者只涉及对ThreadLocal.set(null)) 线程的本地资源。取消设置应在 无论如何,因为不取消设置它可能会导致有问题的行为。Spring的ThreadLocalSupport 可以为您执行此作,并且应始终考虑使用ThreadLocal实例。

6.10. 定义新的建议类型

Spring AOP 被设计为可扩展的。虽然拦截实现策略 目前在内部使用,则可以在 除了围绕 Interception Advice 之外,before、throws advice 和 返回建议后。spring-doc.cadn.net.cn

org.springframework.aop.framework.adapterpackage 是一个 SPI 包,它允许 在不更改核心框架的情况下添加新的自定义通知类型的支持。 自定义Advicetype 的 ID 是它必须实现org.aopalliance.aop.Advicemarker 接口。spring-doc.cadn.net.cn

7. 空安全

尽管 Java 不允许使用其类型系统来表示 null 安全,但 Spring 框架 现在在org.springframework.lang包让您 声明 API 和字段的可为 null 性:spring-doc.cadn.net.cn

Spring 框架本身利用了这些 Comments,但它们也可以在任何 基于 Spring 的 Java 项目,用于声明 null 安全的 API 和可选的 null 安全字段。 尚不支持泛型类型参数、varargs 和数组元素可为 null 性,但 应该在即将发布的发行版中,请参阅 SPR-15942 了解最新信息。可为 Null 性声明应在 Spring Framework 版本,包括次要版本。method 内部使用的类型的可为 null 性 bodies 不在此功能的范围之内。spring-doc.cadn.net.cn

其他常见库(如 Reactor 和 Spring Data)提供了 null 安全的 API,这些 API 使用类似的可为 null 性安排,为 Spring 应用程序开发人员。

7.1. 使用案例

除了为 Spring 框架 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 元注解允许工具供应商 一样,以通用方式提供 null 安全支持,而无需 对 Spring 注解的硬编码支持。spring-doc.cadn.net.cn

没有必要也不建议将 JSR-305 依赖项添加到项目类路径中 利用 Spring 空安全 API。只有使用 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-coremodule 提供了一组抽象来处理各种字节缓冲区 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,它用 一个DataBufferimplementation 的 intent 中实现,这不涉及分配。spring-doc.cadn.net.cn

请注意,WebFlux 应用程序不会创建一个DataBufferFactory直接,而是 通过ServerHttpResponseClientHttpRequest在客户端。 工厂的类型取决于底层的客户端或服务器,例如NettyDataBufferFactory对于 Reactor Netty,DefaultDataBufferFactory对于其他人来说。spring-doc.cadn.net.cn

8.2.DataBuffer

DataBufferinterface 提供与java.nio.ByteBuffer而且还 带来一些额外的好处,其中一些是受到 Netty 的启发ByteBuf. 以下是部分好处列表:spring-doc.cadn.net.cn

8.3.PooledDataBuffer

ByteBuffer 的 Javadoc 中所述, 字节缓冲区可以是 direct 或 non-direct。直接缓冲区可能位于 Java 堆之外 这样就无需复制本机 I/O作。这使得直接缓冲区 对于通过 socket 接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。spring-doc.cadn.net.cn

PooledDataBufferDataBuffer这有助于引用计数 对于字节缓冲池至关重要。它是如何工作的?当PooledDataBuffer是 allocated 的引用计数为 1。调用retain()递增 count 的 count 中,而 调用release()递减它。只要计数大于 0,缓冲区 保证不会被释放。当计数减少到 0 时,池化缓冲区可以是 released,这实际上可能意味着缓冲区的预留内存将返回到 内存池。spring-doc.cadn.net.cn

请注意,与其在PooledDataBuffer直接,在大多数情况下,它更好 要使用DataBufferUtils将 release 或 retain 应用于DataBuffer仅当它是PooledDataBuffer.spring-doc.cadn.net.cn

8.4.DataBufferUtils

DataBufferUtils提供了许多 Utility 方法来作数据缓冲区:spring-doc.cadn.net.cn

  • 将数据缓冲区流加入单个缓冲区中,可能具有零拷贝,例如通过 复合缓冲区,如果底层字节缓冲区 API 支持的话。spring-doc.cadn.net.cn

  • InputStream或蔚来ChannelFlux<DataBuffer>,反之亦然 aPublisher<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-coremodule 提供byte[],ByteBuffer,DataBuffer,ResourceString编码器和解码器实现。这spring-web模块添加 Jackson JSON, Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器spring-doc.cadn.net.cn

8.6. 使用DataBuffer

使用数据缓冲区时,必须特别小心以确保释放缓冲区 因为它们可能是共用的。我们将使用编解码器来说明 这是如何运作的,但概念更普遍地适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。spring-doc.cadn.net.cn

一个Decoder是在创建更高级别之前最后读取 input data buffers 的 对象,因此它必须按如下方式释放它们:spring-doc.cadn.net.cn

  1. 如果Decoder只需读取每个输入缓冲区,即可 立即释放它,它可以通过以下方式执行此作DataBufferUtils.release(dataBuffer).spring-doc.cadn.net.cn

  2. 如果Decoder正在使用FluxMono运算符,例如flatMap,reduce和 其他的则在内部预取和缓存数据项,或者使用filter,skip和其他省略项的 Import,则doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)必须添加到 组合链来确保此类缓冲区在被丢弃之前被释放(可能 也是错误或取消信号的结果。spring-doc.cadn.net.cn

  3. 如果Decoder以任何其他方式保留一个或多个数据缓冲区,则它必须 确保在完全读取时释放它们,或者如果错误或取消发出 在读取和释放缓存的数据缓冲区之前进行。spring-doc.cadn.net.cn

请注意,DataBufferUtils#join提供一种安全有效的数据聚合方式 buffer 流传输到单个数据缓冲区中。同样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. 附录

9.1. XML 架构

附录的这一部分列出了与核心容器相关的 XML 模式。spring-doc.cadn.net.cn

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

前面的配置使用了 SpringFactoryBeanimplementation (FieldRetrievingFactoryBean) 设置isolation属性 设置为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或 non-static 字段值。它通常是 用于检索public static final常量,然后可以使用该常量来设置 另一个 bean 的 property 值或 constructor 参数。spring-doc.cadn.net.cn

以下示例显示了staticfield 公开,通过使用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>

还有一个方便的使用表单,其中staticfield 指定为 Bean name,如下例所示:spring-doc.cadn.net.cn

<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

这确实意味着 bean 不再有任何选择idis (因此任何其他 引用它的 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"/>

前面的配置使用了 SpringFactoryBeanimplementation (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.它的价值ageproperty 为10.spring-doc.cadn.net.cn

<util:property-path/>设置 Bean 属性或构造函数参数

PropertyPathFactoryBean是一个FactoryBean,它计算给定 target 对象。可以直接指定目标对象,也可以通过 Bean 名称指定。然后,您可以使用此 value 作为属性值或构造函数 论点。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"/>

这种形式确实意味着 bean 的名称没有选择。对它的任何引用 也必须使用相同的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>

前面的配置使用了 SpringFactoryBeanimplementation (PropertiesFactoryBean) 实例化java.util.Properties值 从提供的Resourcelocation) 的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>

前面的配置使用了 SpringFactoryBeanimplementation (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>

前面的配置使用了 SpringFactoryBeanimplementation (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>

前面的配置使用了 SpringFactoryBeanimplementation (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

9.1.2. 该aop图式

aop标签处理在 Spring 中配置 AOP 的所有内容,包括 Spring 的 拥有基于代理的 AOP 框架以及 Spring 与 AspectJ AOP 框架的集成。 这些标签在标题为 Aspect Oriented Programming with Spring 的章节中全面介绍。spring-doc.cadn.net.cn

为了完整起见,要使用aopschema 中,您需要具有 Spring XML 配置文件顶部的以下序言( 代码段引用正确的架构,以便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>

9.1.3. 使用context图式

context标签处理ApplicationContext与管道相关的配置 — 也就是说,通常不是对最终用户很重要的 bean,而是对 Spring 中的许多“咕噜咕噜”工作,例如BeanfactoryPostProcessors.以下内容 snippet 引用正确的架构,以便contextnamespace 是 可供您使用: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/>

此元素激活${…​}placeholders 的 v. 指定的属性文件(作为 Spring 资源位置)。此元素 是一种便捷的机制,它设置PropertySourcesPlaceholderConfigurer给你的。如果您需要对特定的PropertySourcesPlaceholderConfigurersetup,您可以自己将其显式定义为 bean。spring-doc.cadn.net.cn

<annotation-config/>

此元素激活 Spring 基础结构以检测 bean 类中的 Comments:spring-doc.cadn.net.cn

或者,您可以选择显式激活单个BeanPostProcessors对于这些注释。spring-doc.cadn.net.cn

此元素不会激活 Spring 的@Transactional注解; 您可以使用<tx:annotation-driven/>元素。同样, Spring 的缓存 Comments 也需要显式启用
<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

9.1.4. Bean 模式

最后但并非最不重要的一点是,我们在beans图式。这些元素 自框架诞生之初就一直在Spring。各种元素的示例 在beansschema 未在此处显示,因为它们已全面介绍 在依赖项和配置中详细介绍(实际上,在那整中)。spring-doc.cadn.net.cn

请注意,您可以向<bean/>XML 定义。 使用这些额外的元数据做什么(如果有的话)完全取决于您自己的自定义 logic 的 API 中 (因此,通常只有在您按照所述编写自己的自定义元素时才有用 在标题为 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

9.2. XML 架构创作

从 2.0 版本开始, Spring 具有一种将基于 schema 的扩展添加到 用于定义和配置 bean 的基本 Spring XML 格式。本节涵盖 如何编写自己的自定义 XML bean 定义解析器,以及 将此类解析器集成到 Spring IoC 容器中。spring-doc.cadn.net.cn

为了便于编写使用架构感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果你不是 熟悉 Spring 标准附带的当前 XML 配置扩展 Spring 发行版,您应该首先阅读上一节 XML 模式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包)。当我们完成时, 我们将能够定义 bean 类型的定义SimpleDateFormat如下:spring-doc.cadn.net.cn

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

(我们包括更详细的 示例将在本附录的后面部分进行。第一个简单示例的目的是引导您 通过创建自定义扩展的基本步骤。spring-doc.cadn.net.cn

9.2.1. 编写 Schema

创建用于 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。

前面的 schema 允许我们配置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-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

前面两个代码片段中的第二个 在容器中创建一个 Bean(由名称dateFormat的类型SimpleDateFormat) 设置了几个属性。spring-doc.cadn.net.cn

基于 schema 的创建配置格式的方法允许紧密集成 使用具有架构感知 XML 编辑器的 IDE。通过使用正确编写的架构,您可以 可以使用自动完成让用户在多个配置选项之间进行选择 在枚举中定义。

9.2.2. 编写一个NamespaceHandler

除了 schema 之外,我们还需要一个NamespaceHandler解析 Spring 在解析配置文件时遇到的这个特定名称空间。在此示例中,NamespaceHandler应该负责解析myns:dateformat元素。spring-doc.cadn.net.cn

NamespaceHandler界面有三种方法:spring-doc.cadn.net.cn

  • init():允许初始化NamespaceHandler,由 Spring 之前。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 支持的范围一起使用。 我们首先突出显示一个简单的示例,不使用装饰,然后 我们在一个更高级的例子中展示 Decoration。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())
    }
}

您可能会注意到,实际上并没有大量的解析逻辑 在这个类中。事实上,NamespaceHandlerSupportclass 有一个内置的 代表团。它支持注册任意数量的BeanDefinitionParser实例,当需要解析其 Namespace。这种清晰的关注点分离使NamespaceHandler处理 编排其命名空间中所有自定义元素的解析,而 委托给BeanDefinitionParsers来执行 XML 解析的繁重工作。这 表示每个BeanDefinitionParser仅包含用于解析单个 custom 元素,正如我们在下一步中看到的那样。spring-doc.cadn.net.cn

9.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代表。

在这个简单的情况下,这就是我们需要做的全部。我们单曲的创建BeanDefinitionAbstractSingleBeanDefinitionParsersuperclass 的 是 Bean 定义的唯一标识符的提取和设置。spring-doc.cadn.net.cn

9.2.4. 注册处理程序和 Schema

编码完成。剩下要做的就是创建 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属性)添加到 Classpath 资源中。此文件是必需的 以防止 Spring 绝对必须使用默认的EntityResolver这需要 用于检索架构文件的 Internet 访问。如果您在此 properties 文件中,Spring 会搜索 schema(在本例中为myns.xsdorg.springframework.samples.xmlpackage) 的 API 中。 以下代码片段显示了我们需要为自定义架构添加的行: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

9.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 我们的定制 bean。

9.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
“Normal” 元素上的自定义属性

编写自己的自定义解析器和相关工件并不难。然而 有时这不是正确的做法。考虑一个场景,您需要 将元数据添加到现有的 Bean 定义中。在这种情况下,您当然 不想编写自己的整个自定义扩展。相反,你只是 希望向现有的 Bean 定义元素添加一个附加属性。spring-doc.cadn.net.cn

再举一个例子,假设你为 service 对象(它不知道)访问集群 JCache,并且您希望确保 命名的 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...
    }
}

现在我们可以转到自定义扩展。首先,我们需要编写 描述 custom 属性的 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