如何在运行时重新转换类

时间:2013-09-02 07:15:54

标签: java jvm aop cglib

我要修改已经加载到jvm中的类。我找到的解决方案是:

  • 1st将代理附加到pid指定的jvm。 (例如8191)(代码:AttachTest)
  • 2nd从已经加载到jvm中的类(例如8191)中找到要修改的类。
  • 3rd使用Instrument添加变压器(代码:AgentMain)
  • 4th用transform方法修改类(例如Person)(代码:DemoTransformer)
  • 5th使用retransformClasses
  • 重新转换课程

从第1步到第5步工作正常,但retransformClasses存在问题。它再次调用transform,其中包含修改类的代码。它修改了我从不想修改的其他类。 我认为问题可能发生在addTransformerretransformClasses期间。但我还是很困惑。那么,如何重新转换一个类?有任何想法吗? THX

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

输出: ATTACH PROGRAM

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 个答案:

答案 0 :(得分:52)

简答

  • 不要遍历Instrumentation中所有已加载的类。相反,只需检查传入变换器的类名称,如果它与目标类匹配,则转换它。否则,只需返回未经修改的传递的classfileBuffer。
  • 在变压器外部进行设置调用(例如,在您的情况下,从代理执行以下操作),然后使用您要查找的类名称初始化变换器(这将是内部格式所以代替 foo.bar.Snafu ,你将寻找与 foo / bar / Snafu 匹配。然后添加变换器,调用转换然后再拆下变压器。
  • 为了调用转换,您需要通过调用 Class.forName (在agentmain中)找到的实际[pre-transform]类,或者如果您必须,则需要可以在 Intrumentation.getAllLoadedClasses()中找到它作为最后的手段。如果尚未加载目标类,则需要类加载器调用 Class.forName(name,boolean,classloader),在这种情况下,您可以将URL传递给目标类class-path agent main string args。

长答案

以下是一些建议:

  1. 将您调用的操作分为两个单独的操作:
    1. 安装代理。这只需要做一次。
    2. 转换目标类[es]。您可能希望 n 次。
  2. 我会在安装代理时注册一个简单的JMX MBean来实现1.2。此MBean应提供类似public void transformClass(String className)的操作。并应参考代理商获得的 Instrumentation 实例进行初始化。 MBean类,接口和任何所需的第三方类应包含在您的代理 loaded.jar 中。它还应包含 ModifyMethodTest 类(我假设它已经存在)。
  3. 在安装代理jar的同时,还要从 $ JAVA_HOME / lib / management-agent.jar 安装管理代理,这将激活管理代理,以便您可以调用转换操作在MBean中,您即将注册。
  4. 参数化您的DemoTransformer类以接受要转换的类名称的内部表单。 (即如果您的二进制类名称为 foo.bar.Snafu ,则内部表单将为 foo / bar / Snafu 。当您的DemoTransformer实例开始获取转换回调时,请忽略所有与您指定的内部表单类名称不匹配的类名称。(即只返回未修改的classfileBuffer)
  5. 您的转换器MBean transformClass 操作应该:
    1. 将传递的类名转换为内部形式。
    2. 创建一个新的DemoTransformer,传递内部表单类名。
    3. 使用Instrumentation.addTransformer(theNewDemoTransformer, true)注册DemoTransformer实例。
    4. 调用Instrumentation.retransformClasses(ClassForName(className))(将二进制类名传递给MBean操作)。当此调用返回时,您的类将被转换。
    5. 使用Intrumentation.removeTransformer(theNewDemoTransformer)删除变压器。
  6. 以下是我未经测试的近似值:

    Transformer 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

      

    &#34;重新转换可能会改变方法体,常量池和   属性。重新转换不得添加,删除或重命名字段   或方法,更改方法的签名或更改继承。   在将来的版本中可能会取消这些限制。类文件   字节不会被检查,验证和安装,直到之后   如果结果字节出错,则应用转换   这个方法会引发异常。&#34;

    顺便说一句,同样的限制也是逐字记录的,用于重新定义类。

    因此,您有两个选择:

    1. 不要添加新方法。这通常是非常有限的,并且取消了使用非常常见的字节码AOP模式(如方法)的资格 包装。根据您使用的字节码操作库,您可以将所需的所有功能注入其中 现有的方法。有些图书馆比其他图书馆更容易。或者,我应该说,有些库会比其他库更容易。

    2. 在类加载之前转换类。这使用了与我们已经讨论过的代码相同的一般模式,除非您不触发 通过调用retransformClasses进行转换。而是在注册类之前注册ClassFileTransformer以执行转换 并且在加载第一个类时将修改目标类。在这种情况下,您可以随心所欲地修改课程 例如,只要最终产品仍然可以验证。击败应用程序(即获取ClassFileTransformer) 在应用程序加载类之前注册)很可能需要像 javaagent 这样的命令,尽管你有严格的控制权 在应用程序的生命周期中,可以在更传统的应用程序层代码中执行此操作。正如我所说,你只需要做 确保在加载目标类之前注册变换器。

    3. 您可能能够使用的#2>另一个变体是使用新的类加载器模拟一个全新的类。如果你创建一个新的 孤立的类加载器,它不会委托给现有的[loaded]类,但可以访问[unloaded]目标类的字节码, 因为JVM认为这是一个全新的类,所以你基本上复制了上面#2的要求。

      ================更新================

      在你的上一次评论中,我觉得我已经失去了一些你所处的位置。无论如何,Oracle JDK 1.6绝对支持转换。我对ASM不太熟悉,但是您发布的最后一个错误表明ASM转换以某种方式修改了不允许的类模式,因此重新转换失败。

      我认为一个有效的例子会增加更多的清晰度。与上面相同的类(加上一个名为Person的测试类)是here。还有一些修改/补充:

      • TransformerService中的转换操作现在有3个参数:
        1. 二进制类名称
        2. 要检测的方法名称
        3. 匹配方法签名的[常规]表达式。 (如果为null或为空,则匹配所有签名)
        4. 使用Javassist类中的ModifyMethodTest完成实际字节码修改。所有的工具都是添加一个如下所示的 System.out.println -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
      • AgentInstaller(具有演示版的主要内容)只是自行安装代理和转换服务。 (更容易进行开发/演示,但仍可与其他JVM一起使用)
      • 一旦代理自行安装,主线程就会创建一个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 。 (字符串1只打印循环索引的负数)。

      启动AgentInstaller后,安装代理并在循环中调用Person,我使用JConsole连接到JVM:

      Finding the AgentInstaller JVM

      我导航到TransformerService MBean并调用 transformClass 操作。我提供了完全限定的类[二进制]名称,仪器的方法名称和正则表达式(I)V ,它只匹配 sayHello 将int作为参数的方法。 (或者我可以提供。* ,或者没有任何东西可以匹配所有重载)。我执行操作。

      Invoking the operation

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

      有意义吗?