jvm 如何在运行时重新转换一个类?

zf9nrax1  于 7个月前  发布在  其他
关注(0)|答案(1)|浏览(93)

我试图修改一个已经加载到JVM中的类。我找到的解决方案是这样的:
1.将代理附加到PID指定的JVM。(例如8191)(代码:AttachTest
1.从已经加载到JVM中的类中找到您想要修改的类(例如,8191)。
1.使用仪器添加Transformer(代码:AgentMain
1.修改类(例如transform方法中的Person)(代码:DemoTransformer
1.使用retransformClasses重新转换类
从第一步到第五步都很好,但是在retransformClasses处有问题。它再次调用了transform,其中包含修改类的代码。它修改了其他类,我从来不想修改。我认为问题可能发生在addTransformerretransformClasses。但我还是很困惑。那么,如何重新转换一个类?

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
l5tcr1uw

l5tcr1uw1#

简短回答

  • 不要遍历从Instrumentation加载的所有类。相反,只需检查传递给Transformer的类名,如果它与目标类匹配,则转换它。否则,简单地返回传递的classfileBuffer。
  • 在Transformer外部进行设置调用(即在您的情况下,从代理执行以下操作),因此使用您希望转换的类名初始化Transformer(这将是 * 内部格式 *,因此您将希望匹配foo/bar/Snafu,而不是foo.bar. Snafu。然后添加Transformer,调用retransform,然后删除Transformer。
  • 为了调用retransform,你将需要实际的[pre-transform]类,你可以通过调用 Class.forName(在agentmain中)找到它,或者如果你绝对需要,你可以在 Intrumentation.getAllLoadedClasses() 中找到它作为最后的手段。如果目标类还没有被加载,你将需要classloader调用 Class.forName(name,boolean,classloader),在这种情况下,你可以在代理主字符串args中传递目标类class-path的URL。
    长回答

以下是一些建议:
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 MBean
transformClass**操作应该:
1.将传递的类名转换为内部形式。
1.创建一个新的DemoTransformer,传递内部表单类名。
1.使用Instrumentation.addTransformer(theNewDemoTransformer, true)注册DemoTransformer示例。
1.调用Instrumentation.retransformClasses(ClassForName(className))(使用传递给MBean操作的二进制类名)。当这个调用返回时,你的类将被转换。
1.用Intrumentation.removeTransformer(theNewDemoTransformer)拆下Transformer。
以下是一个未经检验的近似我的意思:

Transformer MBean

public interface TransformerServiceMBean {
    /**
     * Transforms the target class name
     * @param className The binary name of the target class
     */
    public void transformClass(String className);
}

Transformer服务

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

Transformer类

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的话:
“重新转换可能会更改方法体、常量池和属性。重新转换不得添加、移除或重命名字段或方法,不得更改方法的签名,也不得更改继承。这些限制可能会在未来的版本中取消。在应用转换之前,不会检查、验证和安装类文件字节,如果结果字节出错,此方法将引发异常。”
顺便说一句,对于重定义类,也逐字记录了同样的限制。
因此,您有两个选择:
1.不要添加新方法。这通常是非常有限的,并且不允许使用非常常见的字节码AOP模式,如方法 Package 。根据您使用的字节码操作库,您可能能够将所需的所有功能注入现有方法。有些库比其他库更容易做到这一点。或者,我应该说,有些库会比其他库更容易做到这一点。

1.在类加载之前转换类。除了不通过调用retransformClasses触发转换之外,它使用了与我们已经讨论过的代码相同的通用模式。相反,您注册ClassFileTransformer以在加载类之前执行转换,并且您的目标类将在第一次加载类时被修改。在这种情况下,只要最终产品仍然可以被验证,您就可以自由地以任何方式修改类。将应用程序打到冲头上(即在应用程序加载类之前注册ClassFileTransformer)很可能需要像javaagent这样的命令,尽管如果您严格控制应用程序的生命周期,可以在更传统的应用程序层代码中完成此操作。正如我所说的,您只需要确保在加载目标类之前注册了Transformer。
你可以使用的另一个变体是通过使用一个新的类加载器来 * 模拟 * 一个全新的类。如果你创建了一个新的隔离类加载器,它不会委托给现有的[loaded]类,但是可以访问[unloaded]目标类的字节码,你实际上是在复制上面的需求#2,因为JVM认为这是一个全新的类。

**=

在你最后的评论中,我觉得我有点不知道你在哪里。无论如何,Oracle JDK 1.6绝对支持重新转换。我对ASM不是很熟悉,但是你发布的最后一个错误表明ASM转换以某种方式修改了不允许的类模式,所以重新转换失败了。
我想一个工作的例子会增加更多的清晰度。与上面相同的类(加上一个名为Person的测试类)是here。有几个修改/添加:

  1. TransformerService中的transform操作现在有3个参数:
    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 方法。
    在转换之前,输出如下所示。
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。(String只打印循环索引的负数)。
一旦我启动了Agent,代理就安装好了,Person在循环中被调用,我使用JConsole连接到JVM:

我导航到TransformerService MBean并调用transformClass操作。我提供了完全限定的class [binary]名称,要检测的方法名称,以及一个正则表达式**(I)V**,它 * 只 * 匹配 sayHello 方法,该方法将int作为参数。(或者我可以提供**. *,或者不提供任何内容来匹配所有重载)。我来执行任务。

现在,当我回到运行的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字节码修改除了将代码注入现有方法之外没有进行任何更改。
说得通吗

相关问题