wie man eine Klasse zur Laufzeit zurückübersetzt
Ich bin tring zu ändern-Klasse, die bereits geladen in einer jvm. Die Lösung, die ich gefunden habe ist:
- 1. Befestigen Sie einen agent, um eine jvm festgelegt durch pid. (z.B. 8191)(Codes: AttachTest)
- 2. die Klasse zu Finden, die Sie wollen geändert von denjenigen, die bereits geladen wurden, in die jvm(z.B. 8191).
- 3. Hinzufügen Transformator mit Instrument (Codes: AgentMain)
- 4. Ändern Sie die Klasse(z.B. eine Person) im
transform
Methode(Codes: DemoTransformer) - 5. Retransform die Klasse mit retransformClasses
Funktioniert es vom 1. Schritt, 5. Schritt, aber es gibt Probleme bei retransformClasses
. Er forderte transform
wieder enthält die codes zu ändern, Klasse. Und Es ändern, die anderen Klassen, die ich nie wanna ändern.
Ich denke, das problem möglicherweise aufgetreten während addTransformer
oder retransformClasses
. Aber ich immer noch verwirrt. Naja, wie retransform eine Klasse? Irgendwelche Ideen? 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;
}
}
AUSGÄNGE:
DIE ATTACH-PROGRAMM
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
AUSGÄNGE: ZIEL-PROGRAMM
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
InformationsquelleAutor der Frage Nick Dong | 2013-09-02
Du musst angemeldet sein, um einen Kommentar abzugeben.
Kurze Antwort
Lange Antwort
Hier ein paar Empfehlungen:
public void transformClass(String className)
. und sollte initialisiert mit einer Referenz auf den Agenten erworben Instrumentation Instanz. Die MBean-Klasse, - Schnittstelle und der erforderlichen 3rd-party-Klassen aufgenommen werden sollten, in Ihre Agenten loaded.jar. Es sollte auch enthalten Ihre ModifyMethodTest - Klasse (die ich annehmen, dass es bereits tut).Instrumentation.addTransformer(theNewDemoTransformer, true)
.Instrumentation.retransformClasses(ClassForName(className))
(mit binären Klasse name, der an die MBean-operation). Wenn dieser Aufruf gibt, Ihre Klasse transformiert werden.Intrumentation.removeTransformer(theNewDemoTransformer)
.Folgende ist eine ungetestete Annäherung an das, was ich meine:
Transformator MBean
Transformator Service
Die Klasse Transformator
Der Agent
Des Agent-Installationsprogramms
================= UPDATE =================
Hey Nick; Ja, das ist eine der Beschränkungen der aktuellen (z.B. Java 5-8) Klasse Transformatoren.
Zitat aus der Instrumentation javadoc:
Nebenbei bemerkt, ist diese Einschränkung ist dokumentiert verbatim für die Neudefinition Klassen zu.
Als solche, haben Sie 2 Möglichkeiten:
Nicht das hinzufügen von neuen Methoden. Dies ist Häufig sehr begrenzt und disqualifiziert die Verwendung von sehr Häufig byte-code-AOP-Muster, wie die Methode
Verpackung. Je nachdem welcher byte-code-manipulation-Bibliothek, die Sie verwenden, können Sie in der Lage zu injizieren alle Funktionen, die Sie wollen, in
die vorhandenen Methoden. Manche Bibliotheken machen dies einfacher als anderen. Oder, sollte ich sagen, manche Bibliotheken machen dies einfacher als anderen.
Transform der Klasse vor die Klasse geladen. Diese verwendet die gleiche Allgemeine Muster der code, den wir bereits besprochen, außer dass Sie nicht auslösen
die Transformation durch den Aufruf retransformClasses. Eher, Sie registrieren die ClassFileTransformer zum durchführen der Transformation vor die Klasse geladen wird
und Ihr Ziel-Klasse wird geändert, wenn es ist erste Klasse geladen. In diesem Fall sind Sie ziemlich frei, um die Klasse ändern irgendeiner Weise, die Sie
wie, vorausgesetzt, das Endprodukt kann noch validiert werden. Schlagen die Anwendung auf dem Schlag (D. H. bekommen Sie Ihre ClassFileTransformer
registriert, bevor die Anwendung lädt die Klasse) benötigen wahrscheinlich einen Befehl wie javaagent, obwohl, wenn Sie haben eine strenge Kontrolle
der Lebenszyklus Ihrer Anwendung ist es möglich, dies zu tun, in der mehr traditionellen application-layer-code. Als ich sagte, Sie brauchen nur zu machen
Sie sicher, dass der Transformator registriert, bevor der Ziel-Klasse geladen wird.
Einer anderen Variante #2 Sie können in der Lage sein zu verwenden, ist simulieren eine Marke neue Klasse mit einem neuen classloader. Wenn Sie erstellen eine neue
isolierten classloader, die nicht Delegierte zu den bestehenden [geladen] Klasse, aber hat Zugang zu den [entladen] Ziel der Klasse byte-code,
Sie sind im wesentlichen die Reproduktion der Anforderungen von #2 oben, da die JVM ist der Auffassung, dass dies eine BRANDNEUE Klasse.
================ UPDATE ================
In Ihrer letzten Kommentare, habe ich das Gefühl, ich habe den Faden verloren, ein bisschen, wo Sie sind. Jedenfalls, Oracle JDK 1.6 definitiv unterstützt retransform. Ich bin nicht allzu vertraut mit ASM, aber der Letzte Fehler, den Sie geschrieben zeigt an, dass die ASM-transformation irgendwie geändert die Klasse schema ist nicht zulässig, so dass die retransform gescheitert.
Dachte ich, ein funktionsfähiges Beispiel möchte hinzufügen, mehr Klarheit. Die gleichen Klassen wie oben (plus eine test-Klasse namens Person) sind hier. Es gibt ein paar änderungen/Ergänzungen:
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Vor zu transformieren, dass die Ausgabe sieht wie folgt aus.
Mensch hat 2 sayHello Methoden, nimmt sich ein intder andere nimmt eine String. (Die String-one druckt nur das negativ des loop-index).
Nachdem ich die AgentInstaller, der agent installiert ist und der Mensch aufgerufen wird, in einer Schleife, Verbinde ich den JVM mit JConsole:
Navigiere ich zu der TransformerService MBean und berufen sich auf die transformclass an Betrieb. Ich liefere die voll qualifizierte Klasse [binary] Namen, den Namen der Methode zu instrument, und ein regex-Ausdruck (I)V die Spiele nur die sayHello Methode nimmt ein int als argument. (Oder ich liefern könnte .*- oder-nichts-zu-match-alle überladungen). Führe ich den Betrieb.
Nun, wenn ich zurück an die laufende JVM und untersuchen Sie die Ausgabe:
Getan. Methode instrumentiert.
Halten Sie im Verstand, den Grund der retransform zulässig ist, weil der Javassist-bytecode-änderung, keine änderungen außer einschleusen von code in eine vorhandene Methode.
Sinn ?
InformationsquelleAutor der Antwort Nicholas