【问题标题】:Using Byte Buddy to expand Spring Boot's classpath使用 Byte Buddy 扩展 Spring Boot 的类路径
【发布时间】:2021-02-24 11:43:44
【问题描述】:

AWS SDK 可以通过类路径扫描定位 API 调用拦截器(查找 software/amazon/awssdk/global/handlers/execution.interceptors 并在此处实例化指定的类)。

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

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

对于常规的独立应用程序,这是不费吹灰之力的,因为 Java 代理会自动添加到应用程序的运行时类路径中。 AWS SDK 可以毫无问题地找到我的拦截器。

但是,这种方法与 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 的运行时可用,在不同的类加载器中。

【问题讨论】:

  • 您可以创建一个具有“正常”结构的 jar,而不是“BOOT-INF/lib 以供任何需要使用的东西。您可以查看 spring boot maven 插件docs.spring.io/spring-boot/docs/1.1.2.RELEASE/reference/html/…跨度>
  • 对。我应该添加(并且很快会编辑问题)——我需要一个不需要更改代码或重新打包现有应用程序的解决方案
  • 如果我理解正确,您的代理使用的类是通过不同的类加载器加载的?
  • @JohannesKuhn 是的
  • 好吧,那么最简单的方法就是使用反射。

标签: instrumentation byte-buddy aws-java-sdk javaagents


【解决方案1】:

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

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

通常,您将 Byte Buddy 的 AgentBuilderAdvice 结合使用。建议让您将代码内联到另一个方法中,例如,假设您找到一个带有List&lt;Interceptor&gt; 类型参数的方法:

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 使用目标和代理类加载器解决建议。

【讨论】:

  • 我已经尝试过这种方法(参见我最近的编辑)。谢谢你。但是,这适用于不是 SpringBoot 应用程序的独立 JAR。对于 SpringBoot,注入没有发生,我猜这是由于 SpringBoot 的类加载器拓扑。在运行代理时,甚至没有任何 AWS 开发工具包类是已知的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-13
  • 2015-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多