返回

Spring 框架的核心技术(五)

发布时间:2023-11-18 12:16:28 201

Spring 框架的核心技术(五)_spring

5.6. 选择要使用的 AOP 声明样式

一旦你确定一个方面是实现给定的最佳方法 要求,您如何决定在使用Spring AOP或AspectJ之间以及在 方面语言(代码)样式,@AspectJ注释样式,还是Spring XML样式?这些 决策受许多因素的影响,包括应用程序要求, 开发工具,以及团队对 AOP 的熟悉程度。

5.6.1. 弹簧 AOP 还是全方面 J?

使用最简单的方法。Spring AOP比使用完整的AspectJ更简单,因为 无需将 AspectJ 编译器/编织者引入您的开发中 和构建流程。如果您只需要建议在 Spring 上执行操作 豆,春季AOP是正确的选择。如果您需要建议未由 Spring 容器(例如域对象,通常),您需要使用 方面J.如果您希望建议除 简单的方法执行(例如,字段获取或设置连接点等)。

当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果你不使用Java。 5+,已经为您做出了选择:使用代码样式。如果方面发挥很大 在您的设计中的角色,并且您可以使用AspectJ Eclipse的开发工具(AJDT)插件,AspectJ语言语法是 首选选项。它更干净,更简单,因为语言是有目的地的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中不发挥重要作用,您可能需要考虑使用 @AspectJ风格,坚持在 IDE 中进行常规 Java 编译,并添加 构建脚本的方面编织阶段。

5.6.2. Spring AOP 的@AspectJ或 XML?

如果您选择使用 Spring AOP,则可以选择@AspectJ或 XML 样式。 需要考虑各种权衡。

XML风格可能是现有Spring用户最熟悉的,它由真正的支持。 波约。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个很好的 选择(一个好的测试是你是否认为切入点表达式是你的一部分 您可能希望单独更改的配置)。使用 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。

XML 样式有两个缺点。首先,它没有完全封装 在一个地方实现它所解决的需求。干燥原则说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解如何要求 实现在支持Bean类和XML的声明中被拆分 配置文件。使用@AspectJ样式时,将封装此信息 在单个模块中:方面。其次,XML 样式在以下方面受到更多限制 它可以表达比@AspectJ样式:只有“单例”方面实例化模型 受支持,并且无法组合在 XML 中声明的命名切入点。 例如,在@AspectJ样式中,您可以编写如下内容:

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}

在 XML 样式中,可以声明前两个切入点:

        expression="execution(* get*())"/>

expression="execution(org.xyz.Account+ *(..))"/>

XML 方法的缺点是无法通过组合这些定义来定义点切。​​accountPropertyAccess​

@AspectJ样式支持其他实例化模型和更丰富的切入点 组成。它的优点是将方面保留为模块化单元。它还具有 @AspectJ方面的优点是可以理解(从而消耗)两者 春季AOP和AspectJ。因此,如果您后来决定需要AspectJ的功能 要实现其他要求,您可以轻松迁移到经典的 AspectJ 设置。 总的来说,Spring 团队更喜欢自定义方面的@AspectJ风格,而不仅仅是简单 企业服务的配置。

5.7. 混合宽高比类型

完全可以通过使用自动代理支持混合@AspectJ样式方面, 模式定义的方面,声明的顾问,甚至代理 以及相同配置中其他样式的拦截器。所有这些都已实现 通过使用相同的底层支持机制,可以毫无困难地共存。​​​

5.8. 代理机制

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的代理创建代理 目标对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是常见的 开源类定义库(重新打包到)。​​spring-core​

如果要代理的目标对象实现至少一个接口,则 JDK 动态 使用代理。目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则会创建 CGLIB 代理。

如果要强制使用 CGLIB 代理(例如,代理每个方法 为目标对象定义,而不仅仅是由其接口实现的对象), 你可以这样做。但是,您应该考虑以下问题:

  • 使用 CGLIB,不能建议使用方法,因为它们不能在 运行时生成的子类。final
  • 从 Spring 4.0 开始,代理对象的构造函数不再被调用两次, 因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当您的 JVM 这样做时 不允许绕过构造函数,您可能会看到双重调用和 来自 Spring AOP 支持的相应调试日志条目。

要强制使用 CGLIB 代理,请设置属性的值 元素为 true,如下所示:​​proxy-target-class​​​



要在使用 @AspectJ 自动代理支持时强制 CGLIB 代理,请将元素的属性设置为 如下:​​proxy-target-class​​​​​​true​

5.8.1. 了解 AOP 代理

Spring AOP 是基于代理的。掌握 在你写自己的方面或使用任何方面之前,最后一句话实际上意味着什么 Spring 框架提供的基于 Spring AOP 的方面。

首先考虑以下情况:您有一个普通的、未代理的、 没有什么特别的,直接的对象引用,如下所示 代码片段显示:

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

如果在对象引用上调用方法,则直接在 该对象引用,如下图所示和列表所示:

Spring 框架的核心技术(五)_xml_02

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

当客户端代码具有的引用是代理时,情况会略有变化。考虑 下图和代码片段:

Spring 框架的核心技术(五)_应用程序_03

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

这里要了解的关键是方法中的客户端代码 的类具有对代理的引用。这意味着该方法调用 对象引用是对代理的调用。因此,代理可以委派给所有 与该特定方法调用相关的拦截器(建议)。然而 一旦调用最终到达目标对象(引用 在这种情况下),它可能对自身进行的任何方法调用(例如 asor)都将针对引用而不是代理调用。 这具有重要意义。这意味着不会产生自调用 在与方法调用相关的建议中,获得运行的机会。​​main(..)​​​​Main​​​​SimplePojo​​​​this.bar()​​​​this.foo()​​​​this​

好的,那么该怎么办呢?最佳方法(使用术语“最佳” 松散地在这里)是重构你的代码,这样就不会发生自调用。 这确实需要您做一些工作,但这是最好的、侵入性最小的方法。 下一个方法绝对是可怕的,我们犹豫是否要指出它,确切地说 因为它太可怕了。你可以(对我们来说很痛苦)完全将逻辑联系起来 在您的类中到 Spring AOP,如以下示例所示:

public class SimplePojo implements Pojo {

public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}

public void bar() {
// some logic...
}
}

这完全将你的代码与Spring AOP耦合,它使类本身知道 它被用于 AOP 上下文的事实,这与 AOP 背道而驰。它 创建代理时还需要一些额外的配置,因为 以下示例显示:

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

最后,必须注意的是,AspectJ 没有这个自调用问题,因为 它不是基于代理的AOP框架。

5.9. @AspectJ代理的程序化创建

除了使用 anyor 声明配置中的方面外,还可以以编程方式创建代理 建议目标对象。有关 Spring 的 AOP API 的完整详细信息,请参阅下一章。在这里,我们要关注的是自动的能力 使用@AspectJ方面创建代理。​​​

您可以使用类 为目标对象创建由一个或多个@AspectJ方面建议的代理。 此类的基本用法非常简单,如以下示例所示:​​org.springframework.aop.aspectj.annotation.AspectJProxyFactory​

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

有关更多信息,请参阅javadoc。

5.10. 将 AspectJ 与弹簧应用程序一起使用

到目前为止,我们在本章中介绍的所有内容都是纯粹的春季AOP。在本节中, 我们将介绍如何使用 AspectJ 编译器或 weaver 而不是 or in 除了Spring AOP,如果你的需求超出了Spring AOP提供的设施 独自。

弹簧附带一个小型的AspectJ方面库,可在您的 分布为。您需要按顺序将其添加到类路径中 以使用其中的各个方面。使用 AspectJ 使用 Spring 依赖注入域对象,以及 AspectJ的其他 Spring 方面讨论 此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面讨论了如何 依赖关系注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,在弹簧框架中使用AspectJ进行加载时编织介绍了弹簧应用的加载时间编织 使用AspectJ。​​spring-aspects.jar​

5.10.1. 使用 AspectJ 通过 Spring 依赖注入域对象

Spring 容器实例化和配置应用程序中定义的 bean 上下文。也可以要求 Bean 工厂配置预先存在的 对象,给定包含要应用的配置的 Bean 定义的名称。包含利用此参数的注释驱动方面 允许任何对象的依赖关系注入的能力。该支持旨在 用于在任何容器控制之外创建的对象。域对象 通常属于此类别,因为它们通常是使用运算符或由ORM工具以编程方式创建的,作为数据库查询的结果。​​spring-aspects.jar​​​​new​

注释将类标记为符合弹簧驱动的条件 配置。在最简单的情况下,您可以纯粹将其用作标记注释,如 以下示例显示:​​@Configurable​

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
// ...
}

当以这种方式用作标记接口时,Spring 会配置新的实例 注释类型(在本例中)通过使用 Bean 定义(通常 原型范围),与完全限定的类型名称同名 ().由于 Bean 的默认名称是 其类型的完全限定名称,这是声明原型定义的便捷方法 是省略属性,如以下示例所示:​​Account​​​​com.xyz.myapp.domain.Account​​​​id​



如果要显式指定要使用的原型 Bean 定义的名称,请 可以直接在注释中执行此操作,如以下示例所示:

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
// ...
}

Spring 现在寻找一个名为 bean 定义并将其用作 定义以配置新实例。​​account​​​​Account​

您还可以使用自动连线来避免在 都。要让 Spring 应用自动接线,请使用注释的属性。您可以按类型或名称指定 bothor for 自动布线, 分别。作为替代方法,最好指定显式的、注释驱动的 通过字段或方法级别对 bearbean 进行依赖注入(有关更多详细信息,请参阅基于注释的容器配置)。​​autowire​​​​@Configurable​​​​@Configurable(autowire=Autowire.BY_TYPE)​​​​@Configurable(autowire=Autowire.BY_NAME)​​​​@Configurable​​​​@Autowired​​​​@Inject​

最后,您可以在新的 使用属性创建和配置对象(例如,)。如果此属性为 设置为,Spring 在配置后验证所有属性( 不是基元或集合)已设置。​​dependencyCheck​​​​@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)​​​​true​

请注意,单独使用注释不会执行任何操作。正是thein作用于存在 批注。从本质上讲,该方面说,“从初始化返回后 批注类型的新对象,配置新创建的对象 根据注释的属性使用弹簧”。在这种情况下, “初始化”是指新实例化的对象(例如,实例化的对象 与操作员)以及正在经历的对象 反序列化(例如,通过readResolve())。​​AnnotationBeanConfigurerAspect​​​​spring-aspects.jar​​​​@Configurable​​​​new​​​​Serializable​

为此,必须用AspectJ编织机编织带注释的类型。您可以 使用构建时 Ant 或 Maven 任务来执行此操作(例如,请参阅AspectJ 开发 环境指南​)或加载时编织(参见Spring 框架中的 Load-time Weaving with AspectJ​)。本身需要由 Spring 配置(为了获得 对用于配置新对象的 Bean 工厂的引用)。如果你 使用基于 Java 的配置,可以添加到任意类,如下所示:​​AnnotationBeanConfigurerAspect​​​​@EnableSpringConfigured​​​​@Configuration​

@Configuration
@EnableSpringConfigured
public class AppConfig {
}

如果你更喜欢基于 XML 的配置,Spring上下文命名空间定义了一个方便的元素,你可以按如下方式使用它:​​context:spring-configured​

在配置方面之前创建的对象的实例 导致向调试日志发出消息,并且没有配置 对象正在发生。一个例子可能是 Spring 配置中的 bean,它创建 域对象,当它由 Spring 初始化时。在这种情况下,您可以使用 bean 属性手动指定 bean 依赖于 配置方面。下面的示例演示如何使用属性:​​@Configurable​​​​depends-on​​​​depends-on​

        class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">



单元测试对象​​@Configurable​

支持的目标之一是实现独立的单元测试 的域对象,没有与硬编码查找相关的困难。 iftype没有被AspectJ编织,注释没有影响 在单元测试期间。您可以在对象中设置模拟或存根属性引用 测试并照常进行。如果类型是由AspectJ编织的, 您仍然可以像往常一样在容器外部进行单元测试,但会看到警告 每次构造对象时的消息,指示它具有 不是由 Spring 配置的。​​@Configurable​​​​@Configurable​​​​@Configurable​​​​@Configurable​

使用多个应用程序上下文

用于实现支持 是一个方面J单例方面。单例方面的范围与范围相同 ofmembers:每个类加载器都有一个方面实例来定义类型。 这意味着,如果在同一类装入器中定义多个应用程序上下文 层次结构,您需要考虑在哪里定义 thebean 和 类路径的放置位置。​​AnnotationBeanConfigurerAspect​​​​@Configurable​​​​static​​​​@EnableSpringConfigured​​​​spring-aspects.jar​

考虑具有共享父应用程序的典型 Spring Web 应用程序配置 定义常见业务服务的上下文,以及支持这些服务所需的一切, 以及每个 servlet 的一个子应用程序上下文(其中包含特定的定义 到那个奴仆)。所有这些上下文都共存于同一个类装入器层次结构中, 因此,可以只引用其中之一。 在这种情况下,我们建议在共享中定义 thebean (父)应用程序上下文。这定义了您可能想要的服务 注入到域对象中。结果是您无法配置域对象 引用在子(特定于 servlet)上下文中定义的 bean,方法是使用 @Configurable机制(无论如何,这可能不是您想要做的事情)。​​AnnotationBeanConfigurerAspect​​​​@EnableSpringConfigured​

在同一容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的类加载器加载类型 (例如,通过放置)。仅添加到容器范围的类路径的 ifis (因此由共享父级加载) 类加载器),所有 Web 应用程序共享相同的方面实例(这可能是 不是你想要的)。​​spring-aspects.jar​​​​spring-aspects.jar​​​​WEB-INF/lib​​​​spring-aspects.jar​

5.10.2. AspectJ 的其他弹簧方面

除了方面,还包含一个方面J 可用于驱动 Spring 类型和方法事务管理的方面 用注释注释。这主要适用于以下用户: 想要在 Spring 容器之外使用 Spring 框架的事务支持。​​@Configurable​​​​spring-aspects.jar​​​​@Transactional​

解释注释的方面是。使用此方面时,必须注释 实现类(或该类中的方法或两者),而不是接口(如果 任何)类实现的。AspectJ遵循Java的规则,即注释 接口不是继承的。​​@Transactional​​​​AnnotationTransactionAspect​

类上的注释指定 执行类中的任何公共操作。​​@Transactional​

对类中方法的注释将覆盖默认值 由类注释(如果存在)给出的事务语义。任何方法 可见性可以注释,包括私有方法。注释非公共方法 直接是获取此类方法执行的交易划分的唯一方法。​​@Transactional​

对于想要使用 Spring 配置和事务的 AspectJ 程序员 管理支持但不想(或不能)使用注释,还包含可以扩展以提供自己的切入点的方面 定义。有关更多信息,请参阅来源以获取和方面。例如,以下 摘录显示了如何编写一个方面来配置对象的所有实例 使用与 完全限定的类名:​​spring-aspects.jar​​​​abstract​​​​AbstractBeanConfigurerAspect​​​​AbstractTransactionAspect​

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 运行时本身是 负责方面创建,以及配置AspectJ创建的方法 通过 Spring 的方面依赖于 AspectJ 实例化模型(子句) 由方面使用。​​per-xxx​

AspectJ的大多数方面都是单例方面。这些配置的配置 方面很容易。您可以创建一个将方面类型引用为 的 Bean 定义 正常并包含 thebean 属性。这可确保 Spring 通过向 AspectJ 请求它而不是尝试创建 aspect 实例来获取它。 实例本身。下面的示例演示如何使用属性:​​factory-method="aspectOf"​​​​factory-method="aspectOf"​

        factory-method="aspectOf"> 


非单一实例方面更难配置。但是,可以通过 创建原型 Bean 定义并使用 Support From 在创建 Bean 后配置方面实例 AspectJ 运行时。​​@Configurable​​​​spring-aspects.jar​

如果您有一些@AspectJ方面想要与AspectJ编织(例如, 对域模型类型使用加载时编织)和所需的其他@AspectJ方面 与Spring AOP一起使用,并且这些方面都在Spring中配置,您可以 需要告诉 Spring AOP @AspectJ自动代理支持哪个确切的子集 配置中定义的@AspectJ方面应用于自动代理。您可以 通过在声明中使用一个或多个元素来执行此操作。每个元素指定一个名称模式,并且只有 与至少一种模式匹配的名称用于 Spring AOP 自动代理 配置。以下示例演示如何使用元素:​​​​​​​




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

加载时间编织(LTW)是指将AspectJ方面编织成 应用程序在加载到 Java 虚拟机 (JVM) 时的类文件。 本节的重点是在 弹簧框架。本节不是对 LTW 的一般介绍。有关以下详细信息的完整详细信息 LTW 的细节以及仅使用 AspectJ 配置 LTW(Spring 不是 完全涉及),请参阅AspectJ的LTW部分 开发环境指南。

Spring 框架为 AspectJ LTW 带来的价值在于实现了很多 对织造过程进行更精细的控制。“香草”AspectJ LTW通过使用 Java (5+) 代理程序,通过在启动 JVM.因此,它是一个 JVM 范围的设置,在某些情况下可能很好,但通常是一个 有点太粗糙了。启用弹簧的 LTW 可让您在 每个基础,哪个更细粒度,哪个可以制作更多 在“单 JVM 多应用程序”环境中(例如在典型的环境中发现 应用程序服务器环境)。​​ClassLoader​

此外,在某些环境中,此支持使 加载时编织,而不对应用程序服务器的启动进行任何修改 添加器所需的脚本(正如我们所描述的 在本节的后面)。开发人员配置 启用加载时编织而不是依赖管理员的应用程序上下文 通常负责部署配置的人员,例如启动脚本。​​-javaagent:path/to/aspectjweaver.jar​​​​-javaagent:path/to/spring-instrument.jar​

现在销售宣传已经结束,让我们首先通过AspectJ的快速示例 使用 Spring 的 LTW,然后是有关 例。有关完整示例,请参阅Petclinic 示例应用程序。

第一个例子

假设您是负责诊断的应用程序开发人员 系统中某些性能问题的原因。而不是爆发 分析工具,我们将打开一个简单的分析方面,让我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具紧接着到该特定区域。

以下示例显示了分析方面,这并不花哨。 它是一个基于时间的分析器,使用@AspectJ样式的方面声明:

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

我们还需要创建一个文件,以通知 AspectJ 编织者 我们想把我们的东西编织到我们的课堂上。此文件约定,即 在调用的 Java 类路径上存在一个或多个文件是 标准方面J。以下示例显示该文件:​​META-INF/aop.xml​​​​ProfilingAspect​​​​META-INF/aop.xml​​​​aop.xml​














现在我们可以转到配置的 Spring 特定部分。我们需要 配置(稍后解释)。这个加载时间编织者是 负责将方面配置编织在一个或 更多文件到应用程序中的类中。好的 问题是它不需要很多配置(还有一些 您可以指定的选项,但稍后会详细介绍这些选项),如 以下示例:​​LoadTimeWeaver​​​​META-INF/aop.xml​


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


class="foo.StubEntitlementCalculationService"/>



现在所有必需的工件(方面、文件和 Spring 配置)都已就绪,我们可以创建以下内容 驱动程序类,带有演示 LTW 运行的方法:​​META-INF/aop.xml​​​​main(..)​

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

我们还有最后一件事要做。本节的介绍确实说可以 使用 Spring 有选择地打开 LTW,这是真的。 但是,对于此示例,我们使用Java代理(随Spring一起提供)来打开LTW。 我们使用以下命令运行前面显示的类:​​ClassLoader​​​​Main​

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

这是用于指定和启用代理的标志 来检测在 JVM 上运行的程序。Spring 框架附带了这样的 代理, 的, 它被打包在 那 作为参数的值提供 前面的示例。​​-javaagent​​​​InstrumentationSavingAgent​​​​spring-instrument.jar​​​​-javaagent​

程序执行的输出类似于下一个示例。 (我在实现中引入了一个语句,以便探查器实际上捕获 0 以外的内容 毫秒(毫秒不是 AOP 引入的开销)。 以下清单显示了我们在运行探查器时获得的输出:​​Main​​​​Thread.sleep(..)​​​​calculateEntitlement()​​​​01234​

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms % Task name
------ ----- ----------------------------
01234 100% calculateEntitlement

由于此LTW是通过使用成熟的AspectJ来实现的,因此我们不仅限于建议 春豆。程序的以下细微变化产生相同的结果 结果:​​Main​

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

请注意,在前面的程序中,我们如何引导 Spring 容器和 然后创建一个新的实例 完全在外面 春天的背景。分析建议仍然被编织在一起。​​StubEntitlementCalculationService​

诚然,这个例子太简单了。但是,春季LTW支持的基础知识 在前面的示例中都进行了介绍,本节的其余部分将解释 详细介绍了配置和使用背后的“原因”。

方面

您在 LTW 中使用的方面必须是 AspectJ 方面。你可以把它们写在 要么是AspectJ语言本身,要么你可以用@AspectJ风格来写你的方面。 然后,您的方面既是有效的AspectJ和Spring AOP方面。 此外,编译的方面类需要在类路径上可用。

“元信息/AOP.xml”

AspectJ LTW 基础结构是通过使用 Java 类路径上的一个或多个文件(直接使用或更典型的在 jar 文件中)来配置的。​​META-INF/aop.xml​

该文件的结构和内容在AspectJ参考的LTW部分中有详细说明 文档。因为该文件是 100% AspectJ,所以我们在这里不再进一步描述它。​​aop.xml​

所需库 (罐)

至少,您需要以下库才能使用 Spring 框架的支持。 对于AspectJ LTW:

  • ​spring-aop.jar​
  • ​aspectjweaver.jar​

如果您使用Spring 提供的代理启用 检测,您还需要:

  • ​spring-instrument.jar​
弹簧配置

Spring 的 LTW 支持的关键组件是接口(在包中)和众多实现 它与弹簧发行版一起发货。艾斯负责 添加一个或多个到 AAT 运行时,它为各种有趣的应用程序打开了大门,其中之一是 恰好是各方面的LTW。​​LoadTimeWeaver​​​​org.springframework.instrument.classloading​​​​LoadTimeWeaver​​​​java.lang.instrument.ClassFileTransformers​​​​ClassLoader​

为特定配置 a可以像 添加一行。(请注意,您几乎肯定需要使用 anas 您的 Spring 容器 — 通常,ais 不是 足够了,因为 LTW 支持使用。​​LoadTimeWeaver​​​​ApplicationContext​​​​ApplicationContext​​​​BeanFactory​​​​BeanFactoryPostProcessors​

要启用 Spring 框架的 LTW 支持,您需要配置一个, 这通常是通过使用注释来完成的,如下所示:​​LoadTimeWeaver​​​​@EnableLoadTimeWeaving​

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者,如果您更喜欢基于 XML 的配置,请使用元素。请注意,该元素是在命名空间中定义的。以下示例演示如何使用:​​​​context​​​


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



上述配置自动定义并注册了许多特定于 LTW 的 基础设施豆,如 Aand An,为你。 默认值是类,它尝试 来装饰自动检测。“自动检测”的确切类型取决于您的运行时环境。 下表总结了各种实现:​​LoadTimeWeaver​​​​AspectJWeavingEnabler​​​​LoadTimeWeaver​​​​DefaultContextLoadTimeWeaver​​​​LoadTimeWeaver​​​​LoadTimeWeaver​​​​LoadTimeWeaver​

Table 13. DefaultContextLoadTimeWeaver LoadTimeWeavers

运行时环境

​LoadTimeWeaver​​实现

在阿帕奇雄猫中运行

​TomcatLoadTimeWeaver​

在GlassFish 中运行(仅限于 EAR 部署)

​GlassFishLoadTimeWeaver​

在Red Hat的JBoss AS​或WildFly中运行

​JBossLoadTimeWeaver​

在IBM的WebSphere中运行

​WebSphereLoadTimeWeaver​

在Oracle的WebLogic中运行

​WebLogicLoadTimeWeaver​

JVM从Spring开始​​InstrumentationSavingAgent​​​ (​​java -javaagent:path/to/spring-instrument.jar​​)

​InstrumentationLoadTimeWeaver​

回退,期望底层类加载器遵循通用约定 (即和可选的方法)​​addTransformer​​​​getThrowawayClassLoader​

​ReflectiveLoadTimeWeaver​

请注意,该表仅列出了当您自动检测到的 使用。您可以准确指定要使用的实现。​​LoadTimeWeavers​​​​DefaultContextLoadTimeWeaver​​​​LoadTimeWeaver​

要指定特定的 Java 配置,请实现接口并覆盖该方法。 以下示例指定:​​LoadTimeWeaver​​​​LoadTimeWeavingConfigurer​​​​getLoadTimeWeaver()​​​​ReflectiveLoadTimeWeaver​

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}

如果使用基于 XML 的配置,则可以指定完全限定的类名 作为元素上的属性值。同样,以下示例指定:​​weaver-class​​​​​​ReflectiveLoadTimeWeaver​


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

weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

由配置定义和注册的可以稍后 使用众所周知的名称从 Spring 容器中检索。 请记住,它仅作为弹簧LTW的一种机制而存在 要添加一个或多个的基础结构。实际做 LTW 是(从 包)类。有关更多详细信息,请参阅类的类级javadoc,因为具体如何 实际进行的编织超出了本文档的范围。​​LoadTimeWeaver​​​​loadTimeWeaver​​​​LoadTimeWeaver​​​​ClassFileTransformers​​​​ClassFileTransformer​​​​ClassPreProcessorAgentAdapter​​​​org.aspectj.weaver.loadtime​​​​ClassPreProcessorAgentAdapter​

配置的最后一个属性需要讨论:属性(或者如果你使用 XML)。此属性控制是否 LTW 是否启用。它接受三个可能的值之一,默认值为如果属性不存在。下表总结了三个 可能的值:​​aspectjWeaving​​​​aspectj-weaving​​​​autodetect​

Table 14. AspectJ weaving attribute values

批注值

XML 值

解释

​ENABLED​

​on​

AspectJ 编织已开启,并且根据需要在加载时间编织方面。

​DISABLED​

​off​

LTW 已关闭。加载时不会编织任何方面。

​AUTODETECT​

​autodetect​

如果 Spring LTW 基础设施至少可以找到一个文件, 然后AspectJ编织开始了。否则,它将关闭。这是默认值。​​META-INF/aop.xml​

特定于环境的配置

最后一部分包含您需要的任何其他设置和配置 当您在应用程序服务器和 Web 等环境中使用 Spring 的 LTW 支持时 器皿。

Tomcat, JBoss, WebSphere, WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 全部 提供能够进行本地检测的常规应用。春天的 本机 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。 您可以简单地启用加载时编织,如前所述。 具体来说,您不需要修改要添加的 JVM 启动脚本。​​ClassLoader​​​​-javaagent:path/to/spring-instrument.jar​

请注意,在 JBoss 上,您可能需要禁用应用程序服务器扫描以防止它 在应用程序实际启动之前加载类。一个快速的解决方法是添加 到项目,文件名为以下内容:​​WEB-INF/jboss-scanning.xml​

通用 Java 应用程序

当在不支持的环境中需要类检测时 具体实现,JVM代理是通用解决方案。 对于这种情况,Spring 提供这需要 特定于弹簧(但非常通用)的 JVM 代理,自动检测 通过共同设置。​​LoadTimeWeaver​​​​InstrumentationLoadTimeWeaver​​​​spring-instrument.jar​​​​@EnableLoadTimeWeaving​​​

要使用它,您必须使用 Spring 代理启动虚拟机,方法是提供 以下 JVM 选项:

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改 JVM 启动脚本,这可能会阻止您 在应用程序服务器环境中使用它(取决于您的服务器和您的 操作策略)。也就是说,对于每个 JVM 一个应用程序的部署,例如独立 Spring 引导应用程序,您通常在任何情况下都控制整个 JVM 设置。

5.11. 更多资源

有关AspectJ的更多信息,请访问AspectJ网站。

Eclipse AspectJ by AdrianColyer等(Addison-Wesley, 2005) 提供了一个 AspectJ语言的全面介绍和参考。

AspectJ in Action,Ramnivas Laddad(Manning,2009)的第二版高度推荐 推荐。本书的重点是AspectJ,但很多一般的AOP主题是 探索(在一定深度)。

6. 春季 AOP API

上一章描述了 Spring 对 AOP 的支持,具有@AspectJ和基于模式 方面定义。在本章中,我们将讨论较低级别的Spring AOP API。对于普通 在应用中,我们建议使用带有AspectJ切入点的Spring AOP,如 上一章。

6.1. 春季切入点 API

本节介绍 Spring 如何处理关键切入点概念。

6.1.1. 概念

Spring 的切入点模型可实现独立于建议类型的切入点重用。您可以 用相同的切入点定位不同的建议。

该接口是中央接口,用于 针对特定类别和方法的建议。完整的界面如下:​​org.springframework.aop.Pointcut​

public interface Pointcut {

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();
}

将接口拆分为两部分允许重用类和方法 匹配零件和细粒度组合操作(例如执行“联合” 使用另一个方法匹配器)。​​Pointcut​

该接口用于将切入点限制为一组给定的目标 类。如果该方法始终返回 true,则所有目标类都是 匹配。以下清单显示了接口定义:​​ClassFilter​​​​matches()​​​​ClassFilter​

public interface ClassFilter {

boolean matches(Class clazz);
}

界面通常更重要。完整的界面如下:​​MethodMatcher​

public interface MethodMatcher {

boolean matches(Method m, Class targetClass);

boolean isRuntime();

boolean matches(Method m, Class targetClass, Object... args);
}

该方法用于测试此切入点是否曾经 匹配目标类上的给定方法。当 AOP 创建代理是为了避免对每个方法调用进行测试。如果 双参数方法返回给定方法,方法匹配器返回的方法,三参数匹配方法是 在每个方法调用时调用。这让我们可以切入点查看传递的参数 到目标建议开始之前的方法调用。​​matches(Method, Class)​​​​matches​​​​true​​​​isRuntime()​​​​true​

大多数实现是静态的,这意味着他们的方法 返回。在这种情况下,永远不会调用三参数方法。​​MethodMatcher​​​​isRuntime()​​​​false​​​​matches​

6.1.2. 切入点的操作

弹簧支持切入点的操作(特别是并集和交叉)。

联合是指任一切入点匹配的方法。 交集是指两个切入点匹配的方法。 联合通常更有用。 可以通过在类中使用静态方法或在同一包中使用类来组合切入点。但是,使用AspectJ切入点 表达式通常是一种更简单的方法。​​org.springframework.aop.support.Pointcuts​​​​ComposablePointcut​

6.1.3. 方面J 表达式切入点

从 2.0 开始,Spring 使用的最重要的切入点类型是。这是一个切入点 使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串。​​org.springframework.aop.aspectj.AspectJExpressionPointcut​

有关支持的 AspectJ 切入点基元的讨论,请参阅上一章。

6.1.4. 便利切入点实现

Spring 提供了几个方便的切入点实现。您可以使用其中的一些 径直;其他的则打算在特定于应用程序的切入点中进行子类化。

静态切入点

静态切入点基于方法和目标类,不能考虑 方法的参数。静态切入点足以满足大多数用途,而且是最好的。 Spring 只能在首次调用方法时评估一次静态切入点。 之后,无需在每次方法调用时再次评估切入点。

本节的其余部分介绍一些静态切入点实现,这些实现是 包含在弹簧中。

正则表达式切入点

指定静态切入点的一种明显方法是正则表达式。几个AOP 除了 Spring 之外的框架使这个 possible.is 成为一个通用的常规 使用 JDK 中的正则表达式支持的表达式切入点。​​org.springframework.aop.support.JdkRegexpMethodPointcut​

使用 theclass,您可以提供模式字符串列表。 如果其中任何一个是匹配项,则切入点的计算结果为。(因此, 生成的切入点实际上是指定模式的并集。​​JdkRegexpMethodPointcut​​​​true​

以下示例演示如何使用:​​JdkRegexpMethodPointcut​

        class="org.springframework.aop.support.JdkRegexpMethodPointcut">


.*set.*
.*absquatulate


Spring 提供了一个名为 Convenience 的类,它让我们 还参考 an(请记住,ANCAN 是一个拦截器,在建议之前, 抛出建议等)。在幕后,春天使用了一个。 使用简化了布线,因为一个豆子封装了两者 切入点和建议,如以下示例所示:​​RegexpMethodPointcutAdvisor​​​​Advice​​​​Advice​​​​JdkRegexpMethodPointcut​​​​RegexpMethodPointcutAdvisor​

        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">





.*set.*
.*absquatulate


您可以与任何类型一起使用。​​RegexpMethodPointcutAdvisor​​​​Advice​

属性驱动的切入点

静态切入点的一个重要类型是元数据驱动的切入点。这使用 元数据属性的值(通常是源级元数据)。

动态切入点

动态切入点的评估成本高于静态切入点。他们考虑到 方法参数以及静态信息。这意味着它们必须是 使用每个方法调用进行评估,并且结果无法缓存,因为参数将 不同。

主要的例子是点切。​​control flow​

控制流切入点

弹簧控制流切口在概念上类似于AspectJpointcuts, 虽然没有那么强大。(目前无法指定切入点运行 在与另一个切入点匹配的连接点下方。控制流切入点与 当前调用堆栈。例如,如果连接点由方法调用,则可能会触发 在包装中或按类。控制流切入点 通过使用类指定。​​cflow​​​​com.mycompany.web​​​​SomeCaller​​​​org.springframework.aop.support.ControlFlowPointcut​

6.1.5. 切入点超类

Spring 提供了有用的切入点超类来帮助您实现自己的切入点。

因为静态切入点最有用,所以你应该进行子类。这只需要实现一个 抽象方法(尽管您可以重写其他方法来自定义行为)。这 以下示例演示如何进行子类化:​​StaticMethodMatcherPointcut​​​​StaticMethodMatcherPointcut​

class TestStaticPointcut extends StaticMethodMatcherPointcut {

public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}

还有用于动态切入点的超类。 您可以将自定义切入点与任何建议类型一起使用。

6.1.6. 自定义切入点

因为Spring AOP中的切入点是Java类而不是语言功能(如 AspectJ),您可以声明自定义切入点,无论是静态的还是动态的。习惯 春季的切入点可以任意复杂。但是,我们建议使用 AspectJ 切入点 表达语言,如果可以的话。

6.2. 春季的建议 API

现在我们可以检查Spring AOP如何处理建议。

6.2.1. 建议生命周期

每个建议都是一个春豆。建议实例可以在所有建议之间共享 对象或对于每个建议对象是唯一的。这对应于每个类或 每个实例的建议。

每类建议最常使用。它适用于一般性建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新对象 州。他们只是根据方法和参数行事。

每个实例的建议适用于介绍,以支持混合。在这种情况下, 该建议将状态添加到代理对象。

您可以在同一 AOP 代理中混合使用共享建议和每实例建议。

6.2.2. 春季的建议类型

Spring 提供了多种建议类型,并且可以扩展以支持 任意建议类型。本节介绍基本概念和标准建议类型。

拦截周围的建议

Spring中最基本的建议类型是围绕建议的拦截。

弹簧符合AOP接口,用于使用方法的周围建议 拦截。实现和围绕建议实现的类也应该实现 以下界面:​​Alliance​​​​MethodInterceptor​

public interface MethodInterceptor extends Interceptor {

Object invoke(MethodInvocation invocation) throws Throwable;
}

方法的参数公开了方法 调用、目标连接点、AOP 代理和方法的参数。该方法应返回调用的结果:连接的返回值 点。​​MethodInvocation​​​​invoke()​​​​invoke()​

以下示例显示了一个简单实现:​​MethodInterceptor​

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

请注意对方法的调用。这沿着 拦截器链朝向连接点。大多数调用此方法和 返回其返回值。但是,像周围的任何建议一样,可以 返回不同的值或引发异常,而不是调用 Proceed 方法。 但是,您不想在没有充分理由的情况下执行此操作。​​proceed()​​​​MethodInvocation​​​​MethodInterceptor​

建议前

更简单的建议类型是建议之前。这不需要对象,因为它仅在输入方法之前被调用。​​MethodInvocation​

之前建议的主要优点是不需要调用该方法,因此,不可能无意中无法继续 拦截器链。​​proceed()​

以下清单显示了界面:​​MethodBeforeAdvice​

public interface MethodBeforeAdvice extends BeforeAdvice {

void before(Method m, Object[] args, Object target) throws Throwable;
}

(Spring的API设计将允许 建议之前的字段,尽管通常的对象适用于字段拦截,并且它是 春天不太可能实现它。

请注意,返回类型为 。在建议之前可以在联接之前插入自定义行为 点运行,但无法更改返回值。如果之前的建议抛出 异常,它会停止拦截器链的进一步执行。例外 沿拦截器链向上传播。如果未选中或在签名上 调用的方法,它直接传递给客户端。否则,它是 由 AOP 代理包装在未经检查的异常中。​​void​

以下示例显示了 Spring 中的 before 建议,它计算所有方法调用:

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;
}
}
投掷建议

如果连接点抛出 异常。春天提供打字投掷建议。请注意,这意味着接口不包含任何方法。这是一个 标记接口,标识给定对象实现一个或多个类型化抛出 建议方法。这些应采用以下形式:​​org.springframework.aop.ThrowsAdvice​

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 参数,取决于建议方法是否对该方法感兴趣,以及 参数。接下来的两个列表显示了作为投掷建议示例的类。

如果抛出 ais(包括从子类),则调用以下建议:​​RemoteException​

public class RemoteThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}

与前面不同 建议,下一个示例声明了四个参数,以便它有权访问调用的方法, 方法 参数和目标对象。如果抛出 ais 将调用以下建议:​​ServletException​

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

最后一个示例说明了如何在单个类中使用这两种方法 处理两者。任意数量的投掷建议 方法可以组合在单个类中。下面的清单显示了最后一个示例:​​RemoteException​​​​ServletException​

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
}
}
返回建议后

Spring 中的返回建议后必须实现接口,以下清单显示:​​org.springframework.aop.AfterReturningAdvice​

public interface AfterReturningAdvice extends Advice {

void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}

返回后通知可以访问返回值(它无法修改), 调用的方法、方法的参数和目标。

返回建议后的以下内容将计算所有成功的方法调用,这些方法调用具有 未引发异常:

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

此建议不会更改执行路径。如果它引发异常,则为 抛出拦截器链而不是返回值。

介绍建议

春天把介绍建议当作一种特殊的拦截建议。

引言需要阿南德· 实现以下接口:​​IntroductionAdvisor​​​​IntroductionInterceptor​

public interface IntroductionInterceptor extends MethodInterceptor {

boolean implementsInterface(Class intf);
}

从 AOP 联盟接口继承的方法必须 实施介绍。也就是说,如果调用的方法位于引入的 接口,引入拦截器负责处理方法调用 — 它 无法调用。​​invoke()​​​​MethodInterceptor​​​​proceed()​

介绍建议不能与任何切入点一起使用,因为它仅适用于课堂, 而不是方法,水平。您只能使用介绍建议,它具有以下方法:​​IntroductionAdvisor​

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

ClassFilter getClassFilter();

void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

Class[] getInterfaces();
}

因此,没有与介绍有关 建议。只有类过滤是合乎逻辑的。​​MethodMatcher​​​​Pointcut​

该方法返回此顾问程序引入的接口。​​getInterfaces()​

该方法在内部用于查看是否 引入的接口可以由配置的实现。​​validateInterfaces()​​​​IntroductionInterceptor​

考虑 Spring 测试套件中的一个例子,假设我们想要 将以下接口引入一个或多个对象:

public interface Lockable {
void lock();
void unlock();
boolean locked();
}

这说明了一个混合。我们希望能够将建议的对象投射到, 无论他们的类型和呼叫锁定和解锁方法是什么。如果我们调用该方法,我们 希望所有 setter 方法都抛出 a。因此,我们可以添加一个方面 提供使对象不可变的能力,而无需它们对此一无所知: AOP的一个很好的例子。​​Lockable​​​​lock()​​​​LockedException​

首先,我们需要一个繁重的工作。在此 案例,我们扩展了便利类。我们可以直接实现,但在大多数情况下使用是最好的。​​IntroductionInterceptor​​​​org.springframework.aop.support.DelegatingIntroductionInterceptor​​​​IntroductionInterceptor​​​​DelegatingIntroductionInterceptor​

Theis 旨在将介绍委托给 实际实现引入的接口,隐蔽使用拦截 这样做。可以使用构造函数参数将委托设置为任何对象。这 默认委托(使用无参数构造函数时)为。因此,在下一个示例中, 委托是的子类。 给定一个委托(默认情况下,它本身),一个实例 查找委托实现的所有接口(除外),并支持针对其中任何一个接口的引入。 子类(如can)调用该方法来抑制不应公开的接口。但是,无论多少 准备支持的接口 ANIS,用于控制实际公开哪些接口。一 引入的接口隐藏了目标对同一接口的任何实现。​​DelegatingIntroductionInterceptor​​​​this​​​​LockMixin​​​​DelegatingIntroductionInterceptor​​​​DelegatingIntroductionInterceptor​​​​IntroductionInterceptor​​​​LockMixin​​​​suppressInterface(Class intf)​​​​IntroductionInterceptor​​​​IntroductionAdvisor​

因此,扩展和实现自己。超类自动拾取可以支持的 简介,所以我们不需要指定。我们可以介绍任意数量的 以这种方式进行接口。​​LockMixin​​​​DelegatingIntroductionInterceptor​​​​Lockable​​​​Lockable​

请注意实例变量的使用。这有效地增加了额外的状态 到目标对象中保存的那个。​​locked​

以下示例显示了示例类:​​LockMixin​

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

}

通常,您不需要重写该方法。实现(调用方法 if 引入该方法,否则继续向连接点前进) 够。在本例中,我们需要添加一个检查:不能调用 setter 方法 如果在锁定模式下。​​invoke()​​​​DelegatingIntroductionInterceptor​​​​delegate​

所需的引入只需要保留一个不同的实例并指定引入的接口(在本例中,仅)。一个更复杂的例子可以参考引言 拦截器(将被定义为原型)。在这种情况下,没有 与 A 相关的配置,因此我们使用它创建它。 以下示例显示了我们的类:​​LockMixin​​​​Lockable​​​​LockMixin​​​​new​​​​LockMixinAdvisor​

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}

我们可以非常简单地应用此顾问,因为它不需要配置。(但是,它 是不可能使用没有安。像往常一样,介绍顾问必须是每个实例, 因为它是有状态的。我们需要一个不同的实例,因此,对于每个建议的对象。顾问包括建议对象的一部分 州。​​IntroductionInterceptor​​​​IntroductionAdvisor​​​​LockMixinAdvisor​​​​LockMixin​

我们可以通过使用方法或以编程方式应用此顾问 (推荐的方法)在 XML 配置中,与任何其他顾问一样。所有代理创建 下面讨论的选项(包括“自动代理创建者”)正确处理介绍 和有状态的混合。​​Advised.addAdvisor()​

6.3. 春季的顾问 API

在春季,顾问是一个方面,它只包含一个相关的建议对象 用切入点的表达。

除了介绍的特殊情况外,任何顾问都可以与任何最常用的 advice.is 一起使用 顾问类。它可以与 a、、or 一起使用。​​org.springframework.aop.support.DefaultPointcutAdvisor​​​​MethodInterceptor​​​​BeforeAdvice​​​​ThrowsAdvice​

可以在同一个AOP代理中混合使用Spring中的顾问和建议类型。为 例如,您可以在建议周围、抛出建议和在建议之前使用拦截 一个代理配置。弹簧自动创建必要的拦截器 链。

6.4. 使用创建 AOP 代理​​ProxyFactoryBean​

如果您将 Spring IoC 容器(anor)用于 业务对象(你应该是!),你想使用Spring 的 AOP 实现之一。(请记住,工厂 bean 引入了一层间接层,让 它创建不同类型的对象。​​ApplicationContext​​​​BeanFactory​​​​FactoryBean​

在 Spring 中创建 AOP 代理的基本方法是使用 。这样可以完全控制 切入点,任何适用的建议及其顺序。但是,有更简单的 如果您不需要此类控制,则最好选择这些选项。​​org.springframework.aop.framework.ProxyFactoryBean​

6.4.1. 基础知识

与其他 Spring 实现一样,它引入了一个 间接级别。如果定义 anamed,则对象 引用看不到实例本身,而是一个对象 由方法的实现创建。这 方法创建包装目标对象的 AOP 代理。​​ProxyFactoryBean​​​​FactoryBean​​​​ProxyFactoryBean​​​​foo​​​​foo​​​​ProxyFactoryBean​​​​getObject()​​​​ProxyFactoryBean​

使用 aor 另一个 IoC 感知的最重要好处之一 创建 AOP 代理的类是建议和切入点也可以 由国际奥委会管理。这是一个强大的功能,支持某些难以实现的方法 与其他 AOP 框架一起实现。例如,建议本身可以参考 应用程序对象(除了目标,它应该在任何 AOP 中可用 框架),受益于依赖注入提供的所有可插拔性。​​ProxyFactoryBean​

6.4.2. JavaBean 属性

与 Spring 提供的大多数实现一样,该类本身就是一个 JavaBean。其属性用于:​​FactoryBean​​​​ProxyFactoryBean​

  • 指定要代理的目标。
  • 指定是否使用 CGLIB(稍后将介绍,另请参阅基于 JDK 和 CGLIB 的代理)。

一些关键属性继承自(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括 以下内容:​​org.springframework.aop.framework.ProxyConfig​

  • ​proxyTargetClass​​:如果要代理目标类,而不是 目标类的接口。如果此属性值设置为 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。truetrue
  • ​optimize​​:控制是否将主动优化应用于代理 通过CGLIB创建。您不应随意使用此设置,除非您完全 了解相关 AOP 代理如何处理优化。这是当前使用的 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。
  • ​frozen​​:如果是代理配置,则对配置的更改是 不再允许。这既可以作为轻微的优化,也可以用于这些情况 当您不希望调用方能够在创建代理后(通过接口)操作代理时。此属性的默认值为 ,因此允许更改(例如添加其他建议)。frozenAdvisedfalse
  • ​exposeProxy​​:确定是否应在 a中公开当前代理,以便目标可以访问它。如果目标需要获取 将代理和属性设置为,目标可以使用该方法。ThreadLocalexposeProxytrueAopContext.currentProxy()

特定于的其他属性包括:​​ProxyFactoryBean​

  • ​proxyInterfaces​​:接口名称数组。如果未提供,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。String
  • ​interceptorNames​​:一组、拦截器或其他建议名称 应用。订购量很大,先到先得。也就是说, 列表中的第一个拦截器是第一个能够拦截 调用。StringAdvisor

    这些名称是当前工厂中的豆名,包括来自祖先的豆名 工厂。您不能在此处提及 bean 引用,因为这样做会导致忽略建议的单例设置。ProxyFactoryBean


    可以在拦截器名称后附加星号 ()。这样做会导致 应用名称以星号前部分开头的所有顾问 Bean 待应用。您可以在使用“全局”顾问中找到使用此功能的示例。*
  • 单例:工厂是否应该返回单个对象,无论如何 通常称为该方法。几个实现提供 这样的方法。默认值为。如果您想使用有状态的建议 - 对于 例如,对于有状态混合 - 使用原型建议以及单例值。getObject()FactoryBeantruefalse

6.4.3. 基于 JDK 和 CGLIB 的代理

本节作为有关如何选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档 对象(要代理)。​​ProxyFactoryBean​

如果要代理的目标对象的类(以下简称为 目标类)不实现任何接口,基于 CGLIB 的代理是 创建。这是最简单的方案,因为 JDK 代理是基于接口的,并且没有 接口意味着 JDK 代理甚至是不可能的。您可以插入目标 bean 并通过设置属性指定拦截器列表。请注意,a 即使属性已设置为,也会创建基于 CGLIB 的代理。(这样做毫无意义,最好 从 Bean 定义中删除,因为它充其量是多余的,最坏的情况是 令人困惑。​​interceptorNames​​​​proxyTargetClass​​​​ProxyFactoryBean​​​​false​

如果目标类实现一个(或多个)接口,则代理的类型为 创建取决于配置。​​ProxyFactoryBean​

如果的属性已设置为, 将创建基于 CGLIB 的代理。这是有道理的,并且符合 最小惊喜原则。即使的属性已被设置为一个或多个完全限定的接口名称,事实 该属性设置为导致基于 CGLIB 代理生效。​​proxyTargetClass​​​​ProxyFactoryBean​​​​true​​​​proxyInterfaces​​​​ProxyFactoryBean​​​​proxyTargetClass​​​​true​

如果的属性已设置为一个或多个 完全限定的接口名称,将创建基于 JDK 的代理。创建的 代理实现属性中指定的所有接口。如果目标类碰巧实现的接口比 那些在属性中指定的,一切都很好,但是那些 返回的代理不实现其他接口。​​proxyInterfaces​​​​ProxyFactoryBean​​​​proxyInterfaces​​​​proxyInterfaces​

如果尚未设置的属性,但是 目标类确实实现了一个或多个接口,自动检测目标类实际上确实实现了 实现至少一个接口,并创建一个基于 JDK 的代理。接口 实际代理的是目标类的所有接口 实现。实际上,这与提供每个列表相同 目标类实现到属性的接口。然而 它大大减少了工作量,并且不易出现印刷错误。​​proxyInterfaces​​​​ProxyFactoryBean​​​​ProxyFactoryBean​​​​proxyInterfaces​

6.4.4. 代理接口

考虑一个简单的操作示例。此示例涉及:​​ProxyFactoryBean​

  • 代理的目标 Bean。这是憨豆的定义 示例。personTarget
  • 阿南德提供建议。AdvisorInterceptor
  • 用于指定目标对象 (thebean) 的 AOP 代理 Bean 定义, 代理接口和应用建议。personTarget

下面的清单显示了该示例:













class="org.springframework.aop.framework.ProxyFactoryBean">





myAdvisor
debugInterceptor


请注意,该属性采用一个列表,其中包含 当前工厂中的拦截器或顾问。您可以使用顾问,拦截器,之前,之后 返回,并抛出建议对象。顾问的顺序很重要。​​interceptorNames​​​​String​

前面显示的 Thebean 定义可用于代替实现,如 遵循:​​person​​​​Person​

爪哇岛

科特林

Person person = (Person) factory.getBean("person");

同一 IoC 上下文中的其他 Bean 可以表达对它的强类型依赖关系,如 使用普通的 Java 对象。以下示例演示如何执行此操作:



此示例中的类公开类型的属性。至于 值得关注的是,AOP代理可以透明地代替“真实”的人使用。 实现。但是,它的类将是动态代理类。这是可能的 将其强制转换为接口(稍后讨论)。​​PersonUser​​​​Person​​​​Advised​

您可以使用匿名隐藏目标和代理之间的区别 内豆。只是定义不同。这 仅出于完整性而包含建议。以下示例演示如何使用 匿名内豆:​​ProxyFactoryBean​


















myAdvisor
debugInterceptor


使用匿名内 Bean 的优点是只有一个类型对象。如果我们想要,这很有用 防止应用程序上下文的用户获取对 UnAdvice 的引用 反对或需要避免与 Spring IoC 自动接线的任何歧义。还有, 可以说,该定义的优势是自成一体的。 但是,有时能够从 工厂实际上可能是一个优势(例如,在某些测试场景中)。​​Person​​​​ProxyFactoryBean​

6.4.5. 代理类

如果您需要代理一个类,而不是一个或多个接口,该怎么办?

想象一下,在我们前面的示例中,没有接口。我们需要建议 名为 That 的类未实现任何业务接口。在这种情况下,您 可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将前面显示的属性设置为。虽然最好 编程到接口而不是类,能够建议不 实现接口在使用旧代码时可能很有用。(一般来说,春天 不是规定性的。虽然它使应用良好做法变得容易,但它避免了强制 特定方法。​​Person​​​​Person​​​​proxyTargetClass​​​​ProxyFactoryBean​​​​true​

如果你愿意,你可以在任何情况下强制使用CGLIB,即使你有 接口。

CGLIB 代理的工作原理是在运行时生成目标类的子类。春天 配置此生成的子类以将方法调用委托给原始目标。这 子类用于实现装饰器模式,编织在建议中。

CGLIB 代理通常应该对用户透明。但是,存在一些问题 要考虑:

  • ​Final​​不能建议使用方法,因为它们不能被覆盖。
  • 无需将 CGLIB 添加到类路径中。从Spring 3.2开始,CGLIB被重新打包 并包含在弹簧芯 JAR 中。换句话说,基于 CGLIB 的 AOP 工作“不在 盒子“,JDK 动态代理也是如此。

CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应是决定性的考虑因素。

6.4.6. 使用“全局”顾问

通过在拦截器名称后附加星号,所有具有 Bean 名称匹配的顾问 星号前面的部分将添加到顾问链中。这可以派上用场 如果您需要添加一组标准的“全局”顾问。以下示例定义 两位全球顾问:





global*





6.5. 简明的代理定义

特别是在定义事务代理时,您最终可能会得到许多类似的代理 定义。使用父 Bean 和子 Bean 定义以及内部 Bean 定义可以产生更清晰、更简洁的代理定义。

首先,我们为代理创建父、模板、Bean 定义,如下所示:

        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">



PROPAGATION_REQUIRED


这本身永远不会实例化,因此它实际上可能是不完整的。然后,每个代理 需要创建的是一个子 Bean 定义,它包装了 代理作为内部 Bean 定义,因为无论如何都不会单独使用目标。 以下示例显示了这样一个子 Bean:






您可以覆盖父模板中的属性。在以下示例中, 我们覆盖事务传播设置:








PROPAGATION_REQUIRED,readOnly
PROPAGATION_REQUIRED,readOnly
PROPAGATION_REQUIRED,readOnly
PROPAGATION_REQUIRED


请注意,在父 Bean 示例中,我们显式地将父 Bean 定义标记为 如前所述,通过将属性设置为抽象,因此它实际上可能永远不会 实例。默认情况下,应用程序上下文(但不是简单的 Bean 工厂), 预实例化所有单例。因此,这很重要(至少对于单例豆) 如果您有一个仅打算用作模板的(父)Bean 定义, 并且此定义指定了一个类,您必须确保将属性设置为。否则,应用程序上下文实际上会尝试 预实例化它。​​abstract​​​​true​​​​abstract​​​​true​

6.6. 使用​​ProxyFactory​

使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以使用 Spring AOP 不依赖于 Spring IoC。

目标对象实现的接口是 自动代理。下面的清单显示了为目标对象创建代理,其中包含一个 拦截器和一名顾问:

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

第一步是构造类型对象。您可以使用目标创建它 对象,如前面的示例所示,或指定要在备用中代理的接口 构造 函数。​​org.springframework.aop.framework.ProxyFactory​

您可以添加建议(使用拦截器作为专门的建议)、顾问或两者兼而有之 并终生操纵它们。如果添加 ,则可以使代理实现其他 接口。​​ProxyFactory​​​​IntroductionInterceptionAroundAdvisor​

也有方便的方法(继承自) 这使您可以添加其他建议类型,例如 befores 和 throws advice.is bothand 的超类。​​ProxyFactory​​​​AdvisedSupport​​​​AdvisedSupport​​​​ProxyFactory​​​​ProxyFactoryBean​

6.7. 操作建议对象

无论您创建 AOP 代理如何,都可以使用接口操作它们。任何 AOP 代理都可以强制转换为此 接口,无论它实现哪些其他接口。此接口包括 以下方法:​​org.springframework.aop.framework.Advised​

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

该方法为每个顾问、拦截器或 已添加到工厂的其他建议类型。如果添加了 此索引处返回的顾问是您添加的对象。如果您添加了 拦截器或其他建议类型,Spring 将其包装在带有 总是返回的切入点。因此,如果您添加了 a,则顾问 为此索引返回的是 A,它返回与所有类和方法匹配的切入点。​​getAdvisors()​​​​Advisor​​​​Advisor​​​​true​​​​MethodInterceptor​​​​DefaultPointcutAdvisor​​​​MethodInterceptor​

这些方法可用于添加任何方法。通常,顾问持有 切入点和建议是通用的,您可以使用 任何建议或切入点(但不用于介绍)。​​addAdvisor()​​​​Advisor​​​​DefaultPointcutAdvisor​

默认情况下,甚至可以添加或删除顾问或拦截器,即使是代理 已创建。唯一的限制是无法添加或删除 介绍顾问,因为工厂的现有代理不显示界面 改变。(您可以从工厂获取新的代理以避免此问题。

以下示例演示如何将 AOP 代理强制转换为接口并检查和 操纵其建议:​​Advised​

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

根据您创建代理的方式,您通常可以设置标志。在那 大小写,方法返回,以及任何修改的尝试 通过添加或删除结果的建议。能力 在某些情况下,冻结建议对象的状态很有用(例如, 防止调用代码删除安全拦截器)。​​frozen​​​​Advised​​​​isFrozen()​​​​true​​​​AopConfigException​

6.8. 使用“自动代理”工具

到目前为止,我们已经考虑使用 aor 显式创建 AOP 代理 类似的工厂豆。​​ProxyFactoryBean​

Spring 还允许我们使用“自动代理”的 bean 定义,它可以自动 代理选定的 Bean 定义。这是建立在Spring的“bean后处理器”之上的。 基础结构,允许在容器加载时修改任何 Bean 定义。

在此模型中,您在 XML Bean 定义文件中设置了一些特殊的 Bean 定义 以配置自动代理基础结构。这允许您声明目标 符合自动代理的条件。你不需要使用。​​ProxyFactoryBean​

有两种方法可以执行此操作:

  • 通过使用引用当前上下文中特定 Bean 的自动代理创建器。
  • 值得单独考虑的自动代理创建特例: 由源级元数据属性驱动的自动代理创建。

6.8.1. 自动代理 Bean 定义

本节介绍包提供的自动代理创建者。​​org.springframework.aop.framework.autoproxy​

​BeanNameAutoProxyCreator​

该类是一个自动创建的 名称与文字值或通配符匹配的 Bean 的 AOP 代理。以下 示例演示如何创建 Abean:​​BeanNameAutoProxyCreator​​​​BeanPostProcessor​​​​BeanNameAutoProxyCreator​





myInterceptor


与属性一样,有一个属性而不是一个列表 拦截器,以允许原型顾问的正确行为。命名为“拦截器” 可以是顾问或任何建议类型。​​ProxyFactoryBean​​​​interceptorNames​

与一般的自动代理一样,使用的主要点是 将相同的配置一致地应用于多个对象,并且体积最小 配置。它是将声明性事务应用于多个事务的常用选择 对象。​​BeanNameAutoProxyCreator​

名称匹配的 Bean 定义,例如前面的 andin 例如,是带有目标类的普通旧 Bean 定义。AOP 代理是 由 自动创建。应用相同的建议 到所有匹配的豆子。请注意,如果使用顾问(而不是拦截器 前面的示例),切入点可能以不同的方式应用于不同的 bean。​​jdkMyBean​​​​onlyJdk​​​​BeanNameAutoProxyCreator​

​DefaultAdvisorAutoProxyCreator​

一个更通用且非常强大的自动代理创建者是。这会自动在 当前上下文,无需在自动代理中包含特定的 Bean 名称 顾问的 Bean 定义。它提供了一致的配置和 避免重复作为。​​DefaultAdvisorAutoProxyCreator​​​​BeanNameAutoProxyCreator​

使用此机制涉及:

  • 指定 abean 定义。DefaultAdvisorAutoProxyCreator
  • 在相同或相关的上下文中指定任意数量的顾问。请注意,这些 必须是顾问,而不是拦截器或其他建议。这是必要的, 因为必须有一个切入点进行评估,以检查每个建议的资格 到候选 Bean 定义。

自动评估包含的切入点 在每个顾问中,查看它应该应用于每个业务对象的建议(如果有) (如示例中的桑丁)。​​DefaultAdvisorAutoProxyCreator​​​​businessObject1​​​​businessObject2​

这意味着可以自动将任意数量的顾问应用于每个业务 对象。如果任何顾问中没有与业务对象中的任何方法匹配的切入点, 对象未代理。为新的业务对象添加 Bean 定义时, 如有必要,它们会自动代理。

通常,自动代理的优点是使呼叫者无法或 依赖项以获取不建议的对象。调用 this 返回 AOP 代理,而不是目标业务对象。(“内”内 前面显示的“豆”成语也提供了这个好处。​​getBean("businessObject1")​​​​ApplicationContext​

以下示例创建 abean 和另一个 本节中讨论的元素:​​DefaultAdvisorAutoProxyCreator​













如果您想应用相同的建议,这是非常有用的 一致地支持许多业务对象。一旦基础结构定义到位, 您可以添加新的业务对象,而无需包含特定的代理配置。 您还可以轻松放入其他方面(例如,跟踪或 性能监控方面),只需对配置进行最少的更改。​​DefaultAdvisorAutoProxyCreator​

提供对筛选的支持(通过使用命名 约定,以便仅评估某些顾问,这允许使用多个, 不同的配置,顾问自动代理创建者在同一工厂)和订购。 顾问可以实现接口以确保 如果这是一个问题,请正确订购。用于 前面的示例具有可配置的订单值。默认设置为无序。​​DefaultAdvisorAutoProxyCreator​​​​org.springframework.core.Ordered​​​​TransactionAttributeSourceAdvisor​

6.9. 使用实现​​TargetSource​

弹簧提供了a的概念,在界面中表达。此接口负责 返回实现连接点的“目标对象”。每次 AOP 代理处理方法时,都会要求实现提供目标实例 调用。​​TargetSource​​​​org.springframework.aop.TargetSource​​​​TargetSource​

使用 Spring AOP 的开发人员通常不需要直接使用实现,但是 这为支持池化、热插拔和其他 复杂的目标。例如,池化可以返回不同的目标 每个调用的实例,通过使用池来管理实例。​​TargetSource​​​​TargetSource​

如果未指定 a,则使用默认实现来包装 本地对象。每次调用都会返回相同的目标(如您所料)。​​TargetSource​

本节的其余部分描述了 Spring 提供的标准目标源以及如何使用它们。

6.9.1. 热插拔目标源

存在让目标 的 AOP 代理被切换,同时让呼叫者保留对它的引用。​​org.springframework.aop.target.HotSwappableTargetSource​

更改目标源的目标会立即生效。这是线程安全的。​​HotSwappableTargetSource​

您可以使用 HotSwappableTargetSource 上的方法更改目标,如以下示例所示:​​swap()​

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

以下示例显示了所需的 XML 定义:









前面的调用更改了可交换 Bean 的目标。持有 引用该豆子不知道更改,但立即开始点击 新目标。​​swap()​

虽然此示例没有添加任何建议(没有必要向 使用 a),任何可以与 武断的建议。​​TargetSource​​​​TargetSource​

6.9.2. 池化目标源

使用池化目标源可提供与无状态会话类似的编程模型 EJB,其中维护一个相同实例池,并带有方法调用 转到释放池中的对象。

春季池和SLSB池之间的关键区别是,春季池可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。

Spring 为 Commons Pool 2.2 提供支持,它提供了一个 相当有效的池化实现。你需要罐子在你的 应用程序的类路径以使用此功能。您还可以子类以支持任何其他 池化 API。​​commons-pool​​​​org.springframework.aop.target.AbstractPoolingTargetSource​

以下清单显示了一个示例配置:

        scope="prototype">
... properties omitted










请注意,目标对象(在前面的示例中)必须是 原型。这允许实现创建新实例 根据需要扩大池的目标。有关信息,请参阅AbstractPoolingTargetSource的 javadoc和您希望使用的具体子类 关于它的 properties.is 最基本的,并且始终保证存在。​​businessObjectTarget​​​​PoolingTargetSource​​​​maxSize​

在这种情况下,是需要的拦截器的名称 在同一 IoC 上下文中定义。但是,您不需要指定拦截器 使用池化。如果您只想池化而没有其他建议,则根本不要设置该属性。​​myInterceptor​​​​interceptorNames​

您可以将 Spring 配置为能够将任何池化对象投射到接口,从而公开信息 通过介绍来介绍池的配置和当前大小。你 需要定义类似于以下内容的顾问:​​org.springframework.aop.target.PoolingConfig​




该顾问器是通过在类上调用一个方便的方法获得的,因此使用 。这 顾问的姓名(此处)必须在拦截器名称列表中 公开池化对象。​​AbstractPoolingTargetSource​​​​MethodInvokingFactoryBean​​​​poolConfigAdvisor​​​​ProxyFactoryBean​

演员阵容定义如下:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

使用自动代理可以简化池化。您可以设置实现 由任何自动代理创建者使用。​​TargetSource​

6.9.3. 原型目标源

设置“原型”目标源类似于设置池化。在此 在这种情况下,每次方法调用时都会创建一个目标的新实例。虽然 在现代 JVM 中创建新对象的成本并不高,连接 新对象(满足其 IoC 依赖项)可能更昂贵。因此,您不应该 在没有充分理由的情况下使用此方法。​​TargetSource​

为此,您可以修改前面显示的定义,如下所示 (为了清楚起见,我们还更改了名称):​​poolTargetSource​



唯一的属性是目标 Bean 的名称。在实现中使用继承以确保命名的一致性。与池化目标一样 源,目标 Bean 必须是原型 Bean 定义。​​TargetSource​

6.9.4.目标来源​​ThreadLocal​

​ThreadLocal​​如果需要为每个对象创建一个对象,则目标源非常有用 传入请求(即每个线程)。的概念提供了一个 JDK 范围 将资源透明地存储在线程旁边的工具。设置 ais 与其他类型解释的几乎相同 的目标源,如以下示例所示:​​ThreadLocal​​​​ThreadLocalTargetSource​



6.10. 定义新的建议类型

Spring AOP被设计为可扩展的。虽然拦截实施策略 目前在内部使用,可以支持任意建议类型 除了拦截周围的建议,之前,抛出建议,和 在返回建议后。

该软件包是一个SPI软件包,允许 在不更改核心框架的情况下添加新的自定义建议类型的支持。 自定义类型的唯一约束是它必须实现标记接口。​​org.springframework.aop.framework.adapter​​​​Advice​​​​org.aopalliance.aop.Advice​

有关更多信息,请参阅org.springframework.aop.framework.adapterjavadoc。

7. 零安全

虽然Java不允许你用它的类型系统来表达null-safety,但Spring Framework。 现在在包中提供了以下注释,让您 声明 API 和字段的可空性:org.springframework.lang

  • @Nullable:用于指示 特定参数、返回值或字段可以是。null
  • @NonNull:用于指示特定 参数、返回值或字段不能(参数/返回值不需要) 和适用字段)。null@NonNullApi@NonNullFields
  • @NonNullApi:包级别的注释 将 non-null 声明为参数和返回值的默认语义。
  • @NonNullFields:包上的注释 将非 null 声明为字段的默认语义的级别。

Spring 框架本身利用了这些注释,但它们也可以用于任何 基于 Spring 的 Java 项目,用于声明空安全 API 和可选的空安全字段。 尚不支持泛型类型参数、varargs 和数组元素 nullability 但 应该在即将发布的版本中,有关最新信息,请参阅SPR-15942。预计可空性声明将在 Spring 框架版本,包括次要版本。方法内部使用的类型的可空性 正文不在此功能的范围之内。

7.1. 用例

除了为 Spring Framework API 可空性提供显式声明外, IDE(如IDEA或Eclipse)可以使用这些注释来提供有用的 与空安全相关的警告,以避免在运行时。​​NullPointerException​

它们还用于在 Kotlin 项目中使 Spring API 对 null 安全,因为 Kotlin 原生 支持零安全。更多详情 可在Kotlin 支持文档中找到。

7.2. JSR-305 元注释

Spring 注解是用 JSR305注解(一种休眠但广泛传播的 JSR)进行元注解的。JSR-305元注释让工具供应商 像 IDEA 或 Kotlin 一样以通用方式提供零安全支持,而不必 对 Spring 注释的硬编码支持。

没有必要也不建议将 JSR-305 依赖项添加到项目类路径中 利用 Spring 空安全 API。仅使用的项目,例如基于 Spring 的库 代码库中的空安全注释应添加 Gradle 配置或 Mavenscope 以避免编译警告。​​com.google.code.findbugs:jsr305:3.0.2​​​​compileOnly​​​​provided​

8. 数据缓冲区和编解码器

Java NIO 提供但许多库在上面构建自己的字节缓冲区 API, 特别是对于重用缓冲区和/或使用直接缓冲区的网络操作 有利于性能。例如Netty有层次结构,Undertow使用。 XNIO,Jetty使用池字节缓冲区,并带有要释放的回调,等等。 该模块提供了一组抽象来处理各种字节缓冲区 接口如下:​​ByteBuffer​​​​ByteBuf​​​​spring-core​

  • DataBufferFactory抽象化了数据缓冲区的创建。
  • DataBuffer表示一个字节缓冲区,该缓冲区可以池化。
  • DataBufferUtils为数据缓冲区提供了实用工具方法。
  • 编解码器将数据缓冲流解码或编码为更高级别的对象。

8.1. ​​DataBufferFactory​

​DataBufferFactory​​用于通过以下两种方式之一创建数据缓冲区:

  1. 分配新的数据缓冲区,可以选择预先指定容量(如果已知),即 更高效,即使实现可以按需增长和缩小。DataBuffer
  2. 包装一个现有函数,它用 a实施,这不涉及分配。byte[]java.nio.ByteBufferDataBuffer

请注意,WebFlux 应用程序不会直接创建,而是创建 通过客户端访问它。 工厂的类型取决于底层客户端或服务器,例如对于 Reactor Netty,对于其他客户端或服务器。​​DataBufferFactory​​​​ServerHttpResponse​​​​ClientHttpRequest​​​​NettyDataBufferFactory​​​​DefaultDataBufferFactory​

8.2. ​​DataBuffer​

该接口提供类似的操作,但也 带来了一些额外的好处,其中一些是受Netty启发的。 以下是部分好处列表:​​DataBuffer​​​​java.nio.ByteBuffer​​​​ByteBuf​

  • 以独立位置读写,即不需要调用 toto 在读取和写入之间交替。flip()
  • 容量随需求扩展。java.lang.StringBuilder
  • 通过PooledDataBuffer 进行池化缓冲区和引用计数。
  • 将缓冲区查看为、或。java.nio.ByteBufferInputStreamOutputStream
  • 确定给定字节的索引或最后一个索引。

8.3. ​​PooledDataBuffer​

正如Javadoc for ByteBuffer 中所解释的, 字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可能驻留在 Java 堆之外 这消除了对本机 I/O 操作进行复制的需要。这使得直接缓冲区 对于通过套接字接收和发送数据特别有用,但它们也更多 创建和发布成本很高,这导致了池化缓冲区的想法。

​PooledDataBuffer​​是它的扩展,有助于引用计数 对于字节缓冲池至关重要。它是如何工作的?当 ais 分配的引用计数为 1。调用以递增计数,而 调用以递减它。只要计数大于 0,缓冲区就 保证不被释放。当计数减少到 0 时,池化缓冲区可以是 释放,这实际上可能意味着缓冲区的保留内存返回到 内存池。​​DataBuffer​​​​PooledDataBuffer​​​​retain()​​​​release()​

请注意,在大多数情况下,与其直接操作,不如 使用方便的方法,如果它是 的实例,则仅将发布或保留到 a。​​PooledDataBuffer​​​​DataBufferUtils​​​​DataBuffer​​​​PooledDataBuffer​

8.4. ​​DataBufferUtils​

​DataBufferUtils​​提供了许多在数据缓冲区上运行的实用程序方法:

  • 将数据缓冲区流连接到单个缓冲区中,可能具有零拷贝,例如通过 复合缓冲区(如果基础字节缓冲区 API 支持)。
  • Turnor NIOinto,反之亦然。InputStreamChannelFluxPublisherOutputStreamChannel
  • 释放或保留 aif 缓冲区是 的实例的方法。DataBufferPooledDataBuffer
  • 跳过或从字节流中获取,直到特定的字节计数。

8.5. 编解码器

该软件包提供以下策略接口:​​org.springframework.core.codec​

  • ​Encoder​​以编码到数据缓冲区流中。Publisher
  • ​Decoder​​解码为更高级别对象的流。Publisher

该模块提供,,,,编码器和解码器实现。该模块添加了杰克逊 JSON, Jackson Smile,JAXB2,Protocol Buffers和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器。​​spring-core​​​​byte[]​​​​ByteBuffer​​​​DataBuffer​​​​Resource​​​​String​​​​spring-web​

8.6. 使用​​DataBuffer​

使用数据缓冲区时,必须特别注意确保释放缓冲区 因为它们可能被汇集在一起。我们将使用编解码器来说明 这是如何工作的,但这些概念更普遍地适用。让我们看看编解码器必须做什么 在内部管理数据缓冲区。

在创建更高级别之前,A是最后一个读取输入数据缓冲区的人 对象,因此它必须按如下方式释放它们:​​Decoder​

  1. 如果简单地读取每个输入缓冲区并准备好 立即释放它,它可以通过以下方式做到这一点。DecoderDataBufferUtils.release(dataBuffer)
  2. 如果 ais 使用或运算符,例如、和 其他在内部预取和缓存数据项,或使用运算符(如 ,)的其他人,以及省略项目的其他项,则必须添加到 组合链,以确保此类缓冲液在被丢弃之前被释放,可能 也是由于错误或取消信号。DecoderFluxMonoflatMapreducefilterskipdoOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
  3. 如果以任何其他方式保留一个或多个数据缓冲区,则必须 确保在完全读取时释放它们,或者在出现错误或取消信号的情况下释放它们 在读取和释放缓存的数据缓冲区之前发生。Decoder

请注意,提供了一种安全有效的方法来聚合数据 缓冲流到单个数据缓冲区中。同样,解码器可以使用其他安全方法。​​DataBufferUtils#join​​​​skipUntilByteCount​​​​takeUntilByteCount​

解析其他人必须读取(和释放)的数据缓冲区。所以安没有太多事情可做。但是,如果出现以下情况,则必须注意释放数据缓冲区 使用数据填充缓冲区时发生序列化错误。例如:​​Encoder​​​​Encoder​​​​Encoder​

DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
// serialize and populate buffer..
release = false;
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
return buffer;

anis 的使用者负责释放它接收的数据缓冲区。 在WebFlux应用程序中,输出用于写入HTTP服务器 响应,或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是 编写给服务器响应或客户端请求的代码的责任。​​Encoder​​​​Encoder​

请注意,在 Netty 上运行时,有一些调试选项可用于解决缓冲区泄漏问题。

9. 日志记录

从 Spring Framework 5.0 开始,Spring 自带了自己的 Commons Logging 桥接器。 在模块中。该实现检查是否存在 Log4j 2.x 类路径中的 API 和 SLF4J 1.7 API,并使用找到的第一个作为 日志记录实现,回退到 Java 平台的核心日志记录工具(也 称为JUL或)如果 Log4j 2.x 和 SLF4J 都不可用。​​spring-jcl​​​​java.util.logging​

将 Log4j 2.x 或 Logback(或其他 SLF4J 提供程序)放在类路径中,无需任何额外的内容 桥接,让框架自动适应您的选择。欲了解更多信息,请参阅春季 引导日志记录参考文档。

可以通过以下方式检索实现 以下示例。​​Log​​​​org.apache.commons.logging.LogFactory​

public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
}

10. 附录

10.1.XML 模式

附录的这一部分列出了与核心容器相关的 XML 架构。

10.1.1. 模式​​util​

顾名思义,标签处理常见的实用程序配置 问题,例如配置集合、引用常量等。 要在架构中使用标记,您需要在顶部显示以下前导码 的 Spring XML 配置文件(代码片段中的文本引用了 正确的架构,以便命名空间中的标签可供您使用):​​util​​​​util​​​​util​


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 定义:



class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />

前面的配置使用 Spring实现 (the) 在 bean 上设置属性的值 到常量的值。这是 一切都很好,但它很冗长,并且(不必要地)暴露了春天的内部 为最终用户提供管道。​​FactoryBean​​​​FieldRetrievingFactoryBean​​​​isolation​​​​java.sql.Connection.TRANSACTION_SERIALIZABLE​

以下基于 XML 架构的版本更简洁,清楚地表达了 开发人员的意图(“注入此常量值”),它读起来更好:





从字段值设置 Bean 属性或构造函数参数

FieldReretrieveingFactoryBean是一个检索非静态字段值的字段。它通常是 用于检索常量,然后可用于设置 另一个 Bean 的属性值或构造函数参数。​​FactoryBean​​​​static​​​​public​​​​static​​​​final​

下面的示例演示如何使用staticField属性公开 afield:​​static​

        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">

还有一个方便的使用形式,其中字段被指定为 bean 名称,如以下示例所示:​​static​

        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

这确实意味着豆子不再有任何选择(所以任何其他 引用它的豆子也必须使用这个较长的名称),但这种形式非常 定义简洁,非常方便用作内豆,因为没有 为 Bean 引用指定,如以下示例所示:​​id​​​​id​



class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />

您还可以访问另一个 Bean 的非静态(实例)字段,如 在FieldReretrieveevingFactoryBean类的 API 文档中进行了描述。

将枚举值作为属性或构造函数参数注入到 bean 中是 在春天很容易做到。你实际上不需要做任何事情或知道任何事情 弹簧内部(甚至关于类,如)。 以下示例枚举显示了注入枚举值的难易程度:​​FieldRetrievingFactoryBean​

package javax.persistence;

public enum PersistenceContextType {

TRANSACTION,
EXTENDED
}

现在考虑以下类型资源库和相应的 bean 定义:​​PersistenceContextType​

package example;

public class Client {

private PersistenceContextType persistenceContextType;

public void setPersistenceContextType(PersistenceContextType type) {
this.persistenceContextType = type;
}
}


用​

请考虑以下示例:












前面的配置使用 Spring实现 (the) 创建一个 bean(类型),名为 具有等于该属性的值。​​FactoryBean​​​​PropertyPathFactoryBean​​​​int​​​​testBean.age​​​​age​​​​testBean​

现在考虑以下示例,它添加了 aelement:​












元素的属性值遵循的形式。在这种情况下,它选取名为 bean 的属性。该属性的值是。​​path​​​​​​beanName.beanProperty​​​​age​​​​testBean​​​​age​​​​10​

用于设置 Bean 属性或构造函数参数​

​PropertyPathFactoryBean​​是计算给定属性路径的 目标对象。目标对象可以直接指定,也可以通过 Bean 名称指定。然后你可以使用它 另一个 Bean 定义中的值作为属性值或构造函数 论点。​​FactoryBean​

以下示例按名称显示了对另一个 Bean 使用的路径:












class="org.springframework.beans.factory.config.PropertyPathFactoryBean">


在以下示例中,根据内部 Bean 评估路径:


class="org.springframework.beans.factory.config.PropertyPathFactoryBean">






还有一个快捷方式窗体,其中 Bean 名称是属性路径。 下面的示例演示快捷方式窗体:


class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

这种形式确实意味着在豆子的名称中没有选择。对它的任何引用 还必须使用相同的,即路径。如果用作内部 Bean,根本不需要引用它,如下例所示:​​id​



class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

您可以在实际定义中专门设置结果类型。这不是必需的 对于大多数用例,但它有时可能很有用。请参阅 javadoc 以获取有关以下内容的更多信息 此功能。

用​

请考虑以下示例:




前面的配置使用 Spring实现 (the) 来实例化一个带有值的实例 从提供的资源位置加载)。​​FactoryBean​​​​PropertiesFactoryBean​​​​java.util.Properties​

下面的示例使用 aelement 进行更简洁的表示:​​util:properties​


用​

请考虑以下示例:





pechorin@hero.org
raskolnikov@slums.org
stavrogin@gov.org
porfiry@gov.org


前面的配置使用 Spring实现 (the) 创建一个实例并使用取值对其进行初始化 从供应。​​FactoryBean​​​​ListFactoryBean​​​​java.util.List​​​​sourceList​

下面的示例使用 aelement 进行更简洁的表示:​



pechorin@hero.org
raskolnikov@slums.org
stavrogin@gov.org
porfiry@gov.org

您还可以显式控制实例化的确切类型,以及 通过使用元素上的属性填充。为 例如,如果我们真的需要实例化 ato,我们可以使用 以下配置:​​List​​​​list-class​​​​​​java.util.LinkedList​


jackshaftoe@vagabond.org
eliza@thinkingmanscrumpet.org
vanhoek@pirate.org
d'Arcachon@nemesis.org

如果提供了 noattribute,则容器选择实现。​​list-class​​​​List​

用​

请考虑以下示例:











前面的配置使用 Spring实现 (the) 来创建使用键值对初始化的实例 取自供应。​​FactoryBean​​​​MapFactoryBean​​​​java.util.Map​​​​'sourceMap'​

下面的示例使用 aelement 进行更简洁的表示:​







您还可以显式控制实例化的确切类型,以及 通过使用元素上的属性填充。为 例如,如果我们真的需要实例化 ato,我们可以使用 以下配置:​​Map​​​​'map-class'​​​​​​java.util.TreeMap​






如果提供了 noattribute,则容器选择实现。​​'map-class'​​​​Map​

用​

请考虑以下示例:





pechorin@hero.org
raskolnikov@slums.org
stavrogin@gov.org
porfiry@gov.org


前面的配置使用 Spring实现 (the) 创建一个实例,该实例使用所取值进行初始化 从供应。​​FactoryBean​​​​SetFactoryBean​​​​java.util.Set​​​​sourceSet​

下面的示例使用 aelement 进行更简洁的表示:​



pechorin@hero.org
raskolnikov@slums.org
stavrogin@gov.org
porfiry@gov.org

您还可以显式控制实例化的确切类型,以及 通过使用元素上的属性填充。为 例如,如果我们真的需要实例化 ato,我们可以使用 以下配置:​​Set​​​​set-class​​​​​​java.util.TreeSet​


pechorin@hero.org
raskolnikov@slums.org
stavrogin@gov.org
porfiry@gov.org

如果提供了 noattribute,则容器选择实现。​​set-class​​​​Set​

10.1.2. 模式​​aop​

标签处理在 Spring 中配置 AOP 的所有内容,包括 Spring 的 拥有基于代理的AOP框架以及Spring与AspectJ AOP框架的集成。 这些标签在标题为“面向方面编程与弹簧”的章节中进行了全面介绍。​​aop​

为了完整起见,要使用架构中的标记,您需要具有 Spring XML 配置文件顶部的以下序言(文本 代码段引用正确的架构,以便命名空间中的标记 可供您使用):​​aop​​​​aop​


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



10.1.3. 模式​​context​

标签处理与管道相关的配置 — 也就是说,通常不是对最终用户很重要的 bean,而是对 春天的很多“咕噜咕噜”的工作,比如。以下 代码段引用正确的架构,以便命名空间中的元素 可供您使用:​​context​​​​ApplicationContext​​​​BeanfactoryPostProcessors​​​​context​


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



用​

此元素激活占位符的替换,这些占位符针对 指定的属性文件(作为Spring 资源位置)。此元素 是一种方便的机制,可为您设置PropertySourcesPlaceholderConfigurer。如果您需要对特定设置进行更多控制,您可以自己将其显式定义为 Bean。​​${…}​​​​PropertySourcesPlaceholderConfigurer​

用​

此元素激活 Spring 基础结构以检测 Bean 类中的注释:

  • 弹簧@Configuration模型
  • @Autowired/@Inject,和@Value@Lookup
  • JSR-250的,和(如果可用)@Resource@PostConstruct@PreDestroy
  • JAX-WS's's和EJB 3's(如果可用)@WebServiceRef@EJB
  • JPA'sand(如果有)@PersistenceContext@PersistenceUnit
  • 春天的@EventListener

或者,您可以选择显式激活个人以获取这些批注。​​BeanPostProcessors​

用​

有关基于注释的容器配置一节中详细介绍了此元素。

用​

这个元素在Spring 框架中关于使用 AspectJ 进行加载时编织的部分中有详细说明。

用​

这个元素在关于使用 AspectJ 通过 Spring 依赖注入域对象的部分中有详细说明。

用​

有关配置基于注释的 MBean 导出的部分中详细介绍了此元素。

10.1.4. Bean 模式

最后但并非最不重要的一点是,我们在模式中有元素。这些元素 从框架的曙光开始就一直在春天。各种元素的示例 在模式中不显示在这里,因为它们被非常全面地覆盖 在依赖关系和配置方面(实际上,在整个章节中)。​​beans​​​​beans​

请注意,您可以向 XML 定义添加零个或多个键值对。 使用此额外元数据(如果有的话)完成的操作完全取决于您自己的自定义 逻辑(因此通常仅在编写自己的自定义元素时使用,如前所述 在标题为“XML 架构创作”的附录中)。​

下面的示例显示了周围环境中的元素(请注意,没有任何逻辑来解释它,元数据实际上是无用的 就目前而言)。​​​


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 定义并设置一些使用提供的元数据的高速缓存基础结构。

10.2.XML 架构创作

从 2.0 版开始,Spring 具有一种机制,用于将基于模式的扩展添加到 用于定义和配置 bean 的基本 Spring XML 格式。本节涵盖 如何编写自己的自定义 XML Bean 定义解析器以及 将此类解析器集成到Spring IoC容器中。

为了便于创作使用架构感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果你不是 熟悉 Spring 当前随标准一起提供的 XML 配置扩展 Spring 发行版,您应该首先阅读上一节关于XML 模式。

创建新的 XML 配置扩展:

  1. 创作XML 架构来描述自定义元素。
  2. 编写自定义实现代码。NamespaceHandler
  3. 编写一个或多个实现代码 (这是完成真正工作的地方)。BeanDefinitionParser
  4. 向 Spring 注册您的新工件。

对于一个统一的例子,我们创建了一个 XML扩展(一个自定义XML元素),允许我们配置类型的对象(从包)。当我们完成后, 我们将能够定义 Bean 定义 typeas:​​SimpleDateFormat​​​​java.text​​​​SimpleDateFormat​

    pattern="yyyy-MM-dd HH:mm"
lenient="true"/>

(我们包括更详细的内容 本附录后面将提供示例。第一个简单示例的目的是引导您 通过创建自定义扩展的基本步骤。

10.2.1. 创作模式

创建用于 Spring 的 IoC 容器的 XML 配置扩展始于 创作 XML 架构以描述扩展。对于我们的示例,我们使用以下架构 要配置对象:​​SimpleDateFormat​




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













前面的架构允许我们直接在 XML 应用程序上下文文件通过使用元素,如下所示 示例显示:​​SimpleDateFormat​​​

    pattern="yyyy-MM-dd HH:mm"
lenient="true"/>

请注意,在我们创建基础结构类之后,前面的 XML 代码段是 与以下 XML 代码段基本相同:




前面两个代码片段中的第二个 在容器中创建一个 Bean(由 NameOf 类型标识),并设置了几个属性。​​dateFormat​​​​SimpleDateFormat​

10.2.2. 编码​​NamespaceHandler​

除了模式之外,我们还需要 ato 解析 Spring 在解析配置文件时遇到的这个特定命名空间。对于此示例,应负责元素的解析。​​NamespaceHandler​​​​NamespaceHandler​​​​myns:dateformat​

该接口具有三种方法:​​NamespaceHandler​

  • ​init()​​:允许初始化 和 由 调用 使用处理程序之前的弹簧。NamespaceHandler
  • ​BeanDefinition parse(Element, ParserContext)​​:当春天遇到一个 顶级元素(不嵌套在 Bean 定义或其他命名空间中)。 此方法本身可以注册 Bean 定义、返回 Bean 定义或两者兼而有之。
  • ​BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)​​:叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。 例如,一个或多个 Bean 定义的修饰与Spring 支持的作用域一起使用。 我们首先重点介绍一个简单的例子,不使用装饰,之后 我们在一个更高级的例子中展示了装饰。

虽然你可以自己编码整个 命名空间(因此提供解析命名空间中每个元素的代码), 通常情况下,Spring XML 配置文件中的每个顶级 XML 元素 导致单个 Bean 定义(如本例所示,单个元素生成单个 Bean 定义)。弹簧具有 支持此方案的便利类数。在以下示例中,我们 使用类:​​NamespaceHandler​​​​​​SimpleDateFormat​​​​NamespaceHandlerSupport​

package org.springframework.samples.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

public void init() {
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
}
}

您可能会注意到,实际上并没有很多解析逻辑 在本课程中。事实上,该类有一个内置的概念 代表团。它支持注册任意数量的实例,当它需要解析其中的元素时,它委托给这些实例 命名空间。这种清晰的关注点分离可以处理 编排其命名空间中所有自定义元素的分析,同时 委托 toto 做 XML 解析的繁重工作。这 表示每个仅包含用于解析单个的逻辑 自定义元素,正如我们在下一步中看到的。​​NamespaceHandlerSupport​​​​BeanDefinitionParser​​​​NamespaceHandler​​​​BeanDefinitionParsers​​​​BeanDefinitionParser​

10.2.3. 使用​​BeanDefinitionParser​

如果遇到 XML,则使用 Ais 已映射到特定 Bean 定义解析器的类型的元素 (在这种情况下)。换句话说,泰斯 负责分析架构中定义的一个不同的顶级 XML 元素。在 解析器,我们可以访问 XML 元素(因此也可以访问其子元素),以便 我们可以解析自定义 XML 内容,如以下示例所示:​​BeanDefinitionParser​​​​NamespaceHandler​​​​dateformat​​​​BeanDefinitionParser​

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 {

protected Class getBeanClass(Element element) {
return SimpleDateFormat.class;
}

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

}

在这个简单的情况下,这就是我们需要做的。我们的单身的创建由超类处理,作为 是 Bean 定义的唯一标识符的提取和设置。​​BeanDefinition​​​​AbstractSingleBeanDefinitionParser​

10.2.4. 注册处理程序和模式

编码完成。剩下的就是制作Spring XML。 解析了解自定义元素的基础结构。为此,我们将自定义和自定义 XSD 文件注册到两个特殊用途的属性文件中。这些 属性文件都放置在应用程序的目录中,并且 例如,可以与 JAR 文件中的二进制类一起分发。春天 XML 分析基础结构通过使用 这些特殊属性文件,其格式将在接下来的两节中详细介绍。​​namespaceHandler​​​​META-INF​

写作​​META-INF/spring.handlers​

调用的属性文件包含 XML 架构 URI 到 的映射 命名空间处理程序类。对于我们的示例,我们需要编写以下内容:​​spring.handlers​

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

(字符是 Java 属性格式中的有效分隔符,因此 URI 中的字符需要使用反斜杠进行转义。​​:​​​​:​

键值对的第一部分(键)是与自定义关联的 URI 命名空间扩展名,并且需要与自定义 XSD 架构中指定的属性值完全匹配。​​targetNamespace​

编写“META-INF/spring.schemas”

调用的属性文件包含 XML 架构位置的映射 (与架构声明一起在将架构用作一部分的 XML 文件中引用 的属性)到类路径资源。需要此文件 防止 Spring 绝对必须使用默认值这需要 互联网访问以检索架构文件。如果在此指定映射 属性文件,Spring 在类路径上搜索模式(在本例中为包中)。 以下代码片段显示了我们需要为自定义架构添加的行:​​spring.schemas​​​​xsi:schemaLocation​​​​EntityResolver​​​​myns.xsd​​​​org.springframework.samples.xml​

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(请记住,必须转义该字符。​​:​

我们鼓励您同时部署 XSD 文件(或多个文件) 类路径上的类。​​NamespaceHandler​​​​BeanDefinitionParser​

10.2.5. 在 Spring XML 配置中使用自定义扩展

使用您自己实现的自定义扩展与使用 Spring 提供的“自定义”扩展之一。以下 示例使用在前面步骤中开发的自定义元素 在 Spring XML 配置文件中:​


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











10.2.6. 更详细的示例

本节提供自定义 XML 扩展的一些更详细的示例。

在自定义元素中嵌套自定义元素

本节中提供的示例演示如何编写所需的各种工件 以满足以下配置的目标:


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









上述配置将自定义扩展相互嵌套。该类 实际上由元素配置的是类(在下一个示例中显示)。请注意该类如何不公开 属性的二传手方法。这使得它很难(或者更确切地说是不可能的) 使用资源库注入为类配置 Bean 定义。 以下清单显示了类:​​​​Component​​​​Component​​​​components​​​​Component​​​​Component​

package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

private String name;
private List components = new ArrayList ();

// mmm, there is no setter method for the 'components'
public void addComponent(Component component) {
this.components.add(component);
}

public List getComponents() {
return components;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

此问题的典型解决方案是创建一个公开 属性的二传手属性。以下清单显示了此类自定义:​​FactoryBean​​​​components​​​​FactoryBean​

package com.foo;

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

public class ComponentFactoryBean implements FactoryBean {

private Component parent;
private List children;

public void setParent(Component parent) {
this.parent = parent;
}

public void setChildren(List 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 getObjectType() {
return Component.class;
}

public boolean isSingleton() {
return true;
}
}

这很好用,但它向最终用户暴露了很多 Spring 管道。我们是什么 要做的是编写一个自定义扩展,隐藏所有这些春季管道。 如果我们坚持前面描述的步骤,我们开始 通过创建 XSD 架构来定义自定义标记的结构,如下所示 房源显示:



xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/component"
elementFormDefault="qualified"
attributeFormDefault="unqualified">











再次按照前面描述的过程, 然后我们创建一个自定义:​​NamespaceHandler​

package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

public void init() {
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
}
}

接下来是习俗。请记住,我们正在创造 a那描述一个。以下 列表显示了我们的自定义实现:​​BeanDefinitionParser​​​​BeanDefinition​​​​ComponentFactoryBean​​​​BeanDefinitionParser​

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 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 childElements, BeanDefinitionBuilder factory) {
ManagedList children = new ManagedList(childElements.size());
for (Element element : childElements) {
children.add(parseComponentElement(element));
}
factory.addPropertyValue("children", children);
}
}

最后,各种工件需要注册到 Spring XML 基础结构, 通过修改文件,如下所示:​​META-INF/spring.handlers​​​​META-INF/spring.schemas​

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
“普通”元素上的自定义属性

编写自己的自定义分析器和关联的项目并不难。然而 有时这不是正确的做法。考虑需要 将元数据添加到现有的 Bean 定义中。在这种情况下,你当然 不想编写自己的整个自定义扩展。相反,你只是 想要向现有 Bean 定义元素添加附加属性。

通过另一个示例,假设您为 访问集群JCache 的服务对象(它不知道),并且您希望确保 命名的 JCache 实例在周围的集群中急切地启动。 下面的清单显示了这样的定义:

        jcache:cache-name="checking.account">

然后我们可以在解析属性时创建另一个。然后初始化 为我们命名的JCache。我们还可以修改现有的 for 这样它就依赖于这个新的 JCache-initializing.以下列表显示了我们的:​​BeanDefinition​​​​'jcache:cache-name'​​​​BeanDefinition​​​​BeanDefinition​​​​'checkingAccountService'​​​​BeanDefinition​​​​JCacheInitializer​

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

现在我们可以转到自定义扩展。首先,我们需要作者 描述自定义属性的 XSD 架构,如下所示:



xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/jcache"
elementFormDefault="qualified">



接下来,我们需要创建关联的,如下所示:​​NamespaceHandler​

package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}

}

接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 一个XML属性,我们写a而不是a。 以下列表显示了我们的实现:​​BeanDefinitionDecorator​​​​BeanDefinitionParser​​​​BeanDefinitionDecorator​

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

最后,我们需要将各种工件注册到 Spring XML 基础结构中。 通过修改文件,如下所示:​​META-INF/spring.handlers​​​​META-INF/spring.schemas​

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd

10.3. 应用程序启动步骤

附录的这一部分列出了检测核心容器的现有内容。​​StartupSteps​

Table 15. Application startup steps defined in the core container

名字

描述

标签

​spring.beans.instantiate​

Bean 及其依赖项的实例化。

​beanName​​​Bean的名称,注射点所需的类型。​​beanType​

​spring.beans.smart-initialize​

初始化豆子。​​SmartInitializingSingleton​

​beanName​​豆子的名称。

​spring.context.annotated-bean-reader.create​

的创建。​​AnnotatedBeanDefinitionReader​

​spring.context.base-packages.scan​

扫描基本包。

​packages​​用于扫描的基本软件包数组。

​spring.context.beans.post-process​

豆子后处理阶段。

​spring.context.bean-factory.post-process​

调用 thebeans。​​BeanFactoryPostProcessor​

​postProcessor​​当前后处理器。

​spring.context.beandef-registry.post-process​

调用 thebeans。​​BeanDefinitionRegistryPostProcessor​

​postProcessor​​当前后处理器。

​spring.context.component-classes.register​

通过注册组件类。​​AnnotationConfigApplicationContext#register​

​classes​​用于注册的给定类的数组。

​spring.context.config-classes.enhance​

使用 CGLIB 代理增强配置类。

​classCount​​增强类的计数。

​spring.context.config-classes.parse​

配置类解析阶段与。​​ConfigurationClassPostProcessor​

​classCount​​已处理类的计数。

​spring.context.refresh​

应用程序上下文刷新阶段。

版本 5.3.23

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
Spring 框架的核心技术(四) 2023-11-18 08:55:16