Java Language
Java-agenten
Zoeken…
Klassen wijzigen met agenten
Zorg er eerst voor dat de gebruikte agent de volgende attributen heeft in de Manifest.mf:
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Als u een Java-agent start, krijgt de agent toegang tot de klasse Instrumentatie. Met Instrumentation kunt u addTransformer (ClassFileTransformer-transformator) aanroepen . Met ClassFileTransformers kunt u de bytes van klassen herschrijven. De klasse heeft slechts één methode die de ClassLoader levert die de klasse laadt, de naam van de klasse, een java.lang.Class-instantie ervan, het is ProtectionDomain, en ten slotte de bytes van de klasse zelf.
Het ziet er zo uit:
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
Het wijzigen van een klasse puur uit bytes kan eeuwen duren. Om dit te verhelpen zijn er bibliotheken die kunnen worden gebruikt om de klasse-bytes om te zetten in iets bruikbaarder.
In dit voorbeeld gebruik ik ASM, maar andere alternatieven zoals Javassist en BCEL hebben vergelijkbare functies.
ClassNode getNode(byte[] bytes) {
// Create a ClassReader that will parse the byte array into a ClassNode
ClassReader cr = new ClassReader(bytes);
ClassNode cn = new ClassNode();
try {
// This populates the ClassNode
cr.accept(cn, ClassReader.EXPAND_FRAMES);
cr = null;
} catch (Exception e) {
e.printStackTrace();
}
return cn;
}
Vanaf hier kunnen wijzigingen worden aangebracht in het ClassNode-object. Dit maakt het wijzigen van veld- / methode-toegang ongelooflijk eenvoudig. Plus met ASM's Tree API is het wijzigen van de bytecode van methoden een fluitje van een cent.
Nadat de bewerkingen zijn voltooid, kunt u de ClassNode weer in bytes converteren met de volgende methode en deze teruggeven in de transformatiemethode :
public static byte[] getNodeBytes(ClassNode cn, boolean useMaxs) {
ClassWriter cw = new ClassWriter(useMaxs ? ClassWriter.COMPUTE_MAXS : ClassWriter.COMPUTE_FRAMES);
cn.accept(cw);
byte[] b = cw.toByteArray();
return b;
}
Een agent toevoegen tijdens runtime
Agents kunnen tijdens runtime aan een JVM worden toegevoegd. Om een agent te laden, moet u VirtualMachine.attatch van de API bijvoegen (tekenreeks-id) gebruiken . U kunt vervolgens een gecompileerde agentpot laden met de volgende methode:
public static void loadAgent(String agentPath) {
String vmName = ManagementFactory.getRuntimeMXBean().getName();
int index = vmName.indexOf('@');
String pid = vmName.substring(0, index);
try {
File agentFile = new File(agentPath);
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentFile.getAbsolutePath(), "");
VirtualMachine.attach(vm.id());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Dit roept geen premain ((String agentArgs, Instrumentation inst) op in de geladen agent, maar roept in plaats daarvan agentmain (String agentArgs, Instrumentation inst) aan . Hiervoor moet Agent-Class worden ingesteld in de agent Manifest.mf.
Een basisagent instellen
De klasse Premain bevat de methode "premain (String agentArgs Instrumentation inst)"
Hier is een voorbeeld:
import java.lang.instrument.Instrumentation;
public class PremainExample {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println(agentArgs);
}
}
Wanneer het in een jar-bestand is gecompileerd, opent u het Manifest en zorgt u ervoor dat het het kenmerk Premain-Class heeft.
Hier is een voorbeeld:
Premain-Class: PremainExample
Om de agent te gebruiken met een ander Java-programma "myProgram", moet u de agent definiëren in de JVM-argumenten:
java -javaagent:PremainAgent.jar -jar myProgram.jar