Java:无法在附加的检测期间重新定义类

Java:无法在附加的检测期间重新定义类,java,instrumentation,javaagents,Java,Instrumentation,Javaagents,我有一个简单的应用程序,它在循环中创建并使用ToChange类的实例: public class ToChange { private int i; public ToChange(int i) { this.i = i; } public void print() { System.out.println(i); } } 当应用程序运行时,控制台中有这样的行: 0 1 ------------ 0 2 ------------ 0 3 ---------

我有一个简单的应用程序,它在循环中创建并使用
ToChange
类的实例:

public class ToChange {
  private int i;
  public ToChange(int i) {
    this.i = i;
  }

  public void print() {
    System.out.println(i);
  }
}
当应用程序运行时,控制台中有这样的行:

0
1
------------
0
2
------------
0
3
------------
我还有一个
attach
应用程序,它加载一个代理并将其附加到运行中的JVM。 它有两个参数,第一个是运行JVM进程的
pid
,第二个是将要连接的代理的路径:

// The main class of the attach application
public class Main {
  public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
    String processId = args[0];
    String agentJar = args[1];
    VirtualMachine virtualMachine = VirtualMachine.attach(processId);
    try {
      virtualMachine.loadAgent(agentJar);
    } catch (Exception error) {
      error.printStackTrace();
    } finally {
      virtualMachine.detach();
    }
  }
}
最后是代理,它重新定义了
ToChange
类:

public class MyAgentMain {
  public static void agentmain(String arg, Instrumentation instrumentation) throws Exception {
    System.out.println("Agent mark 1");
    Class<?> toChange = Class.forName("org.abc.security.attach.application.ToChange");

    System.out.println("Agent mark 2");
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    try (InputStream input = MyAgentMain
            .class
            .getResourceAsStream("/org/abc/security/attach/application/ToChange.class")) {
      System.out.println("input reference: " + input);
      byte[] buffer = new byte[1024];
      int length;
      while ((length = input.read(buffer)) != -1) {
        output.write(buffer, 0, length);
      }
      System.out.println("Agent mark 3");
    } catch (Exception error) {
      error.printStackTrace();
    }

    instrumentation.redefineClasses(new ClassDefinition(toChange, output.toByteArray()));
    System.out.println("Agent mark 4");
  }
}
ToChange
类更改为以下内容:

Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 11
Agent-Class: org.abc.security.attach.agent.MyAgentMain
Can-Redefine-Classes: true
public class ToChange {
  private int i;

  public ToChange(int i) {
    this.i = i;
  }

  public void print() {
    System.out.println("Changed:" + this.i);
  }
}
我执行附加应用程序,传递正在运行的
MyApplication
的pid和代理位置:

java——添加模块jdk.attach-jar attach-0.1.0-SNAPSHOT.jar$PID agent-0.1.0-SNAPSHOT.jar

我在
MyApplication
控制台中看到代理生成的
println
s,在
MyApplication
控制台和代理控制台中都没有错误

但是,在代理完成后,我仍然在
MyApplication
中看到相同的输出:

------------
0
7
------------
Agent mark 1
Agent mark 2
input reference: java.io.BufferedInputStream@540e6237
Agent mark 3
Agent mark 4
0
8
------------
0
9
------------
我希望,由于没有错误,插装成功,
ChangeTo
类被重新定义,
println
s不同。 然而,事实并非如此

我做错了什么


编辑以回答我的问题

@johannes kuhn的回答让我想到尝试更改代理中重新定义的类位置,以避免与原始类位于同一路径下

似乎最终插入指令的
MyApplication
的“资源”保持不变,因为重新定义的类在资源中的路径与旧类相同。更改ToChange类在代理资源中的位置,成功了

为清楚起见,以下是工作的
MyAgentMain

public class MyAgentMain {
  public static void agentmain(String arg, Instrumentation instrumentation) throws Exception {
    System.out.println("Agent mark 1");
    Class<?> license = Class.forName("org.abc.security.attach.application.ToChange");

    System.out.println("Agent mark 2");
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    try (InputStream input = MyAgentMain
            .class
            .getResourceAsStream("/ToChange.class")) { // THE NON-WORKING VERSION HAD /org/abc/security/attach/application/ToChange.class
      System.out.println("input reference: " + input);
      byte[] buffer = new byte[1024];
      int length;
      while ((length = input.read(buffer)) != -1) {
        output.write(buffer, 0, length);
      }
      System.out.println("Agent mark 3");
    } catch (Exception error) {
      error.printStackTrace();
    }

    instrumentation.redefineClasses(new ClassDefinition(license, output.toByteArray()));
    System.out.println("Agent mark 4");
  }
}

公共类MyAgentMain{
公共静态void agentmain(字符串arg,检测)引发异常{
系统输出打印号(“代理标记1”);
Class license=Class.forName(“org.abc.security.attach.application.ToChange”);
系统输出打印号(“代理标记2”);
ByteArrayOutputStream输出=新建ByteArrayOutputStream();
try(InputStream输入=MyAgentMain
.类
.getResourceAsStream(“/ToChange.class”){//非工作版本有/org/abc/security/attach/application/ToChange.class
System.out.println(“输入参考:”+输入);
字节[]缓冲区=新字节[1024];
整数长度;
而((长度=输入。读取(缓冲区))!=-1){
输出.写入(缓冲区,0,长度);
}
系统输出打印号(“代理标记3”);
}捕获(异常错误){
错误。printStackTrace();
}
重定义类(新的类定义(license,output.toByteArray());
系统输出打印号(“代理标记4”);
}
}

您是否加载了正确的字节码?java代理总是使用系统类加载器加载,并且系统类加载器搜索路径附加了代理jar文件。我猜
MyAgentMain.class.getResourceAsStream(“/org/abc/security/attach/application/ToChange.class”)
返回原始字节码。一种快速测试方法是查看字节数组的长度和文件大小。您知道,您可以将任何类作为资源加载,即使该类不在资源目录中。尝试重命名文件。您可以在问题上发布答案,而不是编辑问题以提供答案,然后将其标记为已接受(2天后),这样人们就可以清楚地看到您的问题已解决。@kriegaex看来,您对“重定义”的作用有错误的理解。它只适用于已经加载的类,否则,它不会是一个重定义操作(不,它不会进行重传)。当您只想进行加载时转换时,根本不需要调用重定义,只需注册转换器就足够了。
public class MyAgentMain {
  public static void agentmain(String arg, Instrumentation instrumentation) throws Exception {
    System.out.println("Agent mark 1");
    Class<?> license = Class.forName("org.abc.security.attach.application.ToChange");

    System.out.println("Agent mark 2");
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    try (InputStream input = MyAgentMain
            .class
            .getResourceAsStream("/ToChange.class")) { // THE NON-WORKING VERSION HAD /org/abc/security/attach/application/ToChange.class
      System.out.println("input reference: " + input);
      byte[] buffer = new byte[1024];
      int length;
      while ((length = input.read(buffer)) != -1) {
        output.write(buffer, 0, length);
      }
      System.out.println("Agent mark 3");
    } catch (Exception error) {
      error.printStackTrace();
    }

    instrumentation.redefineClasses(new ClassDefinition(license, output.toByteArray()));
    System.out.println("Agent mark 4");
  }
}