和之前的premain方式一样,创建一个Task.jar作为应用程序:
import java.util.Scanner; public class Task { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); scanner.hasNext(); } }把创建的Task.jar运行起来:java -jar Task.jar
2)创建一个agentmain方式的Agent创建一个agentmain方式的Agent02.jar,Agent02.java:
import java.lang.instrument.Instrumentation; public class Agent02 { public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("打印全部加载的类:"); Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class allLoadedClass : allLoadedClasses) { System.out.println(allLoadedClass.getName()); } } }同样生成jar包的话,需要手动定义一个MANIFEST.MF文件
Manifest-Version: 1.0 Agent-Class: com.test.Agent02 3)利用VirtualMachine注入使用VirtualMachine类来利用前面创建的Agent进行代理类注入,VirtualMachine类在jdk目录下的lib/tools.jar包,需要手动导入
package com.test;import com.sun.tools.attach.AgentInitializationException;import com.sun.tools.attach.AgentLoadException;import com.sun.tools.attach.AttachNotSupportedException;import com.sun.tools.attach.VirtualMachine;import java.io.IOException;public class App { public static void main( String[] args ) { try { //VirtualMachine 来自tools.jar // VirtualMachine.attach("9444") 9444为线程PID,使用jps查看 VirtualMachine vm = VirtualMachine.attach("9444"); //指定要使用的Agent路径 vm.loadAgent("C:\\Users\\xxx\\Desktop\\Agent02.jar"); } catch (AttachNotSupportedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AgentLoadException e) { e.printStackTrace(); } catch (AgentInitializationException e) { e.printStackTrace(); } }}运行这个名为App的类之后,正在运行的Task程序会执行代码:
以下是先知社区的图:
Java Agent 代码植入利用agentmain配合Javassist,在方法执行前,修改任意类的方法。在演示之前,先来看几个知识点。
Instrumentation类在agentmain的构造函数中,第二个参数就是Instrumentation
public static void agentmain(String agentArgs, Instrumentation inst)这个类就是用来进行aop操作的类,能够替换和修改某些类的定义
public interface Instrumentation { // 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。 void addTransformer(ClassFileTransformer transformer); // 删除一个类转换器 boolean removeTransformer(ClassFileTransformer transformer); // 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException; // 判断目标类是否能够修改。 boolean isModifiableClass(Class<?> theClass); // 获取目标已经加载的类。 @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); ......}其中addTransformer()和retransformClasses()用来篡改Class的字节码。
从源码中看到addTransformer方法参数中,第一个参数传递的为ClassFileTransformer类型
ClassFileTransformer接口这是一个接口,它提供了一个transform方法:
public interface ClassFileTransformer { default byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { .... }}接下来就用一个示例来演示利用agentmain配合Javassist进行代码植入的操作
示例: