1 字节码扩展技术字节码扩展技术是一种修改现有字节码或动态生成新字节码文件的技术。字节码的实现有多种方式,如下所示。
1.1 ASMASM可以直接生成.class字节码文件,并在类加载到JVM之前动态改变类的行为。 ASM的应用场景包括AOP(Cglib基于ASM)、热部署、修改其他jar包中的类。流程如下: (1)首先通过ClassReader读取编译好的.class文件。 (2)通过Visitor模式修改字节码。 常见的访客类别有:
修改方法修改MethodVisitor、FieldVisitor和变量访问AnnotationVisitor注释(3) 使用ClassWriter重新构建和编译修改后的字节码文件,或将修改后的字节码文件输出到Masu。
缺点ASM虽然可以达到修改字节码的效果,但代码实现层次较低,是虚拟机指令的集合。 IDEA 插件:ASM Bytecode Outline 可以将Java 代码转换为ASM 指令实现。
1.2 JavaAssist 可以使用Javassist实现字节码扩展,动态改变类的结构,动态生成类。编程被简化,因为你不需要了解虚拟机指令,可以直接使用Java编码。 JavaAssist的核心类是ClassPool、CtClass、CtMethod和CtField。
ClassPool:存储CtClass 的映射并通过classPool.get(类的完整路径名)检索CtClass。 CtClass:代码中类文件的抽象表示。 Ct方法:方法。 CtField:对应的类。由于ASM和JavaAssist属性和变量不足,只能修改加载前类内部的字节码,而无法修改和加载正在运行的JVM字节码文件。例子
package com.artemis.xm.agent;public class A { public void method(){ System.out.println(\’method.\’); }}public class JavassistTest { public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, IOException { ClassPool cp=ClassPool.getDefault(); CtClass cc=cp.get(\’com.artemis.xm.agent.A\’); CtMethod cm=cc.getDeclaredMethod(\’method\’); cm.insertBefore(\'{ System.out.println(\\\’start\\\’); }\’); cm.insertAfter(\'{ System.out.println(\\\’end\\\’); }\’); (); A a=(A) c.newInstance() a.method();
startmethod.end1.3instrumentJava从1.5开始就提供了java.lang.instrument,为Java程序提供了API来监控、收集性能信息、诊断问题等。 Instrumentation是java.lang.instrument包下的一个接口。该接口允许您修改加载和卸载的类。定义仪器接口
public Interface Instrumentation { //为Instrumentation注册一个类文件转换器,可以修改读取类文件的字节码。 void addTransformer(ClassFileTransformerTransformer, boolean canRetransform);//重新触发JVM加载的类的类加载void retransformClasses(Class .classes) throws UnmodifyingClassException; //当前JVM加载的所有类对象Get Class[] getAllLoadedClasses();调用Instrumentation#addTransformer设置转换器后,所有后续的JVM类在加载之前都会被这个转换方法拦截。 ClassFileTransformer接口定义
public Interface ClassFileTransformer { public byte[]transform(ClassLoaderloader,String className,Class classBeingRedependent, ProtectionDomain ProtectionDomain, byte[] classBytes) throws IllegalClassFormatException { //此处读取并转换类文件return classBytes }} ClassFileTransformer 接口的转换方法是。当类文件加载时,transform方法接收原始类文件的字节数组,可以使用ASM或Javassist重写接收到的字节码或生成新的字节码数组。
2 Java Agent2.1 Java 代理概述Java 代理使用instrumentation 接口(java.lang.instrument)创建代理,并使用instrumentation 的API 来读取和重写当前的JVM 类。 Java代理是一种特殊的Jar包,不能自行启动,必须附加到JVM进程中。 Java 代理有两种类型:premain 和Agentmain。 premain 代理:该代理类包含一个premain 方法。当JVM加载一个类时,它首先执行代理类的premain方法,然后执行Java程序本身的main方法。这就是premain 这个名字的由来。 premain 方法允许您在加载类文件之前对其进行修改。 Agentmain代理:JVM启动后通过JVMTI的Attach API机制远程加载。通过Attach API,您可以访问已启动的Java 进程并拦截类加载。从JDK 1.6 开始,检测支持在运行时更改类定义。
有两种类型的代理:在主程序之前运行的代理和在主程序之后运行的代理(前一种版本在1.6 及更高版本中可用)。
2.2 实现Java 代理使用Java 代理需要几个步骤。
定义MANIFEST.MF 文件。该文件必须包含Premain-Class 选项,通常还包含Can-Redefine-Classes 和Can-Retransform-Classes 选项。创建一个包含Premain-Class指定的premain方法的类,并将premain类和MANIFEST.MF文件打包成jar包。启动代理方法,参数为-javaagent: jar包路径。 2.2.1 为需要增强的类定义增强方法定义需要增强的类Cat
public class Cat { public void beginSleep() { while (true) { sleep(); try { Thread.sleep(5000L) } catch (InterruptedException e) { e.printStackTrace() } } System.out.println(\’猫在睡觉\’);
公共类PreMainTransformerimplements ClassFileTransformer { @Override 公共byte[]transform(ClassLoaderloader,String className,ClassclassBeingRefine,ProtectionDomainprotectionDomain,byte[]classfileBuffer) 抛出IllegalClassFormatException { if (!className.equals(\’com.dc.husky.agent.Cat\’)) { return null; } System.out.println(\’预主转换Class:\’ + className);
公共类AgentMainTransformerimplements ClassFileTransformer { @Override public byte[]transform(ClassLoaderloader,String className,ClassclassBeingRedefine,ProtectionDomainprotectionDomain,byte[]classfileBuffer) { System.out.println(\’agentMain transform Class:\’ + className); } ; ClassClassPath classPath=new ClassPath(this.getClass()); CtClass cc=cp.get(\’com.dc.husky.agent.CtMethod\’);=cc.getDeclaredMethod(\’sleep\’); System.out.println(\’start\’); }\’); m.insertAfter(\'{ System.out.println(\’end\’); }\’ ); return cc.toBytecode(); e.printStackTrace() }} 定义代理。
public class MyAgent { public static void premain(String AgentArgs, Instrumentation inst) { inst.addTransformer(new PreMainTransformer(), true); System.out.println(\’premain 代理已加载!\’); addTransformer(new AgentMainTransformer(), true); try { //重新定义类并加载新字节码inst.retransformClasses(Cat.class); System.out.println(\’Agent Load Done.\’ ); ) { System.out.println(\’加载代理失败!\’) } }}2.2.2 在资源目录中设置新目录META-INF,并添加该目录在.MF 文件
MANIFREST.MF 文件的作用
Premain-Class:包含premain 方法的类(类的完整路径名) Agent-Class:包含Agentmain 方法的类(类的完整路径名) Can-Redefine-Classes:true 表示该代理所需的类无法重新定义马苏。默认值为false(可选)。 Can-Retransform-Classes:true 意味着该代理可以重新定义它需要的类。 2.2.3 打包agent jar包添加以下插件打包maven pom agent jar 包
. org.apache.maven.plugins maven-assemble-plugin jar-with-dependency src/main/resources/META-INF/MANIFEST.MF 附加包2.2.4 目标JVM进程启动目标进程并获取JVM过程。皮多
public static void main(String[] args) { String name=ManagementFactory.getRuntimeMXBean().getName(); String s=name.split(\’@\’)[0]; //输出当前Pid 4375 System.out 。 println (\’pid:\’+s); Cat Cat cat=new Cat();}2.2.5 Attacher JVM 进程启动一个新的Attacher 进程并设置以下VM 参数。
-javaagent:/Users/**/IdeaProjects/huskey/huskey-agent/target/huskey-agent-1.0.0-SNAPSHOT-jar-with-dependency.jarAttacher进程代码
public class Attacher { public static void main(String[] args) { try { String pid=\’4375\’ VirtualMachine.attach(pid);Agent/Target/huskey-agent-1.0.0-SNAPSHOT-jar-with-dependency. jar\’); Thread.sleep(100000000) } catch (Exception e) { e.printStackTrace() }}2.3 重新加载类对执行的影响:首先,运行目标JVM 进程并获取pid。控制台每5 秒显示一次“cat is sleep”输出。接下来,启动Attacher 上的main() 方法并向其传递目标JVM 的PID。现在,如果您返回到目标JVM 的控制台,您将看到在打印“start”和“end”之前和之后每5 秒打印一次“cat is sleep”。即运行时字节码增强。已完成并且该类已重新加载。
本文和图片来自网络,不代表火豚游戏立场,如若侵权请联系我们删除:https://www.huotun.com/game/583597.html