Sök…


Ändra klasser med agenter

För det första, se till att agenten som används har följande attribut i Manifest.mf:

Can-Redefine-Classes: true
Can-Retransform-Classes: true

Om du startar en java-agent får agenten åtkomst till klassinstrumentationen. Med Instrumentation kan du ringa addTransformer (ClassFileTransformer transformer) . ClassFileTransformers låter dig skriva om byte för klasser. Klassen har bara en enda metod som tillhandahåller ClassLoader som laddar klassen, klassens namn, en java.lang.Class-instans av det, det är ProtectionDomain, och slutligen byte för klassen själv.

Det ser ut så här:

byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
          ProtectionDomain protectionDomain, byte[] classfileBuffer)

Att ändra en klass rent från byte kan ta åldrar. För att avhjälpa detta finns det bibliotek som kan användas för att konvertera klassbyte till något mer användbart.

I det här exemplet kommer jag att använda ASM, men andra alternativ som Javassist och BCEL har liknande funktioner.

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

Härifrån kan ändringar göras i ClassNode-objektet. Detta gör att det är otroligt enkelt att ändra fält / metodåtkomst. Plus med ASMs Tree API att modifiera bytekoden för metoder är en bris.

När redigeringarna är klar kan du konvertera ClassNode tillbaka till byte med följande metod och returnera dem i transformationsmetoden :

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

Lägga till en agent vid körning

Agenter kan läggas till en JVM vid körning. För att ladda en agent måste du använda Attach API: s VirtualMachine.attatch (String-id) . Du kan sedan ladda en kompilerad agentburk med följande metod:

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

Detta kommer inte att ringa premain ((String agentArgs, Instrumentation inst) i den laddade agenten, utan istället kommer att ringa agentmain (String agentArgs, Instrumentation inst) . Detta kräver att Agent-Class ska ställas in i agenten Manifest.mf.

Ställa in en grundläggande agent

Premain-klassen kommer att innehålla metoden "premain (String agentArgs Instrumentation inst)"

Här är ett exempel:

import java.lang.instrument.Instrumentation;

public class PremainExample {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println(agentArgs);
    }
}

Öppna manifestet och säkerställ att det har Premain-attributet när det sammanställts i en burkfil.

Här är ett exempel:

Premain-Class: PremainExample

För att använda agenten med ett annat java-program "myProgram" måste du definiera agenten i JVM-argumenten:

java -javaagent:PremainAgent.jar -jar myProgram.jar


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow