使用 Byte Buddy 扩展 Spring Boot 的类路径

问题描述

AWS 开发工具包可以通过类路径扫描(查找 software/amazon/awssdk/global/handlers/execution.interceptors 并实例化在那里指定的类)来定位 API 调用拦截器。

我正在编写一个 Java 代理,目的是让我的拦截器可以被 AWS 开发工具包定位。

  • 我的拦截器与 Java 代理捆绑在一起。
  • 我的拦截器实现了 AWS 的 ExecutionInterceptor
  • AWS 开发工具包与我的代理捆绑在一起,因为我希望最终用户提供他们自己的 AWS 开发工具包版本。

对于常规的独立应用程序,这很容易,因为 Java 代理会自动添加到应用程序的运行时类路径中。 AWS 开发工具包可以毫无问题地找到我的拦截器。

然而,这种方法完全打破了 Spring Boot 应用程序,其中 AWS 开发工具包作为 BOOT-INF/lib 下的依赖项捆绑在一起。原因归结为 Spring Boot 的类加载层次结构。可以找到我的拦截器类,但由于无法找到 AWS 的 ExecutionInterceptor,加载失败,因为它加载到层次结构中的“较低”类加载器中。

所以我想我的方法应该是以某种方式修改 Spring Boot 的类加载器搜索。但是,我面临以下问题:

  • 调用代理时,尚未创建 Spring Boot 的“较低”类加载器。
  • 我不完全确定我需要检测什么。

我读过 Byte Buddy 能够在这种“有趣”的情况下提供帮助,但还没有找到使这项工作发挥作用的方法。有什么想法吗?

编辑:我正在寻找一种不需要代码/包装更改的解决方案,因此是 Java 代理方法

编辑:我尝试过的东西)

遵循 Rafael 的回答:SDK 中解析所有拦截器的方法在类 SdkDefaultClientBuilder 中,称为 resolveExecutionInterceptors

以下内容适用于非 SpringBoot 应用程序的独立 JAR:

    public static void installAgent(Instrumentation inst) {
        new AgentBuilder.Default()
            .with(RedeFinitionStrategy.disABLED)
            .type(ElementMatchers.nameEndsWith("SdkDefaultClientBuilder"))
            .transform(
                    new Transformer() {
                @Override
                public Builder<?> transform(Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule module) {
                    return builder.visit(Advice.to(MyAdvice.class).on(ElementMatchers.named("resolveExecutionInterceptors")));
                }
            }
                    ).installOn(inst);
    }

然而,对于 SpringBoot 应用程序,建议似乎根本没有应用。我猜这是因为 SdkDefaultClientBuilder 类型在代理启动时甚至不可用。它在 SpringBoot 的运行时可用,在不同的类加载器中。

解决方法

Byte Buddy 允许您在任何类的任何方法中注入代码,因此您需要找出的第一个也是唯一的主要事情是您的拦截器的实例化位置。这通常可以通过在工作场景中的拦截器的构造函数中设置断点并调查堆栈中的方法来完成。找出发现类的位置,例如读取 software/amazon/awssdk/global/handlers/execution.interceptors 的方法。

确定此方法后,您需要找到一种方法来手动提取代理定义的拦截器并手动添加它们。例如,如果将文件提取的拦截器添加到类型为 List<Interceptor> 的参数中,您可以使用 Byte Buddy 修改此方法以添加代理的方法。

通常,您将 Byte Buddy 的 AgentBuilderAdvice 结合使用来执行此操作。建议让您将代码内联到另一个方法中,例如,假设您找到一个参数类型为 List<Interceptor> 的方法:

class MyAdvice {
  @Advice.OnMethodEnter
  static void enter(@Advice.Argument(0) List<Interceptor> interceptors) {
    interceptors.addAll(MyAgent.loadMyInterceptors());
  }
}

您现在可以通过以下方式将此代码内联到相关方法中:

class MyAgent {
  public static void premain(String arg,Instrumentation inst) {
    new AgentBuilder.Default().type(...).transform((builder,...) -> builder
      .visit(Advice.to(MyAdvice.class).on(...))).install(inst);
  }
}

如果有问题的类在代理的类加载器上不可用,则您可能需要使用 AgentBuilder.Transformer.ForAdvice,其中 Byte Buddy 使用目标和代理类加载器来解析建议。