web-dev-qa-db-ja.com

実行時にクラスを再変換する方法

私はすでにjvmにロードされている変更クラスをtringしています。私が見つけた解決策は:

  • 最初に、pidで指定されたjvmにエージェントを接続します。 (例:8191)(コード:AttachTest)
  • 2番目に、jvmにすでにロードされているクラス(例:8191)から変更するクラスを見つけます。
  • 3番目の計測器を使用してトランスを追加(コード:AgentMain)
  • 4番目のtransformメソッドのクラス(Personなど)を変更します(コード:DemoTransformer)
  • 5番目 retransformClasses を使用してクラスを再変換します

最初のステップから5番目のステップまで正常に動作しますが、retransformClassesに問題があります。クラスを変更するコードを含むtransformを再度呼び出しました。そして、それは私が変更したくない他のクラスを変更します。 addTransformerまたはretransformClassesの間に問題が発生した可能性があります。しかし、私はまだ混乱しています。さて、クラスを再変換する方法は?何か案は? 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;
    }
}

出力:アタッチプログラム

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

出力:TARGET PROGRAM

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
36
Nick Dong

短い答え

  • Instrumentationから読み込まれたすべてのクラスを繰り返し処理しないでください。代わりに、トランスフォーマーに渡されたクラス名を調べ、それがターゲットクラスと一致する場合は変換します。それ以外の場合は、単に渡されたclassfileBufferを変更せずに返します。
  • トランスフォーマーの外部でセットアップ呼び出しを行い(つまり、エージェントから次のようにします)、トランスフォーマーを変換したいクラス名で初期化します(これはinner formatになります)したがって、foo.bar.Snafuの代わりに、foo/bar/Snafu。次に、トランスフォーマーを追加し、retransformを呼び出して、トランスフォーマーを削除します。
  • Retransformを呼び出すには、Class.forName(agentmain内)を呼び出して見つけることができる実際の[pre-transform]クラスが必要です。または、絶対に必要な場合は、それを見つけることができます。 in Intrumentation.getAllLoadedClasses()最後の手段として。ターゲットクラスがロードされていない場合は、クラスローダーにClass.forName(name、boolean、classloader)を呼び出す必要があります。この場合、ターゲットクラスのクラスパスにURLを渡すことができます。エージェントのメイン文字列引数。

長い答え

ここにいくつかの推奨事項があります:

  1. 呼び出す操作を2つの個別の操作に分けます。
    1. エージェントをインストールします。これは一度だけ行う必要があります。
    2. ターゲットクラスを変換します。これを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.Snafの場合、内部形式はfoo/bar/Snafになります。DemoTransformerインスタンスが変換コールバックの取得を開始するため、すべて無視します指定した内部形式のクラス名と一致しないクラス名(つまり、単に変更されていないclassfileBufferを返す)
  5. トランスフォーマーMBeantransformClassオペレーションは次のようになります。
    1. 渡されたクラス名を内部形式に変換します。
    2. 内部フォームクラス名を渡して、新しいDemoTransformerを作成します。
    3. Instrumentation.addTransformer(theNewDemoTransformer, true)を使用して、DemoTransformerインスタンスを登録します。
    4. Instrumentation.retransformClasses(ClassForName(className))を呼び出します(MBean操作にバイナリクラス名を渡します)。この呼び出しが戻ると、クラスが変換されます。
    5. Intrumentation.removeTransformer(theNewDemoTransformer)を使用してトランスフォーマーを削除します。

以下は、私が何を意味するかのテストされていない近似です:

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 Service

_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)クラストランスフォーマーの制限の1つです。 Instrumentation javadoc から引用するには:

「再変換により、メソッド本体、定数プール、および属性が変更される場合があります。再変換では、フィールドまたはメソッドの追加、削除、または名前変更、メソッドのシグネチャの変更、または継承の変更を行ってはなりません。これらの制限は将来のバージョンで解除される可能性があります。クラスファイルのバイト数変換が適用されるまでチェック、検証、インストールされません。結果のバイトがエラーの場合、このメソッドは例外をスローします。」

余談ですが、この同じ制限は、クラスを再定義するためにも逐語的に文書化されています。

そのため、2つのオプションがあります。

  1. 新しいメソッドを追加しないでください。これは一般的に非常に制限があり、メソッドのラッピングのような非常に一般的なバイトコードAOPパターンの使用を不適格とします。使用しているバイトコード操作ライブラリによっては、必要なすべての機能を既存のメソッドに注入できる場合があります。一部のライブラリは、これを他のライブラリよりも簡単にします。あるいは、一部のライブラリでは、これが他のライブラリよりも簡単になります。

  2. クラスがロードされる前にクラスを変換します。これは、retransformClassesを呼び出して変換をトリガーしないことを除いて、すでに説明したコードと同じ一般的なパターンを使用します。代わりに、ClassFileTransformerを登録して変換を実行しますbeforeクラスがロードされ、最初にクラスがロードされたときにターゲットクラスが変更されます。この場合、最終製品をまだ検証できるのであれば、クラスを自由に変更できます。アプリケーションを打ち負かす(つまり、アプリケーションがクラスをロードする前にClassFileTransformerを登録する)には、おそらくjavaagentのようなコマンドが必要ですが、アプリケーションのライフサイクルを厳密に制御できるため、これをより伝統的なアプリケーションレイヤーコードで行うことができます。先ほど述べたように、ターゲットクラスがロードされる前にトランスフォーマーを登録する必要があります。

#2のもう1つのバリエーションは、新しいクラスローダーを使用してsimulateまったく新しいクラスにすることです。既存の[ロード済み]クラスに委譲しないが、[アンロード済み]ターゲットクラスのバイトコードにアクセスできる新しい分離クラスローダーを作成する場合、JVMがこれを考慮しているため、上記の#2の要件を本質的に再現しています。真新しいクラスになるために。

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

あなたの最後のコメントで、私はあなたがどこにいるのか少しトラックを失ったような気がします。とにかく、Oracle JDK 1.6は間違いなく再変換をサポートしています。私はASMについてあまり詳しくありませんが、投稿した最後のエラーは、ASM変換が何らかの理由で許可されていないクラススキーマを変更したため、再変換が失敗したことを示しています。

実用的な例がさらに明確になると考えました。上記と同じクラス(およびPersonと呼ばれる1つのテストクラス)は here です。いくつかの変更/追加があります:

  • TransformerService の変換操作に3つのパラメーターがあります:
    1. バイナリクラス名
    2. 計測するメソッド名
    3. メソッドシグネチャに一致する[正規]式。 (nullまたは空の場合、すべての署名に一致します)
    4. 実際のバイトコードの変更は、 ModifyMethodTest クラスの Javassist を使用して行われます。インスツルメンテーションが行うのは、次のようなSystem.out.printlnを追加することだけです。-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
  • AgentInstaller (デモのメインがあります)は、エージェントと変換サービスを自己インストールするだけです。 (Dev/Demoの目的には簡単ですが、他のJVMでも動作します)
  • エージェントが自己インストールされると、メインスレッドは Person インスタンスを作成してループし、Personの2つの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メソッドがあり、1つはintを受け取り、もう1つはStringを受け取ります。 (文字列1は、ループインデックスの負の値を出力するだけです)。

AgentInstallerを起動すると、エージェントがインストールされ、Personがループで呼び出されたら、JConsoleを使用してJVMに接続します。

Finding the AgentInstaller JVM

TransformerService MBeanに移動し、transformClass操作を呼び出します。完全修飾クラス[バイナリ]名、計測するメソッド名、およびonlyに一致する正規表現(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バイトコードの変更が、既存のメソッドにコードを挿入する以外に変更を加えなかったからです。

理にかなっていますか?

54
Nicholas