在 Infra 应用程序中使用 AspectJ

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

Infra 附带了一个小的 AspectJ 切面库,在你的发行版中作为独立的 infra-aspects.jar 提供。 你需要将其添加到类路径中才能使用其中的切面。使用 AspectJ 依赖注入 Infra 领域对象AspectJ 的其他 Infra 切面 讨论了 该库的内容以及如何使用它。使用 Infra IoC 配置 AspectJ 切面 讨论了如何 依赖注入使用 AspectJ 编译器织入的 AspectJ 切面。最后, TODAY Framework 中的 AspectJ 加载时织入 介绍了使用 AspectJ 的 Infra 应用程序的加载时织入。

使用 AspectJ 依赖注入 Infra 领域对象

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

@Configurable 注解将类标记为有资格进行 Infra 驱动的配置。在最简单的情况下, 你可以纯粹将其用作标记注解,如下例所示:

  • Java

package com.xyz.domain;

import infra.beans.factory.annotation.Configurable;

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

当以这种方式用作标记接口时,Infra 使用与完全限定类型名称(com.xyz.domain.Account) 相同的名称的 bean 定义(通常是原型作用域)来配置注解类型的新实例(在本例中为 Account)。 由于通过 XML 定义的 bean 的默认名称是其类型的完全限定名称,因此声明原型定义的便捷方法是 省略 id 属性,如下例所示:

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

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

  • Java

package com.xyz.domain;

import infra.beans.factory.annotation.Configurable;

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

Infra 现在会查找名为 account 的 bean 定义,并使用该定义来配置新的 Account 实例。

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

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

请注意,仅使用注解本身不会起任何作用。infra-aspects.jar 中的 AnnotationBeanConfigurerAspect 才是对注解的存在起作用的组件。本质上,该切面表示: “在从初始化带有 @Configurable 注解的类型的新对象返回后,根据注解的属性使用 Infra 配置新创建的对象”。 在这种情况下,“初始化”指的是新实例化的对象(例如,使用 new 操作符实例化的对象) 以及正在进行反序列化的 Serializable 对象(例如,通过 readResolve())。

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

  • Java

@Configurable(preConstruction = true)

你可以在 AspectJ 编程指南此附录 中找到有关 AspectJ 中各种切点类型的语言语义的更多信息。

为此,必须使用 AspectJ 织入器织入带注解的类型。你可以使用构建时的 Ant 或 Maven 任务来执行此操作 (例如,参见 AspectJ 开发环境指南) 或加载时织入(参见 TODAY Framework 中的 AspectJ 加载时织入)。 AnnotationBeanConfigurerAspect 本身需要由 Infra 配置(以便获得用于配置新对象的 bean 工厂的引用)。 你可以定义相关配置如下:

include-code::./ApplicationConfiguration[tag=snippet,indent=0]

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

<bean id="myService"
    class="com.xyz.service.MyService"
    depends-on="infra.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

  <!-- ... -->

</bean>
除非你真的打算在运行时依赖其语义,否则不要通过 bean 配置器切面激活 @Configurable 处理。 特别是,请确保不要在注册为容器的常规 Infra bean 的 bean 类上使用 @Configurable。 这样做会导致双重初始化,一次通过容器,一次通过切面。

单元测试 @Configurable 对象

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

AspectJ 的其他 Infra 切面

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

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

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

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

infra-aspects 提供了类似的切面,为标准的 jakarta.transaction.Transactional 注解 提供完全相同的功能。有关更多详细信息,请查看 JtaAnnotationTransactionAspect

对于希望使用 Infra 配置和事务管理支持但不希望(或不能)使用注解的 AspectJ 程序员, infra-aspects.jar 还包含 abstract 切面,你可以扩展这些切面以提供自己的切点定义。 有关更多信息,请参阅 AbstractBeanConfigurerAspectAbstractTransactionAspect 切面的源代码。 作为示例,以下摘录显示了如何编写一个切面,使用匹配完全限定类名的原型 bean 定义来配置领域模型中定义的所有对象实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

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

  // 创建新 bean(领域模型中的任何对象)
  protected pointcut beanCreation(Object beanInstance) :
    initialization(new(..)) &&
    CommonPointcuts.inDomainModel() &&
    this(beanInstance);
}

使用 Infra IoC 配置 AspectJ 切面

当你在 Infra 应用程序中使用 AspectJ 切面时,很自然地希望并期望能够使用 Infra 配置此类切面。 AspectJ 运行时本身负责切面的创建,而通过 Infra 配置 AspectJ 创建的切面的方式 取决于切面使用的 AspectJ 实例化模型(per-xxx 子句)。

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

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

  <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 注意 factory-method="aspectOf" 属性

非单例切面更难配置。但是,可以通过创建原型 bean 定义并使用 infra-aspects.jar 中的 @Configurable 支持在 AspectJ 运行时创建 bean 后配置切面实例来实现。

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

<aop:aspectj-autoproxy>
	<aop:include name="thisBean"/>
	<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被 <aop:aspectj-autoproxy/> 元素的名称误导。使用它会导致创建 Infra AOP 代理。 这里使用了 @AspectJ 风格的切面声明,但并未涉及 AspectJ 运行时。

TODAY Framework 中的 AspectJ 加载时织入

加载时织入 (LTW) 指的是在 Java 虚拟机 (JVM) 加载应用程序的类文件时,将 AspectJ 切面织入其中的过程。 本节的重点是在 TODAY Framework 的特定上下文中配置和使用 LTW。本节不是 LTW 的一般介绍。 有关 LTW 的详细信息以及仅使用 AspectJ(完全不涉及 Infra)配置 LTW 的信息,请参阅 AspectJ 开发环境指南的 LTW 部分

TODAY Framework 为 AspectJ LTW 带来的价值在于能够对织入过程进行更细粒度的控制。 “原生” AspectJ LTW 是通过使用 Java (5+) 代理来实现的,该代理通过在启动 JVM 时指定 VM 参数来开启。 因此,这是一个 JVM 范围的设置,在某些情况下可能没问题,但通常有点过于粗糙。 启用 Infra 的 LTW 允许你在每个 ClassLoader 的基础上开启 LTW,这更加细粒度, 并且在“单 JVM 多应用程序”环境(例如典型的应用服务器环境)中更有意义。

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

现在推销结束,让我们先通过一个使用 Infra 的 AspectJ LTW 快速示例,然后详细介绍示例中引入的元素。 有关完整示例,请参阅 Petclinic 示例应用程序

第一个示例

假设你是一名应用程序开发人员,被指派诊断系统中某些性能问题的原因。 我们将开启一个简单的性能分析切面,以便快速获取一些性能指标,而不是使用分析工具。 然后,我们可以紧接着对该特定区域应用更细粒度的分析工具。

这里展示的示例使用 XML 配置。你也可以使用 Java 配置 来配置和使用 @AspectJ。 具体来说,你可以使用 @EnableLoadTimeWeaving 注解作为 <context:load-time-weaver/> 的替代方案 (详情请参见 下文)。

下例显示了性能分析切面,它并不花哨。它是一个基于时间的分析器,使用了 @AspectJ 风格的切面声明:

  • Java

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import infra.util.StopWatch;
import infra.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 * com.xyz..*.*(..))")
  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>
    <!-- 仅织入我们特定于应用程序的包和子包中的类 -->
    <include within="com.xyz..*"/>
  </weaver>

  <aspects>
    <!-- 仅织入此切面 -->
    <aspect name="com.xyz.ProfilingAspect"/>
  </aspects>

</aspectj>
建议仅织入特定的类(通常是应用程序包中的类,如上面的 aop.xml 示例所示), 以避免副作用,例如 AspectJ 转储文件和警告。从效率的角度来看,这也是最佳实践。

现在我们可以继续进行 Infra 特定的配置部分。我们需要配置一个 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">

  <!-- 服务对象;我们将分析其方法 -->
  <bean id="entitlementCalculationService"
      class="com.xyz.StubEntitlementCalculationService"/>

  <!-- 这将开启加载时织入 -->
  <context:load-time-weaver/>
</beans>

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

  • Java

package com.xyz;

// imports

public class Main {

  public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

    EntitlementCalculationService service =
        ctx.getBean(EntitlementCalculationService.class);

    // 性能分析切面被“织入”到此方法执行周围
    service.calculateEntitlement();
  }
}

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

java -javaagent:C:/projects/xyz/lib/infra-instrument.jar com.xyz.Main

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

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

Calculating entitlement

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

由于此 LTW 是通过使用完整的 AspectJ 实现的,因此我们不仅限于通知 Infra bean。 以下对 Main 程序的轻微变体产生了相同的结果:

  • Java

package com.xyz;

// imports

public class Main {

  public static void main(String[] args) {
    new ClassPathXmlApplicationContext("beans.xml");

    EntitlementCalculationService service =
        new StubEntitlementCalculationService();

    // 性能分析切面将被“织入”到此方法执行周围
    service.calculateEntitlement();
  }
}

请注意,在前面的程序中,我们引导 Infra 容器,然后完全在 Infra 上下文之外创建一个 StubEntitlementCalculationService 的新实例。性能分析通知仍然被织入。

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

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

切面

你在 LTW 中使用的切面必须是 AspectJ 切面。你可以使用 AspectJ 语言本身编写它们, 也可以使用 @AspectJ 风格编写切面。你的切面既是有效的 AspectJ 切面,也是有效的 Infra AOP 切面。 此外,编译后的切面类需要在类路径上可用。

META-INF/aop.xml

AspectJ LTW 基础设施是通过使用 Java 类路径上的一个或多个 META-INF/aop.xml 文件配置的 (直接在类路径上,或者更典型的是在 jar 文件中)。例如:

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

  <weaver>
    <!-- 仅织入我们特定于应用程序的包和子包中的类 -->
    <include within="com.xyz..*"/>
  </weaver>

</aspectj>
建议仅织入特定的类(通常是应用程序包中的类,如上面的 aop.xml 示例所示), 以避免副作用,例如 AspectJ 转储文件和警告。从效率的角度来看,这也是最佳实践。

AspectJ 参考文档 的 LTW 部分详细介绍了 此文件的结构和内容。因为 aop.xml 文件是 100% AspectJ,所以我们这里不再进一步描述它。

必需的库 (JARS)

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

  • infra-aop.jar

  • aspectjweaver.jar

如果你使用 Infra 提供的代理来启用检测,还需要:

  • infra-instrument.jar

Infra 配置

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

如果你不熟悉运行时类文件转换的概念,请在继续之前查看 java.lang.instrument 包的 javadoc API 文档。 虽然该文档并不详尽,但至少你可以看到关键接口和类(以便在阅读本节时作为参考)。

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

要启用 TODAY Framework 的 LTW 支持,你需要配置一个 LoadTimeWeaver,如下所示:

@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}

前面的配置会自动为你定义和注册许多特定于 LTW 的基础设施 bean,例如 LoadTimeWeaverAspectJWeavingEnabler。 默认的 LoadTimeWeaverDefaultContextLoadTimeWeaver 类,它尝试装饰一个自动检测到的 LoadTimeWeaver。 “自动检测”到的 LoadTimeWeaver 的确切类型取决于你的运行时环境。 下表总结了各种 LoadTimeWeaver 实现:

Table 1. DefaultContextLoadTimeWeaver LoadTimeWeavers
运行时环境 LoadTimeWeaver 实现

Apache Tomcat 中运行

TomcatLoadTimeWeaver

使用 Infra InstrumentationSavingAgent 启动的 JVM (java -javaagent:path/to/infra-instrument.jar)

InstrumentationLoadTimeWeaver

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

ReflectiveLoadTimeWeaver

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

要配置特定的 LoadTimeWeaver,请实现 LoadTimeWeavingConfigurer 接口并覆盖 getLoadTimeWeaver() 方法 (或使用 XML 等效项)。下例指定了一个 ReflectiveLoadTimeWeaver

@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {

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

配置定义并注册的 LoadTimeWeaver 稍后可以通过知名名称 loadTimeWeaver 从 Infra 容器中检索。 请记住,LoadTimeWeaver 仅作为 Infra LTW 基础设施添加一个或多个 ClassFileTransformers 的机制存在。 执行 LTW 的实际 ClassFileTransformerClassPreProcessorAgentAdapter(来自 org.aspectj.weaver.loadtime 包)类。 有关更多详细信息,请参阅 ClassPreProcessorAgentAdapter 类的类级别 javadoc, 因为实际如何实现织入的细节超出了本文档的范围。

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

Table 2. AspectJ weaving 属性值
注解值 XML 值 解释

ENABLED

on

AspectJ 织入开启,切面在加载时适当地被织入。

DISABLED

off

LTW 关闭。加载时不会织入任何切面。

AUTODETECT

autodetect

如果 Infra LTW 基础设施可以找到至少一个 META-INF/aop.xml 文件, 则 AspectJ 织入开启。否则,它关闭。这是默认值。

特定于环境的配置

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

Tomcat, JBoss, WildFly

Tomcat 和 JBoss/WildFly 提供了一个通用的应用程序 ClassLoader,能够进行本地检测。 Infra 原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 织入。 你可以简单地启用加载时织入,如 前面所述。 具体来说,你不需要修改 JVM 启动脚本来添加 -javaagent:path/to/infra-instrument.jar

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

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

通用 Java 应用程序

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

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

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

请注意,这需要修改 JVM 启动脚本,这可能会阻止你在应用服务器环境中使用它 (取决于你的服务器和操作策略)。话虽如此,对于每个 JVM 一个应用程序的部署,例如独立的 Infra 应用程序,通常你可以控制整个 JVM 设置。