本文最后编辑于 前,其中的内容可能需要更新。
Instrument 学习
主要是解决jvm运行时类的重载。
Instrument
JDK从1.5版本引入了java.lang.instrument包,开发者可以更方便的实现字节码增强。
java.lang.instrument包的结构如下:
1 2 3 4 5 6 7
| java.lang.instrument - ClassDefinition - ClassFileTransformer - IllegalClassFormatException - Instrumentation - UnmodifiableClassException - UnmodifiableModuleException
|
核心功能由接口java.lang.instrument.Instrumentation提供
使用方式
instrumention有两种使用方式
第一种
通过jvm的启动参数-javaagent来启动,例如
java -javaagent:myagent.jar Mymain
需要MANIFEST.MF配合找到Agent入口类,这样JVM在加载时会先执行AgentMain类的premain方法,再执行JAVA本身的main方法。
这种方法仅限于main方法之前执行,局限较大,重点学一下第二种
第二种:
jdk6之后引入了动态Attach Agent方案。instrument支持了在运行时对类定义的修改。要使用instrument的类修改功能,我们需要实现它提供的ClassFileTransformer接口,定义一个类文件转换器。接口中的transform()方法会在类文件被加载时调用,而在transform方法里,我们可以利用上文中的ASM或Javassist对传入的字节码进行改写或替换,生成新的字节码数组后返回。
测试:
先写一个MyTestMain类,这是我们要去修改的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import java.util.concurrent.TimeUnit;
public class MyTestMain { public static void main(String[] args) throws InterruptedException { while(true){ System.out.println(foo()); TimeUnit.SECONDS.sleep(3); } }
public static int foo(){ return 100; } }
|
目标是注入之后使foo return 50
首先创建maven项目agent-demo
先按照asm的方式创建MyClassVisitor和MyMethodVisitor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.MethodVisitor;
public class MyClassVisitor extends ClassVisitor { public MyClassVisitor(int api, ClassVisitor classVisitor) { super(api, classVisitor); }
@Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if ("foo".equals(name)) { System.out.println("----准备修改foo方法----"); return new MyMethodVisitor(api,mv,access,name,descriptor); } return mv; } }
|
MyMethodVisitor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.commons.AdviceAdapter;
public class MyMethodVisitor extends AdviceAdapter {
protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) { super(api, methodVisitor, access, name, descriptor); }
@Override protected void onMethodEnter() { mv.visitIntInsn(BIPUSH, 50); mv.visitInsn(IRETURN); } }
|
创建MyClassFileTransformer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.ClassWriter;
import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain;
public class MyClassFileTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (!"MyTestMain".equals(className)) return classfileBuffer; ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); ClassVisitor cv = new MyClassVisitor(262144,cw); cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); return cw.toByteArray(); } }
|
AgentMain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException;
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { System.out.println("---agent called---"); inst.addTransformer(new MyClassFileTransformer(),true); Class[] classes = inst.getAllLoadedClasses(); for (int i = 0; i < classes.length; i++) { if ("MyTestMain".equals(classes[i].getName())) { System.out.println("----start----"); inst.retransformClasses(classes[i]); System.out.println("----end----"); break; } } } }
|
pom里还得设置一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <compilerArguments> <verbose /> <bootclasspath>/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/rt.jar:/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/jce.jar:/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/jsse.jar</bootclasspath> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifestEntries> <Agent-Class> AgentMain </Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin>
</plugins> </build>
|
把agent-demo打包为jar,记下路径
之后创建attach-demo,创建MyAttachMain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class MyAttachMain { public static void main(String[] args) { VirtualMachine vm = null; try { vm = VirtualMachine.attach("13566"); vm.loadAgent("/Users/fmyyy/tools/agent-demo/target/agent-demo-1.0-SNAPSHOT.jar"); } catch (Exception e) { e.printStackTrace(); } finally { if (vm != null) { try { vm.detach(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
运行MyTestMain,用jps记下pid,替换MyAttachMain里的id即可,运行MyAttachMain,回去查看MyTestMain就会发现已经修改成功:
