【问题标题】:Alter value of a static field during class loading using a Java agent使用 Java 代理在类加载期间更改静态字段的值
【发布时间】:2014-11-20 19:48:26
【问题描述】:

我们有一个调用类 X 的一些方法的 java 进程。类 X 有 timeout 静态字段,它决定线程在出现错误时应该等待多长时间。现在,我想在不更改 java 进程的情况下更改该值(我不想要部署,并且此更改是实验性的)。如何使用 java 代理将此超时值更改为 1 分钟 (1*60*1000)

Class X {
    ....
    // timeout = 5 minutes
    private static long timeout = 5*60*1000;
    ....
}

简而言之,如何编写java代理来改变一个静态变量的值。我已经阅读了一些教程,但没有一个解释如何做到这一点。我无权访问 main 方法。该项目由 IOC 容器运行。

谢谢,

里士

【问题讨论】:

  • 为什么不把它从你的代码中抽象出来,放到一个属性文件中呢?然后,如果需要,您可以在运行时更改属性文件,并且每当您的类被实例化时,它将在那时读取属性文件。此外,静态字段不是final,因此使用 setter/getter,您也可以在运行时操作它的值。
  • 您可以使用反射并访问字段tutorials.jenkov.com/java-reflection/…

标签: java classloader bytecode-manipulation javaagents


【解决方案1】:

使用反射,您可以很容易地实现这一点:

class EasyFieldAlterationAgent {
  public static void premain(String args) throws Exception {
    Field field = X.class.getDeclaredField("timeout");
    field.setAccessible(true);
    field.setValue(null, 42L); // set your value here.
  }
}

请注意,这将更改的字段不是之前,而是就在类加载时间之后。如果这对您来说不可能,您也可以重新定义类本身,我只建议在以下情况下使用:

  1. 您的安全设置不允许您使用反射。
  2. 您需要在执行类的类型初始化程序之前更改该值。

如果您希望在加载类之前真正更改字段,那么您很幸运,您希望更改既是static 又定义原始值的字段的值。此类字段将其值直接存储在字段的位置。使用代理,您可以定义一个 ClassFileTransformer,它只是动态地更改字段的值。 ASM 是实现这种简单转换的好工具。使用此工具,您可以大致如下实现您的代理:

class FieldAlterationAgent {
  public static void premain(String args, Instrumentation inst) {
    instrumentation.addTransformer(new ClassFileTransformer() {
      @Override
      public void byte[] transform(ClassLoader loader,
                                   String className,
                                   Class<?> classBeingRedefined,
                                   ProtectionDomain protectionDomain,
                                   byte[] classfileBuffer)
          throws IllegalClassFormatException {
        if (!className.equals("X") {
          return classFileBuffer;
        }
        ClassWriter classWriter = new ClassWriter(new ClassVisitor() {
          @Override
          public FieldVisitor visitField(int access, 
                                         String name, 
                                         String desc, 
                                         String signature, 
                                         Object value) {
            if(name.equals("timeout") {
              value = 42L; // set value here, make sure its a long!
            }
            return super.visitField(access, name, desc, signature, value);
          }
        }, 0);
        new ClassReader(classFileBuffer).accept(classWriter);
        return classWriter.toByteArray();
      }
    });
  }
}

您可以看出后一个版本需要更多代码,并且需要将您的代理与其 ASM 依赖项打包在一起。

要应用代理,请将任一类放入jar file and put it onto the agent path

【讨论】:

  • 谢谢。在第一个示例中,您如何访问 X。X 是如何在 premain 中传递的?
  • 如果你的类在类路径上可用,系统类加载器会查找它。 Premain 传递了命令行参数,您可以在其中传递信息。否则,硬编码或设置系统属性。
  • java 代理是一个不同的 jar,它只有转换器类和包含 premain 方法的类。我的所有其他类都在没有代理运行的原始 jar 中。
  • 将您的应用程序类添加为 编译时 依赖项。代理类由系统类加载器加载。然后,您的应用程序对此类加载器可见。如果您正在运行的应用程序服务器的类对系统类加载器不可见,则需要使用策略二,仅将类命名为字符串。
  • 谢谢,我去看看asm。我想这就是我需要的。
猜你喜欢
  • 1970-01-01
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 2020-01-14
  • 1970-01-01
  • 2011-03-19
相关资源
最近更新 更多