Szukaj…


Modyfikowanie klas za pomocą agentów

Po pierwsze, upewnij się, że używany agent ma następujące atrybuty w pliku Manifest.mf:

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

Uruchomienie agenta Java umożliwi agentowi dostęp do klasy Instrumentation. Za pomocą Instrumentacji można wywołać addTransformer (transformator ClassFileTransformer) . ClassFileTransformers pozwoli Ci przepisać bajty klas. Klasa ma tylko jedną metodę, która dostarcza ClassLoader, który ładuje klasę, nazwę klasy, instancję java.lang.Class, jest to ProtectionDomain, a na koniec bajty samej klasy.

To wygląda tak:

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

Modyfikowanie klasy wyłącznie z bajtów może trwać wieki. Aby temu zaradzić, istnieją biblioteki, których można użyć do konwersji bajtów klasy na coś bardziej użytecznego.

W tym przykładzie będę używać ASM, ale inne alternatywy, takie jak Javassist i BCEL, mają podobne funkcje.

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

Stąd można dokonać zmian w obiekcie ClassNode. To sprawia, że zmiana dostępu do pola / metody jest niezwykle łatwa. Plus z drzewem API ASM modyfikującym kod bajtowy metod to pestka.

Po zakończeniu edycji możesz przekonwertować ClassNode z powrotem na bajty za pomocą następującej metody i zwrócić je w metodzie transformacji :

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

Dodawanie agenta w czasie wykonywania

Agentów można dodawać do JVM w czasie wykonywania. Aby załadować agenta, musisz użyć VirtualMachine.attatch (Attachment String) Attach API. Następnie można załadować skompilowany słoik agenta za pomocą następującej metody:

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

To nie wywoła premain ((String agentArgs, Instrumentation inst) w załadowanym agencie, ale zamiast tego wywoła agentmain (String agentArgs, Instrumentation inst) . Wymaga to ustawienia klasy agenta w Manifest.mf agenta.

Konfigurowanie podstawowego agenta

Klasa Premain będzie zawierać metodę „premain (String agentArgs Instrumentation inst)”

Oto przykład:

import java.lang.instrument.Instrumentation;

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

Po skompilowaniu do pliku jar otwórz Manifest i upewnij się, że ma on atrybut Premain-Class.

Oto przykład:

Premain-Class: PremainExample

Aby użyć agenta z innym programem Java „myProgram”, musisz zdefiniować agenta w argumentach JVM:

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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow