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");
}
}