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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow