[英]how to retransform a class at runtime
我正在修改已经加载到jvm中的类。 我找到的解决方案是:
transform
方法中修改类(例如Person)(代码:DemoTransformer) 从第1步到第5步工作正常,但在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
简答
答案很长
以下是一些建议:
public void transformClass(String className)
。 并且应该通过引用代理程序获取的Instrumentation实例进行初始化。 MBean类,接口和任何所需的第三方类应包含在代理程序的loaded.jar中 。 它还应该包含您的ModifyMethodTest类(我假设它已经这样做了)。 Instrumentation.addTransformer(theNewDemoTransformer, true)
注册DemoTransformer实例。 Instrumentation.retransformClasses(ClassForName(className))
(将二进制类名传递给MBean操作)。 当此调用返回时,您的类将被转换。 Intrumentation.removeTransformer(theNewDemoTransformer)
删除变换器。 以下是我未经测试的近似值:
变形金刚MBean
public interface TransformerServiceMBean {
/**
* Transforms the target class name
* @param className The binary name of the target class
*/
public void transformClass(String className);
}
变压器服务
public class TransformerService implements TransformerServiceMBean {
/** The JVM's instrumentation instance */
protected final Instrumentation instrumentation;
/**
* Creates a new TransformerService
* @param instrumentation The JVM's instrumentation instance
*/
public TransformerService(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
}
/**
* {@inheritDoc}
* @see com.heliosapm.shorthandexamples.TransformerServiceMBean#transformClass(java.lang.String)
*/
@Override
public void transformClass(String className) {
Class<?> targetClazz = null;
ClassLoader targetClassLoader = null;
// first see if we can locate the class through normal means
try {
targetClazz = Class.forName(className);
targetClassLoader = targetClazz.getClassLoader();
transform(targetClazz, targetClassLoader);
return;
} catch (Exception ex) { /* Nope */ }
// now try the hard/slow way
for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
if(clazz.getName().equals(className)) {
targetClazz = clazz;
targetClassLoader = targetClazz.getClassLoader();
transform(targetClazz, targetClassLoader);
return;
}
}
throw new RuntimeException("Failed to locate class [" + className + "]");
}
/**
* Registers a transformer and executes the transform
* @param clazz The class to transform
* @param classLoader The classloader the class was loaded from
*/
protected void transform(Class<?> clazz, ClassLoader classLoader) {
DemoTransformer dt = new DemoTransformer(clazz.getName(), classLoader);
instrumentation.addTransformer(dt, true);
try {
instrumentation.retransformClasses(clazz);
} catch (Exception ex) {
throw new RuntimeException("Failed to transform [" + clazz.getName() + "]", ex);
} finally {
instrumentation.removeTransformer(dt);
}
}
}
类变压器
public class DemoTransformer implements ClassFileTransformer {
/** The internal form class name of the class to transform */
protected String className;
/** The class loader of the class */
protected ClassLoader classLoader;
/**
* Creates a new DemoTransformer
* @param className The binary class name of the class to transform
* @param classLoader The class loader of the class
*/
public DemoTransformer(String className, ClassLoader classLoader) {
this.className = className.replace('.', '/');
this.classLoader = classLoader;
}
/**
* {@inheritDoc}
* @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.equals(this.className) && loader.equals(classLoader)) {
return new ModifyMethodTest(classfileBuffer).modiySleepMethod();
}
return classfileBuffer;
}
}
中介
public class AgentMain {
public static void agentmain (String agentArgs, Instrumentation inst) throws Exception {
TransformerService ts = new TransformerService(inst);
ObjectName on = new ObjectName("transformer:service=DemoTransformer");
// Could be a different MBeanServer. If so, pass a JMX Default Domain Name in agentArgs
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(ts, on);
// Set this property so the installer knows we're already here
System.setProperty("demo.agent.installed", "true");
}
}
代理安装程序
public class AgentInstaller {
/**
* Installs the loader agent on the target JVM identified in <code>args[0]</code>
* and then transforms all the classes identified in <code>args[1..n]</code>.
* @param args The target JVM pid in [0] followed by the classnames to transform
*/
public static void main(String[] args) {
String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
String vid = args[0];
VirtualMachine vm = VirtualMachine.attach(vid);
// Check to see if transformer agent is installed
if(!vm.getSystemProperties().contains("demo.agent.installed")) {
vm.loadAgent(agentPath);
// that property will be set now,
// and the transformer MBean will be installed
}
// Check to see if connector is installed
String connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress", null);
if(connectorAddress==null) {
// It's not, so install the management agent
String javaHome = vm.getSystemProperties().getProperty("java.home");
File managementAgentJarFile = new File(javaHome + File.separator + "lib" + File.separator + "management-agent.jar");
vm.loadAgent(managementAgentJarFile.getAbsolutePath());
connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress", null);
// Now it's installed
}
// Now connect and transform the classnames provided in the remaining args.
JMXConnector connector = null;
try {
// This is the ObjectName of the MBean registered when loaded.jar was installed.
ObjectName on = new ObjectName("transformer:service=DemoTransformer");
// Here we're connecting to the target JVM through the management agent
connector = JMXConnectorFactory.connect(new JMXServiceURL(connectorAddress));
MBeanServerConnection server = connector.getMBeanServerConnection();
for(int i = 1; i < args.length; i++) {
String className = args[i];
// Call transformClass on the transformer MBean
server.invoke(on, "transformClass", new Object[]{className}, new String[]{String.class.getName()});
}
} catch (Exception ex) {
ex.printStackTrace(System.err);
} finally {
if(connector!=null) try { connector.close(); } catch (Exception e) {}
}
// Done. (Hopefully)
}
}
=================更新=================
嘿尼克; 是的,这是当前(即Java 5-8)类变压器的局限之一。 引用Instrumentation javadoc :
“重新转换可能会改变方法体,常量池和属性。重新转换不得添加,删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能会在将来的版本中解除。类文件字节不会检查,验证和安装,直到应用了转换后,如果结果字节错误,则此方法将引发异常。“
另外,对于重新定义类也逐字记录了同样的限制。
因此,您有两种选择:
不要添加新方法。 这通常是非常有限的,并且取消了使用非常常见的字节码AOP模式(如方法包装)的资格。 根据您正在使用的字节码操作库,您可以将所需的所有功能注入现有方法。 有些图书馆比其他图书馆更容易。 或者,我应该说,有些库会比其他库更容易。
在类加载之前转换类。 这使用了与我们已经讨论过的代码相同的一般模式,除了您不通过调用retransformClasses触发转换。 相反,您注册ClassFileTransformer以在加载类之前执行转换,并且在第一个类加载时将修改目标类。 在这种情况下,您可以随意修改类,只要最终产品仍然可以验证。 击败应用程序(即在应用程序加载类之前注册ClassFileTransformer)很可能需要像javaagent这样的命令,尽管如果你能够严格控制应用程序的生命周期,就可以在更传统的环境中执行此操作。应用层代码。 正如我所说,你只需要确保在加载目标类之前注册变换器。
您可以使用的#2的另一个变体是使用新的类加载器来模拟全新的类。 如果你创建一个新的独立类加载器,它不会委托给现有的[loaded]类,但是可以访问[unloaded]目标类字节代码,那么你基本上可以重现上面#2的要求,因为JVM认为这个成为一个全新的课程。
================更新================
在你上次的评论中,我觉得我已经失去了一些你在哪里的轨道。 无论如何,Oracle JDK 1.6绝对支持转换。 我对ASM不太熟悉,但是您发布的最后一个错误表明ASM转换以某种方式修改了不允许的类模式,因此重新转换失败。
我认为一个工作的例子会增加更多的清晰度。 与上面相同的类(加上一个名为Person的测试类)就在这里 。 有几个修改/补充:
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
在转换之前,该输出如下所示。
Temp File:c:\temp\com.heliosapm.shorthandexamples.AgentMain8724970986698386534.jar
Installing AgentMain...
AgentMain Installed
Agent Loaded
Instrumentation Deployed:true
Hello [0]
Hello [0]
Hello [1]
Hello [-1]
Hello [2]
Hello [-2]
Person有2个sayHello方法,一个接受一个int ,另一个接受一个String 。 (字符串1只打印循环索引的负数)。
一旦启动AgentInstaller,就会安装代理并在循环中调用Person,我使用JConsole连接到JVM:
我导航到TransformerService MBean并调用transformClass操作。 我提供了完全限定的类[二进制]名称,仪器的方法名称和正则表达式(I)V , 它只匹配以int作为参数的sayHello方法。 (或者我可以提供。* ,或者没有任何东西可以匹配所有重载)。 我执行操作。
现在,当我回到正在运行的JVM并检查输出时:
Examining class [com/heliosapm/shorthandexamples/Person]
Instrumenting class [com/heliosapm/shorthandexamples/Person]
[ModifyMethodTest] Adding [System.out.println("\n\t-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]");]
[ModifyMethodTest] Intrumented [1] methods
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [108]
Hello [-108]
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [109]
Hello [-109]
完成。 方法检测。
请记住,允许重新转换的原因是因为Javassist字节码修改除了将代码注入现有方法之外没有做任何更改。
合理 ?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.