Java Language
Java-agenter
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