🍊
翻译橙
🍊返回主站🤖参与贡献
  • hello,这里是翻译橙
  • spring boot参考文档
    • 1. 法律
    • 2. 寻求帮助
    • 3. 文档概述
    • 4. 入门
    • 5. 升级Spring Boot
    • 6. 使用 Spring Boot 进行开发
      • 6.1. 构建系统
      • 6.2. 构建你的代码
      • 6.3. 配置类
      • 6.4. 自动配置
      • 6.5. Spring Bean 和依赖注入
      • 6.6. 使用@SpringBootApplication注解
      • 6.7. 运行您的应用程序
      • 6.8. 开发者工具
      • 6.9. 打包您的生产应用程序
      • 6.10. 接下来读什么
    • 7.核心特性
      • 7.1. SpringApplication
      • 7.2. 外部化配置
      • 7.3.Profile配置
      • 7.4.日志记录
      • 7.5.国际化
      • 7.6 面向切面的编程
      • 7.7. JSON
      • 7.8. 任务执行与调度
      • 7.9. 单元测试
        • 7.9.1. 测试范围依赖
        • 7.9.2. 测试 Spring 应用程序
        • 7.9.3. 测试 Spring Boot 应用程序
        • 7.9.4. 测试容器
        • 7.9.5. 测试工具
      • 7.10. Docker Compose 支持
      • 7.11. 测试容器支持
      • 7.12. 创建您自己的自动配置
      • 7.13. Kotlin 支持
      • 7.14 SSL
      • 7.15.接下来要读什么
    • 8. 网络
      • 8.1. Servlet Web 应用程序
        • 8.1.1. “Spring Web MVC 框架”
        • 8.1.2. JAX-RS 和Jersey
        • 8.1.3. 嵌入式 Servlet 容器支持
      • 8.2 反应式网络应用程序
        • 8.2.1. “Spring WebFlux 框架”
        • 8.2.2. 嵌入式反应式服务器支持
        • 8.2.3. 反应式服务器资源配置
      • 8.3. 优雅关机
      • 8.4. spring安全
        • 8.4.1. MVC安全
        • 8.4.2. WebFlux 安全
        • 8.4.3. OAuth2
        • 8.4.4. SAML 2.0
      • 8.5. spring 会话
      • 8.6.GraphQL
      • 8.7. Spring HATEOAS
      • 8.8.接下来读什么
    • 9. 数据
      • 9.1. SQL数据库
      • 9.2. 使用 NoSQL 技术
      • 9.3. 接下来读什么
    • 10. 消息
      • 10.1. JMS
      • 10.2. AMQP
      • 10.3. Apache Kafka 支持
      • 10.4. Apache Pulsar 支持
      • 10.5. RSocket
      • 10.6. Spring Integration
      • 10.7. WebSockets
      • 10.8. What to Read Next
    • 11. IO
      • 11.1. 缓存
      • 11.2. Hazelcast
      • 11.3. Quartz 调度程序
      • 11.4. 发送电子邮件
      • 11.5. 验证
      • 11.6. 调用 REST 服务
      • 11.7. web services
      • 11.8. 使用 JTA 进行分布式事务
      • 11.9. 接下来读什么
    • 12. 容器镜像
  • Spring核心功能
    • 1.IOC容器和Bean简介
      • 1.2. 容器概述
      • 1.3. Bean概述
      • 1.4. 依赖项
        • 1.4.1. 依赖注入
        • 1.4.2. 详细的依赖关系和配置
        • 1.4.3. 使用depends-on
        • 1.4.4. 延迟初始化的 Bean
        • 1.4.5. 自动装配协作者
        • 1.4.6. 方法注入
    • 2. Resources
      • 2.1. 介绍
      • 2.2. Resource接口
      • 2.3. 内置Resource实现
      • 2.4. ResourceLoader接口
      • 2.5. ResourcePatternResolver接口
      • 2.6. ResourceLoaderAware接口
      • 2.7. 资源作为依赖
      • 2.8. 应用程序上下文和资源路径
    • 3. 验证、数据绑定和类型转换
      • 3.1. 使用 Spring 的 Validator 接口进行验证
      • 3.2. 将代码解析为错误消息
      • 3.3. Bean 操作和BeanWrapper
      • 3.4. spring类型转换
      • 3.5. spring字段格式
      • 3.6. 配置全局日期和时间格式
      • 3.7. Java Bean 验证
    • 4. SpEL表达式
    • 5. Spring 面向切面编程
      • 5.1. AOP 概念
      • 5.2. Spring AOP 的能力和目标
      • 5.3. AOP 代理
      • 5.4. @AspectJ 支持
        • 5.4.1. 启用@AspectJ 支持
        • 5.4.2. 声明一个切面
        • 5.4.3. 声明切入点
        • 5.4.4. 声明切点
        • 5.4.5. 切面说明
        • 5.4.6. 切面实例化模型
        • 5.4.7. AOP 示例
      • 5.5. 基于模式的 AOP 支持
      • 5.6. 选择要使用的 AOP 声明样式
      • 5.7. 混合切面类型
      • 5.8. 代理机制
      • 5.9. @AspectJ 代理的程序化创建
      • 5.10. 在 Spring 应用程序中使用 AspectJ
      • 5.11.更多资源
    • 6. Spring AOP API
      • 6.1. Spring中的切入点API
      • 6.2. Spring 中的 Advice API
      • 6.3. Spring 中的 Advisor API
      • 6.4. 使用ProxyFactoryBean创建 AOP 代理
      • 6.5. 简洁的代理定义
      • 6.6. 以编程方式创建 AOP 代理ProxyFactory
      • 6.7. 操作切面对象
      • 6.8. 使用“自动代理”工具
      • 6.9. 使用TargetSource实现
      • 6.10. 定义新的切面类型
    • 7. 空指针安全
    • 8. 数据缓冲器和编解码器
    • 9. 日志
    • 10. 附录
      • 10.1. XML 模式
      • 10.2. 自定义XML Schema
        • 10.2.1. 创作 Schema
        • 10.2.2. 编码一个NamespaceHandler
        • 10.2.3. 使用BeanDefinitionParser
        • 10.2.4. 注册处理程序和模式
        • 10.2.5. 在 Spring XML 配置中使用自定义扩展
        • 10.2.6. 更详细的例子
      • 10.3. 应用程序启动步骤
  • 使用redis实现分布式锁
  • Java 安全标准算法名称
  • JDK 9 JEP
  • JDK 10 JEP
  • 人件
    • 《人件》
    • 第一部分 管理人力资源
      • 01 此时此刻,一个项目正在走向失败
      • 02 干酪汉堡,做一个,卖一个
      • 03 维也纳在等你
      • 04 质量——如果时间允许
      • 05 再谈帕金森定律
      • 06 苦杏素
    • 第二部分 办公环境
      • 07 家具警察
      • 08 “朝九晚五在这里啥也完成不了。”
      • 09 在空间上省钱
      • 间奏曲:生产效率度量和不明飞行物
      • 10 大脑时问与身体时间
      • 11 电话
      • 12 门的回归
      • 13 采取保护步骤
    • 第三部分 正确的人
      • 14 霍恩布洛尔因素
      • 15 谈谈领导力
      • 16 雇一名杂耍演员
      • 17 与他人良好合作
      • 18 童年的终结
      • 19 在这儿很开心
      • 20 人力资本
    • 第四部分 高效团队养成
      • 21 整体大于部分之和
      • 22 黑衣团队
      • 23 团队自毁
      • 24 再谈团队自毁
      • 25 竞争
      • 26 一顿意面晚餐
      • 27 敞开和服
      • 28 团队形成的化学反应
    • 第五部分 沃土
      • 29 自我愈复系统
      • 30 与风险共舞
      • 3l 会议、独白和交流
      • 32 终极管理罪恶得主是……
      • 33 “邪恶”电邮
      • 34 让改变成为可能
      • 35 组织型学习
      • 36 构建社区
    • 第六部分 快乐地工作
      • 37 混乱与秩序
      • 38 自由电子
      • 39 霍尔加·丹斯克
由 GitBook 提供支持
在本页

这有帮助吗?

在GitHub上编辑
  1. Spring核心功能
  2. 5. Spring 面向切面编程

5.10. 在 Spring 应用程序中使用 AspectJ

上一页5.9. @AspectJ 代理的程序化创建下一页5.11.更多资源

最后更新于1年前

这有帮助吗?

到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中,如果您的需求超出了 Spring AOP 单独提供的功能,我们将了解如何使用 AspectJ 编译器或编织器来代替 Spring AOP 或作为 Spring AOP 的补充。

Spring 附带了一个小的 AspectJ 切面库,它在您的发行版中为spring-aspects.jar. 您需要将其添加到您的类路径中才能使用其中的切面。和讨论了这个库的内容以及如何使用它。讨论了如何依赖注入使用 AspectJ 编译器编织的 AspectJ 切面。最后, 中使用 AspectJ 进行加载时编织介绍了使用 AspectJ 的 Spring 应用程序的加载时编织。

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

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

@Configurable注解将一个类标记为符合 Spring 驱动配置的条件。在最简单的情况下,您可以将其纯粹用作标记注解,如以下示例所示:

package com.xyz.myapp.domain;

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

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

当以这种方式用作标记接口时,SpringAccount通过使用与完全限定类型名称com.xyz.myapp.domain.Account(由于 bean 的默认名称是其类型的完全限定名称,因此声明原型定义的一种方便方法是省略id属性,如以下示例所示:

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果要显式指定要使用的原型 bean 定义的名称,可以直接在注解中这样做,如以下示例所示:

package com.xyz.myapp.domain;

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

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

Spring 现在查找名为的 bean 定义并将account其用作配置新Account实例的定义。

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

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

请注意,单独使用注释不会产生任何作用。 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 作用于注释的存在。实质上,该方面说,“从使用 @Configurable 注解的类型的新对象初始化返回后,根据注解的属性使用 Spring 配置新创建的对象”。在此上下文中,“初始化”指的是新实例化的对象(例如,使用 new 运算符实例化的对象)以及正在进行反序列化(例如,通过 readResolve())的可序列化对象。

上一段中的关键词之一是“本质上”。在大多数情况下,“从新对象的初始化返回后”的确切语义是可以的。在这种情况下,“初始化之后”意味着依赖项是在对象构建之后注入的。这意味着依赖项不能在类的构造函数体中使用。如果您希望在构造函数主体运行之前注入依赖项,从而可以在构造函数主体中使用,则需要在 @Configurable声明中定义这个,如下所示:@Configurable(preConstruction = true)

@Configuration
@EnableSpringConfigured
public class AppConfig {
}
<context:spring-configured/>

在配置切面之前创建的对象实例会@Configurable导致向调试日志发出消息,并且不会进行对象配置。一个示例可能是 Spring 配置中的 bean,它在 Spring 初始化时创建域对象。在这种情况下,您可以使用 depends-onbean 属性手动指定 bean 依赖于配置切面。以下示例显示了如何使用该depends-on属性:

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

    <!-- ... -->

</bean>

不要通过 bean 配置器切面激活@Configurable处理,除非您真的想在运行时依赖它的语义。特别是,请确保不要在容器中注册为常规 Spring bean 的 bean 类上使用@Configurable。这样做会导致双重初始化,一次通过容器,一次通过切面。

单元测试@Configurable对象

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

使用多个应用程序上下文

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

考虑一个典型的 Spring Web 应用程序配置,它具有一个共享的父应用程序上下文,它定义了公共业务服务、支持这些服务所需的一切,以及每个 servlet 的一个子应用程序上下文(其中包含特定于该 servlet 的定义)。所有这些上下文共存于同一个类加载器层次结构中,因此AnnotationBeanConfigurerAspect只能保存对其中一个的引用。在这种情况下,我们推荐在共享(父)应用程序上下文中定义@EnableSpringConfigured bean。这定义了您可能想要注入到域对象中的服务。结果是您无法使用@Configurable 机制(这可能不是您想要做的事情)来配置域对象,并引用在子(特定于servlet)上下文中定义的bean。

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

5.10.2. AspectJ 的其他 Spring 切面

除了@Configurable切面之外,spring-aspects.jar还包含一个 AspectJ 切面,您可以使用它来驱动 Spring 的事务管理,以处理使用注解进行@Transactional注解的类型和方法。这主要适用于希望在 Spring 容器之外使用 Spring Framework 的事务支持的用户。

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

类上的@Transactional注解指定执行类中任何公共操作的默认事务语义。

类中方法的@Transactional注解会覆盖类注解(如果存在)给出的默认事务语义。可以注解任何可见性的方法,包括私有方法。直接注解非公共方法是获得执行此类方法的事务分界的唯一方法。

从 Spring Framework 4.2 开始,spring-aspects提供了一个类似的切面,为标准注解javax.transaction.Transactional提供完全相同的功能。检查 JtaAnnotationTransactionAspect更多细节。

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

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        CommonPointcuts.inDomainModel() &&
        this(beanInstance);
}

5.10.3. 使用 Spring IoC 配置 AspectJ 切面

当您将 AspectJ 切面与 Spring 应用程序一起使用时,自然希望并期望能够使用 Spring 配置这些切面。AspectJ 运行时本身负责切面创建,通过 Spring 配置 AspectJ 创建的切面的方式取决于切面使用的 AspectJ 实例化模型(per-xxx子句)。

大多数 AspectJ 切面都是单例切面。这些切面的配置很容易。您可以创建一个引用切面类型的 bean 定义,并包含factory-method="aspectOf"bean 属性。这确保 Spring 通过向 AspectJ 请求它而不是尝试自己创建实例来获取切面实例。以下示例显示了如何使用factory-method="aspectOf"属性:

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> 

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>

非单例方面更难配置。但是,可以通过创建原型 bean 定义并使用 spring-aspects.jar 中的 @Configurable 支持来配置方面实例(一旦 AspectJ 运行时创建了 bean),就可以实现这一点。

如果你有一些@AspectJ 切面想用AspectJ 编织(例如,对域模型类型使用加载时编织)和其他@AspectJ 切面想和Spring AOP 一起使用,并且这些切面都在Spring 中配置,您需要告诉 Spring AOP @AspectJ 自动代理支持配置中定义的 @AspectJ 切面的确切子集应该用于自动代理。 您可以通过在<aop:aspectj-autoproxy/>声明中使用一个或多个<include/>元素来做到这一点。每个<include/>元素指定一个名称模式,只有名称与至少一个模式匹配的 bean 才会用于 Spring AOP 自动代理配置。以下示例显示了如何使用<include/>元素:

<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>

不要被<aop:aspectj-autoproxy/>元素的名称误导。使用它会导致创建 Spring AOP 代理。这里使用了@AspectJ 样式的切面声明,但不涉及 AspectJ 运行时。

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

Spring Framework 为 AspectJ LTW 带来的价值在于能够对编织过程进行更细粒度的控制。'Vanilla' AspectJ LTW 是通过使用 Java (5+) 代理来实现的,该代理在启动 JVM 时通过指定 VM 参数来打开。因此,它是一个 JVM 范围的设置,在某些情况下可能很好,但通常有点过于粗糙。启用 Spring 的 LTW 允许您逐个打开 LTW ClassLoader,这更细粒度,并且在“单 JVM 多应用程序”环境中更有意义(例如在典型的应用程序服务器环境中) )。

第一个例子

假设您是一名应用程序开发人员,他的任务是诊断系统中某些性能问题的原因。与其打破一个分析工具,我们将打开一个简单的分析切面,让我们快速获得一些性能指标。然后,我们可以立即将更细粒度的分析工具应用于该特定区域。

以下示例显示了分析切面,这并不花哨。它是一个基于时间的分析器,使用@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(){}
}

我们还需要创建一个META-INF/aop.xml文件,通知 AspectJ 编织器我们想要将我们的类编织ProfilingAspect到我们的类中。这种文件约定,即在 Java 类路径中存在一个文件(或多个文件)称为META-INF/aop.xml标准 AspectJ。以下示例显示了该aop.xml文件:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>

</aspectj>

现在我们可以继续进行配置的特定于 Spring 的部分。我们需要配置一个LoadTimeWeaver(稍后解释)。这个加载时编织器是负责将一个或多个META-INF/aop.xml文件中的切面配置编织到应用程序中的类中的基本组件。好处是它不需要太多的配置(还有一些选项可以指定,但后面会详细介绍),如下例所示:

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

    <!-- a service object; we will be profiling its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>

    <!-- this switches on the load-time weaving -->
    <context:load-time-weaver/>
</beans>

现在所有必需的工件(切面、META-INF/aop.xml 文件和 Spring 配置)都已就位,我们可以创建以下驱动程序类,并使用一个main(..)方法来演示 LTW 的实际操作:

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

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

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

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

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

Calculating entitlement

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

由于这个 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于通知 Spring bean。该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 容器,然后创建一个完全在 Spring 上下文之外的新StubEntitlementCalculationService实例。分析通知仍然被融入其中。

诚然,这个例子很简单。但是,Spring 中 LTW 支持的基础知识已经在前面的示例中介绍过,本节的其余部分将详细解释每一位配置和使用背后的“原因”。

这个ProfilingAspect例子中使用的可能是基本的,但它非常有用。这是开发时切面的一个很好的例子,开发人员可以在开发期间使用它,然后轻松地从部署到 UAT 或生产中的应用程序的构建中排除。

切面

您在 LTW 中使用的切面必须是 AspectJ 切面。您可以使用 AspectJ 语言本身编写它们,也可以使用 @AspectJ 样式编写切面。那么你的切面都是有效的 AspectJ 和 Spring AOP 切面。此外,编译的切面类需要在类路径上可用。

'META-INF/aop.xml'

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

所需的库 (JARS)

至少,您需要以下库来使用 Spring Framework 对 AspectJ LTW 的支持:

  • spring-aop.jar

  • aspectjweaver.jar

  • spring-instrument.jar

spring 配置

Spring 的 LTW 支持中的关键组件是LoadTimeWeaver接口(在 org.springframework.instrument.classloading包中),以及 Spring 发行版附带的众多实现。LoadTimeWeaver负责在运行时向 java.lang.instrument.ClassFileTransformers添加一个或多个ClassLoader,这为各种有趣的应用程序打开了大门,其中之一恰好是切面的 LTW。

如果您不熟悉运行时类文件转换的想法,请java.lang.instrument在继续之前查看包的 javadoc API 文档。虽然该文档并不全面,但至少您可以看到关键接口和类(供您阅读本节时参考)。

LoadTimeWeaver为特定配置 ApplicationContext可以像添加一行一样简单。(请注意,您几乎肯定需要使用 ApplicationContext作为 Spring 容器——通常, BeanFactory是不够的,因为 LTW 支持使用BeanFactoryPostProcessors.)

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

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者,如果您更喜欢基于 XML 的配置,请使用该 <context:load-time-weaver/>元素。请注意,该元素是在 context命名空间中定义的。下面的例子展示了如何使用<context:load-time-weaver/>:

<?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。默认LoadTimeWeaver是DefaultContextLoadTimeWeaver类,它试图装饰一个自动检测到的LoadTimeWeaver. “自动检测”的确切类型LoadTimeWeaver 取决于您的运行时环境。下表总结了各种LoadTimeWeaver实现:

运行环境

LoadTimeWeaver执行

TomcatLoadTimeWeaver

GlassFishLoadTimeWeaver

JBossLoadTimeWeaver

WebSphereLoadTimeWeaver

WebLogicLoadTimeWeaver

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

InstrumentationLoadTimeWeaver

回退,期望底层的 ClassLoader 遵循通用约定(即addTransformer,可选的getThrowawayClassLoader方法)

ReflectiveLoadTimeWeaver

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

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

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

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

如果使用基于 XML 的配置,则可以将完全限定的类名指定为 <context:load-time-weaver/>元素的weaver-class属性值。同样,以下示例指定了一个ReflectiveLoadTimeWeaver:

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

    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

LoadTimeWeaver稍后可以使用众所周知的名称从 Spring 容器中检索由配置定义和注册的loadTimeWeaver. 请记住,它LoadTimeWeaver仅作为 Spring 的 LTW 基础架构添加一个或多个ClassFileTransformers. 执行 LTW的实际 ClassFileTransformer是ClassPreProcessorAgentAdapter(来自org.aspectj.weaver.loadtime包的)类。有关更多详细信息,请参阅该类的类级别 javadoc ClassPreProcessorAgentAdapter,因为实际如何实现编织的细节超出了本文档的范围。

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

注解值
XML 值
解释

ENABLED

on

AspectJ weaving 已打开,并且在加载时适当地编织切面。

DISABLED

off

LTW 已关闭。在加载时没有编织任何切面。

AUTODETECT

autodetect

如果 Spring LTW 基础结构可以找到至少一个META-INF/aop.xml文件,则 AspectJ weaving 处于打开状态。否则,它会关闭。这是默认值。

特定于环境的配置

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

Tomcat、JBoss、WebSphere、WebLogic

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

<scanning xmlns="urn:jboss:scanning:1.0"/>

通用 Java 应用程序

当在特定实现不支持的环境中需要类检测时LoadTimeWeaver,JVM 代理是通用解决方案。对于这种情况,Spring 提供了 InstrumentationLoadTimeWeaver,它需要特定于 Spring 的(但非常通用的)JVM 代理 spring-instrument.jar,由常见的 @EnableLoadTimeWeaving 和 <context:load-time-weaver/> 设置自动检测。

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

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

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

中找到有关 AspectJ 中各种切入点类型的语言语义的更多信息。

为此,必须使用 AspectJ 编织器编织带注解的类型。您可以使用构建时 Ant 或 Maven 任务来执行此操作(例如,参见 )或加载时编织(参见)。 AnnotationBeanConfigurerAspect本身需要由Spring 配置(为了获得对用于配置新对象的 bean 工厂的引用)。如果使用基于 Java 的配置,则可以添加@EnableSpringConfigured到任何 @Configuration类中,如下所示:

如果您更喜欢基于 XML 的配置,Spring 定义了一个方便的context:spring-configured元素,您可以按如下方式使用它:

加载时编织 (LTW) 是指将 AspectJ 切面编织到应用程序的类文件中的过程,因为它们正在加载到 Java 虚拟机 (JVM) 中。本节的重点是在 Spring Framework 的特定上下文中配置和使用 LTW。本节不是对 LTW 的一般介绍。有关 LTW 的详细信息以及仅使用 AspectJ 配置 LTW(根本不涉及 Spring)的详细信息,请参阅 。

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

现在推销已经结束,让我们先来看一个使用 Spring 的 AspectJ LTW 的快速示例,然后详细介绍示例中介绍的元素。有关完整示例,请参阅 。

此处提供的示例使用 XML 配置。您还可以通过配置和使用 @AspectJ 。具体来说,您可以使用 @EnableLoadTimeWeaving注解作为<context:load-time-weaver/>替代 (详见)。

该文件的结构和内容在 的 LTW 部分中有详细说明。因为该aop.xml文件是 100% AspectJ,所以我们在此不再赘述。

如果使用,还需要:

在

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

在 Red Hat 的或

在 IBM 的

在 Oracle 的

Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 都提供了一个ClassLoader能够进行本地检测的通用应用程序。Spring 的本机 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。,您可以简单地启用加载时编织。具体来说,您无需修改 JVM 启动脚本即可添加: -javaagent:path/to/spring-instrument.jar.

Using AspectJ to Dependency Inject Domain Objects with Spring
AspectJ 的其他 Spring 切面
使用 Spring IoC 配置 AspectJ 切面
在 Spring Framework
您可以在AspectJ Programming Guide
的这个附录
AspectJ 开发环境指南
Spring Framework 中使用 AspectJ 的加载时编织
context命名空间
AspectJ 开发环境指南的 LTW 部分
在某些环境中
Petclinic 示例应用程序
Java 配置
下文
AspectJ 参考文档
Spring 提供的代理来启用检测
如前所述
Apache Tomcat中运行
GlassFish
JBoss AS
WildFly中运行
WebSphere中运行
WebLogic中运行