网站首页 > java教程 正文
1 字节码增强技术
字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。字节码的实现方式有下图几种:
1.1 ASM
ASM可以直接生成.class字节码文件,也可以在类被加载入JVM之前动态修改类行为。 ASM的应用场景有AOP(Cglib就是基于ASM)、热部署、修改其他jar包中的类等。
过程如下
(1)先通过ClassReader读取编译好的.class文件
(2)其通过访问者模式(Visitor)对字节码进行修改 常见的Visitor类有:
- MethodVisitor,对方法进行修改
- FieldVisitor,对变量进行修改的等
- AnnotationVisitor, 访问注解
(3)通过ClassWriter重新构建编译修改后的字节码文件、或者将修改后的字节码文件输出到文件中
不足 ASM虽然可以达到修改字节码的效果,但是代码实现上更偏底层,是一个个虚拟机指令的集合。
IDEA下的插件:ASM Bytecode Outline 能够将Java代码转换成ASM中的指令实现.
1.2 JavaAssist
利用Javassist实现字节码增强时,能动态改变类的结构或者动态生成类。直接使用java编码的形式,而不需要了解虚拟机指令,编程简单。
JavaAssist中核心的类是ClassPool、CtClass、CtMethod、CtField。
- ClassPool:保存CtClass的Map,通过classPool.get(类全路径名)来获取CtClass
- CtClass:编译时类信息,它是一个class文件在代码中的抽象表现形式
- CtMethod:对应类中的方法
- 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\"); }");
Class c = cc.toClass();
A a = (A) c.newInstance();
a.method();
}
}
输出
start
method...
end
1.3 instrument
Java 从 1.5 开始提供了 java.lang.instrument,为Java 程序提供 API,比如用于监控、收集性能信息、诊断问题等。
Instrumentation 是 java.lang.instrument 包下的一个接口,这个接口允许我们对已加载和未加载的类进行修改。
Instrumentation接口定义
public interface Instrumentation {
//为Instrumentation 注册一个类文件转换器,可以修改读取类文件字节码
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
//对JVM已经加载的类重新触发类加载
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
//获取当前 JVM 加载的所有类对象
Class[] getAllLoadedClasses();
}
调用Instrumentation#addTransformer设置 transformer以后,后续JVM加载所有类之前都会被这个transform方法拦截。
ClassFileTransformer接口定义
public interface ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException {
// 在这里读取、转换类文件
return classBytes;
}
}
ClassFileTransformer接口的transform方法会在类文件被加载时调用,transform方法接收原类文件的字节数组,而在方法里,可以利用ASM或Javassist对传入的字节码进行改写或替换,生成新的字节码数组后返回。
2 Java Agent
2.1 Java Agent 简介
Java Agent 使用 Instrumentation 接口(java.lang.instrument)来编写 Agent,Instrumentation 的 API 用来读取和改写当前 JVM 的类。
Java Agent 是?个特殊的 Jar 包,它并不能单独启动的,而必须依附于一个 JVM 进程。
Java Agent 有premain 和 agentmain 两种:
premain代理:这个代理类包含一个 premain 方法。JVM 在类加载时候会先执行代理类的 premain 方法,再执行 Java 程序本身的 main 方法,这就是 premain 名字的来源。在 premain 方法中可以对加载前的 class 文件进行修改。
agentmain代理:在JVM 启动后通过JVMTI的Attach API机制 远程加载。通过Attach API,我们可以访问已经启动的 Java 进程,从而拦截类的加载。从 JDK 1.6 开始Instrumentation支持了在运行时对类定义的修改。
Agent分为两种, 一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)
2.2 实现一个Java Agent
使用Java Agent需要几个步骤:
- 定义一个 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();
}
}
}
public void sleep() {
System.out.println("cat is sleeping");
}
}
preMain
public class PreMainTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.equals("com.dc.husky.agent.Cat")) {
return null;
}
System.out.println("premain transform Class:" + className);
return classfileBuffer;
}
}
agentMain
public class AgentMainTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println("agentMain transform Class:" + className);
try {
ClassPool cp = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(this.getClass());
cp.insertClassPath(classPath);
CtClass cc = cp.get("com.dc.husky.agent.Cat");
CtMethod m = cc.getDeclaredMethod("sleep");
m.insertBefore("{ System.out.println("start"); }");
m.insertAfter("{ System.out.println("end"); }");
return cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
定义Agent
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new PreMainTransformer(), true);
System.out.println("premain agent loaded !");
}
public static void agentmain(String args, Instrumentation inst) {
inst.addTransformer(new AgentMainTransformer(), true);
try {
//重定义类并载入新的字节码
inst.retransformClasses(Cat.class);
System.out.println("Agent Load Done.");
} catch (Exception e) {
System.out.println("agent load failed!");
}
}
}
2.2.2 配置
在 resources 目录下新建目录:META-INF,在该目录下新建MANIFREST.MF文件
MANIFREST.MF文件的作用
- Premain-Class :包含 premain 方法的类(类的全路径名)
- Agent-Class :包含 agentmain 方法的类(类的全路径名)
- Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
- Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
2.2.3 打包agent jar包
maven pom加入下列plugin,打包agent jar包
<build>
<plugins>
<plugin>
...
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.2.4 目标 JVM 进程
启动目标进程,得到当前的Pid
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 = new Cat();
cat.beginSleep();
}
2.2.5 Attacher JVM 进程
新启动一个Attacher进程,配置下列vm参数
-javaagent:/Users/**/IdeaProjects/huskey/huskey-agent/target/huskey-agent-1.0.0-SNAPSHOT-jar-with-dependencies.jar
Attacher进程代码
public class Attacher {
public static void main(String[] args) {
try {
String pid = "4375";
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent("/Users/qian/IdeaProjects/huskey/huskey-agent/target/huskey-agent-1.0.0-SNAPSHOT-jar-with-dependencies.jar");
Thread.sleep(100000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 效果
以下为运行时重新载入类的效果: 先运行目标 JVM 进程,得到pid,可以在控制台看到每隔五秒输出一次”cat is sleeping”。接着启动Attacher中的main()方法,并将目标JVM 的pid传入。此时回到目标JVM的控制台,可以看到现在每隔五秒输出”cat is sleeping”前后会分别输出”start”和”end”,也就是说完成了运行时的字节码增强,并重新载入了这个类。
- 上一篇: 教你用Java字节码做点有趣的事
- 下一篇: JVM系列:几张图看懂java字节码
猜你喜欢
- 2025-01-07 Python GUI 编程:tkinter 初学者入门指南——几何布局管理器 Place
- 2025-01-07 偷天换日,用JavaAgent欺骗你的JVM
- 2025-01-07 大数据必学Java基础(二):Java核心机制
- 2025-01-07 求你了,别再说 Java 对象都是在堆内存上分配空间了
- 2025-01-07 Java 虚拟机是什么?——探秘 JVM 的核心机制!
- 2025-01-07 5.2 JVM常见问题及面试题
- 2025-01-07 JVM实战—1.Java代码的运行原理
- 2025-01-07 浅谈字节码增强技术系列2-Asm与Cglib
- 2025-01-07 【JVM字节码】Class文件与字节码,机器码的关系
- 2025-01-07 虚拟机字节码操作黑科技工具简介
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)