我试图修改一个已经加载到JVM中的类。我找到的解决方案是这样的:
1.将代理附加到PID指定的JVM。(例如8191)(代码:AttachTest
)
1.从已经加载到JVM中的类中找到您想要修改的类(例如,8191)。
1.使用仪器添加Transformer(代码:AgentMain
)
1.修改类(例如transform
方法中的Person
)(代码:DemoTransformer
)
1.使用retransformClasses
重新转换类
从第一步到第五步都很好,但是在retransformClasses
处有问题。它再次调用了transform
,其中包含修改类的代码。它修改了其他类,我从来不想修改。我认为问题可能发生在addTransformer
或retransformClasses
。但我还是很困惑。那么,如何重新转换一个类?
public class AttachTest {
public static void main(String[] args) throws AttachNotSupportedException,
IOException, AgentLoadException, AgentInitializationException {
String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
String vid = args[0];
VirtualMachine vm = VirtualMachine.attach(vid);
vm.loadAgent(agentPath);
}
}
//代理
public class AgentMain {
public static void agentmain (String agentArgs, Instrumentation inst)
throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {
Class<?> [] allLoadedClasses = inst.getAllLoadedClasses();
String tmpString = null;
for (int i = 0; i<allLoadedClasses.length; i++) {
tmpString = allLoadedClasses[i].getName();
if (0 != tmpString.length()) {
if (-1 != tmpString.lastIndexOf(".")) {
tmpString = tmpString.substring(tmpString.lastIndexOf(".")+1,tmpString.length());
}
if (tmpString.equals("Person")) {
inst.addTransformer(new DemoTransformer(), true);
inst.retransformClasses(allLoadedClasses[i]);
}
}
}
}
}
|
public class DemoTransformer implements ClassFileTransformer {
@Override
public byte[] transform (ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
ModifyMethodTest tm = new ModifyMethodTest(classfileBuffer);
byte[] byteArray = null;
try {
byteArray = tm.modiySleepMethod();
} catch (Exception e) {
e.printStackTrace();
}
return byteArray;
}
}
*输出: 附件 *
javax.management.RuntimeMBeanException: java.lang.RuntimeException: Failed to transform [Person]
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrow(DefaultMBeanServerInterceptor.java:856)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrowMaybeMBeanException(DefaultMBeanServerInterceptor.java:869)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:838)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:761)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1427)
at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:72)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1265)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1360)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:788)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
at sun.rmi.transport.Transport$1.run(Transport.java:159)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:255)
at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:233)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:142)
at com.sun.jmx.remote.internal.PRef.invoke(Unknown Source)
at javax.management.remote.rmi.RMIConnectionImpl_Stub.invoke(Unknown Source)
at javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection.invoke(RMIConnector.java:993)
at AttachStackOverflow.main(AttachStackOverflow.java:57)
Caused by: java.lang.RuntimeException: Failed to transform [Person]
at loaded3.TransformerService.transform(TransformerService.java:75)
at loaded3.TransformerService.transformClass(TransformerService.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:93)
at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:27)
at com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:208)
at com.sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.java:120)
at com.sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.java:262)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:836)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:761)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1427)
at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:72)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1265)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1360)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:788)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
at sun.rmi.transport.Transport$1.run(Transport.java:159)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:124)
at loaded3.TransformerService.transform(TransformerService.java:72)
... 31 more
*输出: 目标值 *
print Call sayHello()
print Hello World!
Supported Redefine
Supported Retransform
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:1
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = java/io/PrintStream [arg2] = println #5
[arg1] = java/io/PrintStream [arg2] = println #13
[arg1] = java/util/concurrent/TimeUnit [arg2] = sleep #22
[arg1] = java/io/PrintStream [arg2] = println #30
sayHello2
consturct Modifymethod
[arg1] = java/io/PrintStream [arg2] = println #5
[arg1] = java/io/PrintStream [arg2] = println #13
<init>
consturct Modifymethod
[arg1] = java/lang/Object [arg2] = <init> #1
main
consturct Modifymethod
[arg1] = Person [arg2] = <init> #4
[arg1] = Person [arg2] = sayHello #9
[arg1] = Person [arg2] = sayHello2 #13
[arg1] = java/lang/InterruptedException [arg2] = printStackTrace #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:2
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = java/io/PrintStream [arg2] = println #5
[arg1] = java/io/PrintStream [arg2] = println #13
[arg1] = java/util/concurrent/TimeUnit [arg2] = sleep #22
[arg1] = java/io/PrintStream [arg2] = println #30
sayHello2
consturct Modifymethod
[arg1] = java/io/PrintStream [arg2] = println #5
[arg1] = java/io/PrintStream [arg2] = println #13
<init>
consturct Modifymethod
[arg1] = java/lang/Object [arg2] = <init> #1
main
consturct Modifymethod
[arg1] = Person [arg2] = <init> #4
[arg1] = Person [arg2] = sayHello #9
[arg1] = Person [arg2] = sayHello2 #13
[arg1] = java/lang/InterruptedException [arg2] = printStackTrace #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
print in sayHello()
print Call sayHello2()
print Hello World!2
1条答案
按热度按时间l5tcr1uw1#
简短回答
长回答
以下是一些建议:
1.将您正在调用的操作分为两个单独的操作:
1.安装代理。这只需要做一次。
1.转换目标类[es]。你可能想做这个 n 次。
1.我将通过在安装代理时注册一个简单的JMXMBean来实现1.2。此MBean应提供类似
public void transformClass(String className)
的操作。并且应该使用对代理获取的Instrumentation示例的引用进行初始化。MBean类、接口和任何必需的第三方类都应该包含在代理的loaded.jar中。它还应该包含您的ModifyMethodTest类(我假设它已经包含了)。1.在安装代理jar的同时,还要从**$JAVA_HOME/lib/management-agent.jar安装管理代理,这将激活管理代理,以便您可以在将要注册的MBean中调用转换操作。
1.设置DemoTransformer类的参数,以接受要转换的类名的 * 内部形式 *。(例如,如果你的二进制类名是 foo.bar.Snafu,那么内部形式将是 foo/bar/Snafu。当您的DemoTransformer示例开始获取转换回调时,忽略所有与您指定的内部表单类名不匹配的类名。(即简单地返回未修改的classfileBuffer)
1.然后,您的transformer MBeantransformClass**操作应该:
1.将传递的类名转换为内部形式。
1.创建一个新的DemoTransformer,传递内部表单类名。
1.使用
Instrumentation.addTransformer(theNewDemoTransformer, true)
注册DemoTransformer示例。1.调用
Instrumentation.retransformClasses(ClassForName(className))
(使用传递给MBean操作的二进制类名)。当这个调用返回时,你的类将被转换。1.用
Intrumentation.removeTransformer(theNewDemoTransformer)
拆下Transformer。以下是一个未经检验的近似我的意思:
Transformer MBean
Transformer服务
Transformer类
特工
代理人
**=
嘿,尼克;是的,这是目前的限制之一(即。Java 5-8)类转换器。引用Instrumentation javadoc的话:
“重新转换可能会更改方法体、常量池和属性。重新转换不得添加、移除或重命名字段或方法,不得更改方法的签名,也不得更改继承。这些限制可能会在未来的版本中取消。在应用转换之前,不会检查、验证和安装类文件字节,如果结果字节出错,此方法将引发异常。”
顺便说一句,对于重定义类,也逐字记录了同样的限制。
因此,您有两个选择:
1.不要添加新方法。这通常是非常有限的,并且不允许使用非常常见的字节码AOP模式,如方法 Package 。根据您使用的字节码操作库,您可能能够将所需的所有功能注入现有方法。有些库比其他库更容易做到这一点。或者,我应该说,有些库会比其他库更容易做到这一点。
1.在类加载之前转换类。除了不通过调用retransformClasses触发转换之外,它使用了与我们已经讨论过的代码相同的通用模式。相反,您注册ClassFileTransformer以在加载类之前执行转换,并且您的目标类将在第一次加载类时被修改。在这种情况下,只要最终产品仍然可以被验证,您就可以自由地以任何方式修改类。将应用程序打到冲头上(即在应用程序加载类之前注册ClassFileTransformer)很可能需要像javaagent这样的命令,尽管如果您严格控制应用程序的生命周期,可以在更传统的应用程序层代码中完成此操作。正如我所说的,您只需要确保在加载目标类之前注册了Transformer。
你可以使用的另一个变体是通过使用一个新的类加载器来 * 模拟 * 一个全新的类。如果你创建了一个新的隔离类加载器,它不会委托给现有的[loaded]类,但是可以访问[unloaded]目标类的字节码,你实际上是在复制上面的需求#2,因为JVM认为这是一个全新的类。
**=
在你最后的评论中,我觉得我有点不知道你在哪里。无论如何,Oracle JDK 1.6绝对支持重新转换。我对ASM不是很熟悉,但是你发布的最后一个错误表明ASM转换以某种方式修改了不允许的类模式,所以重新转换失败了。
我想一个工作的例子会增加更多的清晰度。与上面相同的类(加上一个名为Person的测试类)是here。有几个修改/添加:
1.二进制类名
1.仪器的方法名称
1.一个匹配方法签名的[正则]表达式。(如果为null或空,则匹配所有签名)
1.实际的字节码修改是使用ModifyMethodTest类中的Javassist完成的。插装所做的就是添加一个 System.out.println,看起来像这样:
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
1.代理程序(包含演示程序的Main)只会自行安装代理程序和转换服务。(用于开发/演示目的,但仍然可以与其他JVM一起使用)
1.代理自安装后,主线程创建一个Person示例并循环,调用Person的两个 sayHello 方法。
在转换之前,输出如下所示。
Person有2个 sayHello 方法,一个接受一个int,另一个接受一个String。(String只打印循环索引的负数)。
一旦我启动了Agent,代理就安装好了,Person在循环中被调用,我使用JConsole连接到JVM:
我导航到TransformerService MBean并调用transformClass操作。我提供了完全限定的class [binary]名称,要检测的方法名称,以及一个正则表达式**(I)V**,它 * 只 * 匹配 sayHello 方法,该方法将int作为参数。(或者我可以提供**. *,或者不提供任何内容来匹配所有重载)。我来执行任务。
现在,当我回到运行的JVM并检查输出时:
好了。方法已安装。
请记住,允许重新转换的原因是Javassist字节码修改除了将代码注入现有方法之外没有进行任何更改。
说得通吗