web-dev-qa-db-fra.com

comment retransformer une classe au moment de l'exécution

Je suis en train de modifier la classe qui est déjà chargée dans un jvm. La solution que j'ai trouvée est:

  • 1er Attachez un agent à un jvm spécifié par pid. (par exemple 8191) (Codes: AttachTest)
  • 2e Trouvez la classe que vous souhaitez modifier parmi celles qui ont déjà été chargées dans le jvm (par exemple 8191).
  • 3e Ajouter un transformateur à l'aide d'un instrument (Codes: AgentMain)
  • 4e Modifier la classe (par exemple, Person) dans la méthode transform (Codes: DemoTransformer)
  • 5e Retransformez la classe en utilisant retransformClasses

Cela fonctionne bien de la 1ère étape à la 5ème étape, mais il y a des problèmes à retransformClasses. Il a de nouveau appelé transform qui contient des codes pour modifier la classe. Et cela modifie d'autres classes que je ne veux jamais modifier. Je pense que le problème peut survenir pendant addTransformer ou retransformClasses. Mais je suis toujours confus. Eh bien, comment retransformer une classe? Des idées? 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);
    }
}

//Agent

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

SORTIES: LE PROGRAMME DE FIXATION

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

SORTIES: PROGRAMME CIBLE

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

Réponse courte

  • Ne parcourez pas toutes les classes chargées d'Instrumentation. Examinez plutôt le nom de classe transmis au transformateur et s'il correspond à votre classe cible, puis transformez-le. Sinon, renvoyez simplement le classfileBuffer passé sans modification.
  • Effectuez les appels de configuration en dehors du transformateur (c'est-à-dire dans votre cas, procédez comme suit depuis votre agent), donc initialisez votre transformateur avec le nom de classe que vous cherchez à transformer (ce sera le format interne donc au lieu de foo.bar.Snafu , vous chercherez à faire face à foo/bar/Snafu . Ajoutez ensuite le transformateur, appelez retransform et retirez le transformateur.
  • Pour appeler la retransformation, vous aurez besoin de la classe [de pré-transformation] réelle que vous pouvez trouver en appelant Class.forName (dans l'agentmain), ou si vous le devez absolument, vous pourriez le trouver dans Intrumentation.getAllLoadedClasses () en dernier recours. Si la classe cible n'a pas été chargée, vous aurez besoin du chargeur de classe pour appeler Class.forName (nom, booléen, chargeur de classe) auquel cas vous pouvez passer l'URL au chemin de classe de la classe cible dans la chaîne principale de l'agent args.

Réponse longue

Voici quelques recommandations:

  1. Séparez l'opération que vous appelez en 2 opérations distinctes:
    1. Installez l'agent. Cela a seulement besoin d'être fait une fois.
    2. Transformez la classe cible [es]. Vous voudrez peut-être le faire n fois.
  2. Je voudrais implémenter 1.2 en enregistrant un MBean JMX simple lorsque vous installez l'agent. Ce MBean doit fournir une opération comme public void transformClass(String className). et doit être initialisé avec une référence à l'instance d'instrumentation acquise de l'agent . La classe MBean, l'interface et toutes les classes tierces requises doivent être incluses dans le fichier charger.jar de votre agent . Il doit également contenir votre classe ModifyMethodTest (ce que je suppose déjà).
  3. En même temps que vous installez votre jar d'agent, installez également l'agent de gestion à partir de $ Java_HOME/lib/management-agent.jar qui activera la gestion afin que vous puissiez invoquer l'opération de transformation dans le MBean que vous êtes sur le point d'enregistrer.
  4. Paramétrez votre classe DemoTransformer pour accepter le formulaire interne du nom de la classe que vous souhaitez transformer. (c'est-à-dire si le nom de votre classe binaire est foo.bar.Snafu, la forme interne sera foo/bar/Snafu. Lorsque votre instance DemoTransformer commence à recevoir des rappels de transformation, ignorez tous les noms de classe qui ne correspondent pas au nom de classe de formulaire interne que vous avez spécifié. (c'est-à-dire renvoyer simplement le classfileBuffer non modifié)
  5. Votre opération de transformation MBean transformClass doit alors:
    1. Convertissez le nom de classe transmis en formulaire interne.
    2. Créez un nouveau DemoTransformer, en passant le nom de classe du formulaire interne.
    3. Enregistrez l'instance DemoTransformer à l'aide de Instrumentation.addTransformer(theNewDemoTransformer, true).
    4. Appelez Instrumentation.retransformClasses(ClassForName(className)) (avec le nom de classe binaire passé à l'opération MBean). Lorsque cet appel reviendra, votre classe sera transformée.
    5. Retirez le transformateur avec Intrumentation.removeTransformer(theNewDemoTransformer).

Ce qui suit est une approximation non testée de ce que je veux dire:

MBean du transformateur

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

Service de transformateur

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

Le transformateur de classe

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

}

L'agent

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

}

Le programme d'installation de l'agent

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

================= MISE À JOUR =================

Salut Nick; Oui, c'est l'une des limitations des transformateurs de classe actuels (c'est-à-dire Java 5-8). Pour citer le Instrumentation javadoc :

"La retransformation peut modifier le corps des méthodes, le pool constant et les attributs. La retransformation ne doit pas ajouter, supprimer ou renommer des champs ou des méthodes, modifier les signatures des méthodes ou modifier l'héritage. Ces restrictions pourraient être levées dans les versions futures. Les octets du fichier de classe ne sont pas vérifiés, vérifiés et installés tant que les transformations n'ont pas été appliquées, si les octets résultants sont erronés, cette méthode lèvera une exception. "

Soit dit en passant, cette même limitation est documentée textuellement pour la redéfinition des classes également.

En tant que tel, vous avez 2 options:

  1. N'ajoutez pas de nouvelles méthodes. Ceci est généralement très limitatif et disqualifie l'utilisation de modèles AOP de code octet très courants comme le wrapping de méthode. Selon la bibliothèque de manipulation de code octet que vous utilisez, vous pourrez peut-être injecter toutes les fonctionnalités souhaitées dans les méthodes existantes. Certaines bibliothèques rendent cela plus facile que d'autres. Ou, devrais-je dire, certaines bibliothèques rendront cela plus facile que d'autres.

  2. Transformez la classe avant qu'elle ne soit chargée. Cela utilise le même modèle général du code que nous avons déjà discuté, sauf que vous ne déclenchez pas la transformation en appelant retransformClasses. Au lieu de cela, vous enregistrez le ClassFileTransformer pour effectuer la transformation avant la classe est chargée et votre classe cible sera modifiée lors de son chargement de première classe. Dans ce cas, vous êtes à peu près libre de modifier la classe comme vous le souhaitez, à condition que le produit final puisse toujours être validé. Battre l'application au punch (c'est-à-dire enregistrer votre ClassFileTransformer avant que l'application charge la classe) nécessitera très probablement une commande comme javaagent , bien que si vous avez contrôle strict du cycle de vie de votre application, il est possible de le faire dans un code de couche d'application plus traditionnel. Comme je l'ai dit, vous devez simplement vous assurer que le transformateur est enregistré avant le chargement de la classe cible.

Une autre variante du # 2 que vous pourriez utiliser est de simuler une toute nouvelle classe en utilisant un nouveau chargeur de classe. Si vous créez un nouveau chargeur de classe isolé qui ne déléguera pas à la classe [chargée] existante, mais qui a accès au code d'octet de la classe cible [déchargée], vous reproduisez essentiellement les exigences du n ° 2 ci-dessus car la JVM considère cela être une toute nouvelle classe.

================ MISE À JOUR ================

Dans vos derniers commentaires, j'ai l'impression d'avoir perdu un peu la trace de votre position. En tout cas, Oracle JDK 1.6 prend très certainement en charge la retransformation. Je ne connais pas trop ASM, mais la dernière erreur que vous avez publiée indique que la transformation ASM a en quelque sorte modifié le schéma de classe, ce qui n'est pas autorisé, de sorte que la retransformation a échoué.

J'ai pensé qu'un exemple de travail ajouterait plus de clarté. Les mêmes classes que ci-dessus (plus une classe de test appelée Personne) sont ici . Il y a quelques modifications/ajouts:

  • L'opération de transformation dans TransformerService a maintenant 3 paramètres:
    1. Le nom de classe binaire
    2. Le nom de la méthode à instrumenter
    3. Une expression [régulière] pour correspondre à la signature de la méthode. (si nul ou vide, correspond à toutes les signatures)
    4. La modification réelle du bytecode est effectuée en utilisant Javassist dans la classe ModifyMethodTest . L'instrumentation ne fait qu'ajouter un System.out.println qui ressemble à ceci: -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
  • AgentInstaller (qui a le Main de la démo) installe juste automatiquement l'agent et le service de transformation. (Plus facile à des fins de développement/démo, mais fonctionnera toujours avec d'autres machines virtuelles Java)
  • Une fois que l'agent est auto-installé, le thread principal crée une instance Person et boucle simplement, appelant les deux méthodes sayHello de la personne.

Avant la transformation, cette sortie se présente comme suit.

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]

La personne a 2 sayHello méthodes, l'une prend un int , l'autre prend un Chaîne . (La chaîne affiche simplement le négatif de l'index de boucle).

Une fois que j'ai démarré AgentInstaller, l'agent est installé et la personne est invoquée en boucle, je me connecte à la JVM à l'aide de JConsole:

Finding the AgentInstaller JVM

Je navigue vers le MBean TransformerService et j'appelle l'opération transformClass . Je fournis le nom de classe [binaire] complet, le nom de la méthode à l'instrument et une expression régulière (I) V qui correspond à seulement la méthode sayHello qui prend un entier comme argument. (Ou je pourrais fournir . * , ou rien pour correspondre à toutes les surcharges). J'exécute l'opération.

Invoking the operation

Maintenant, quand je reviens à la JVM en cours d'exécution et examine la sortie:

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]

Terminé. Méthode instrumentée.

Gardez à l'esprit, la raison pour laquelle la retransformation est autorisée est que la modification du bytecode Javassist n'a apporté aucune modification autre que l'injection de code dans une méthode existante.

Ça a du sens?

54
Nicholas