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:
transform
(Codes: DemoTransformer)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
Réponse courte
Réponse longue
Voici quelques recommandations:
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à).Instrumentation.addTransformer(theNewDemoTransformer, true)
.Instrumentation.retransformClasses(ClassForName(className))
(avec le nom de classe binaire passé à l'opération MBean). Lorsque cet appel reviendra, votre classe sera transformée.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:
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.
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:
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
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:
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.
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?