1.ASM
ASM官网—官方文档
ASM是什么
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
ASM能做什么
用一句话总结:ASM能够修改.class文件,从而动态生成类或者增强现有类的功能
查看一个.class文件
打开一个class文件,所有Java编译后的文件都是cafe babe开头,只要看到这个开头
都是Java class编译后的文件,可以理解为Java的字节码文件
Java虚拟机规范 里面详细介绍了每一个Java字节码意思
可使用IDEA下的一个Plugins(插件)下安装一个jclasslib Bytecode viewer插件
安装完成后,点击菜单栏中View下的Show Bytecode With jclasslib
如下图所示这10项是一个class文件中所有包含的内容
2.使用ASM读取一个类
在官方文档 2.2.2中
如何解析一个类呢,我们需要自定义一个ClassPrinter继承ClassVisitor这个类
ASM给我们提供一个接口,我们可以写自定义一个ClassVistor的访问者,访问Class中的每一个节点,如:版本、名称、方法等,可以访问的很多,这里只是一部分
我们去重写visit方法,程序一但执行,当我们去访问class文件的时候,在visit中就会打印出其内容,
当我们把一个类都访问了一遍,执行visitEnd()方法;例:
自建一个测试类
如:
package com.mashibing.dp.ASM
public class T1{
int i = 0;
public void m(){
int j = 1;
}
}
ClassReader cr = new
ClassReder(ClassPrinter.class.getClassLoader().getResourceAsSteream(
"com/mashibing/db/ASM/T1.class"));//读取测试类内容
执行结果
可以看到把T1这个类的内容读取了出来
3.使用ASM写一个类
参考官方文档 2.2.3
我们自己将其进行一些修改
/*自定义一个ClassWriter对象*/
ClassWriter cw = new ClassWriter(0);
/*生成1.5版本,名为comparable,父类为Object*/
cw.visit(V1_5,ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE,"pkg/comparable",null,"java/long/Object",null);
/*生成Field,名字是"LESS",类型是int,值是-1*/
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,"LESS","I",null,-1).visitEnd();
...
/*最后生成一个方法,方法名为"compareTo",参数是java/lang/Object,*/
cw.visitMethod(ACC_PUBLIC+ACC_ABSTRACT,"compareTo","(Ljava/lang/Object;)I",null,null).visitEnd();
/*将其转换为二进制数组*/
byte[] b = cw.toByteArray();
/*使用这个字节数组*/
MyclassLoader myClassLoader = new MyClassLoader();//自定义ClassLoader
/*load为一个Class对象,这里会涉及到Java的反射*/
Class c = myClassLoader.defineClass("pkg.Comparable",b);
/*然后将其打印出来,由于是一个这是一个接口,所以他没有构造方法,第0个方法就是"comparable"*/
System.out.println(c.getMethods()[0].getName());
Java虚拟机规范里指定了返回值为int类型,所以我们必须要写成"Int"类型
运行结果:
4.使用ASM转换一个类
当我们学会如何使用ASM读和写后,把原来的类读出来,读出来后的内容加上自己写的动态代理生成新的内容,直接生成在内存中.
- 参考官方文档2.2.4 Transforming classes(改变一个类)
/*
*如果我们直接使用,就是将Reader复制到Writer
*/
byte[] b1 = ...;//被改变的类
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(b1);
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray();//将其写出来的转变为字节数字进行修改
/*所以在其中间添加一个ClassVisitor*/
byte[] b1 = ...;
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(b1);
ClassVisitor cv = new ClassVisitor(ASM4, cw) {};
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray();
我们需要在Reader传给Writer中添加一个Adapter,将Reader传过来的经过Adapter修改一下,在传给Writer
如下图:
让我们自己来试一下
如下图所示,首先是读取一个类,图中所读取的类是Tank.class
Tank类中非常简单,注意的是它没有实现任何接口
package com.mashibing.dp.ASM
/*Tank类*/
public class Tank{
public void move(){
System.out.println("Tank Moving ClaClaCla...");
}
}
我们在回到图一,当程序执行第14行代码时,Tank类就被load至内存,
然后看第24行这个方法visitCode(),这个方法是访问它的机器码
/*图一摘取*/
public void visitCode() {
//添加的指令,静态调用,调用了TimeProxy这个类里的before方法
visitMethodInsn(INVOKESTATIC,"com/mashibing/dp/ASM/TimeProxy"
,"before","()V",false);
super.visitCode();//如果只调用此方法相当于直接复制
}
一个类的Visitor去访问类,一个方法的Visitor去访问方法
从图1可以看出,这是Visitor套Visitor我们把一个class load到内存,里面有一方法叫move(),写一个Class Visitor 去访问这个Class 如图中:Tank.class.
当我们访问到这个方法的时候,又使用访问方法的Visitor去访问这个方法:move(),访问这个方法的Visitor 里面重写了它的VisitorCode()方法,当访问到VisitorCode()这个方法里面
在super.visitCode()前加了一个指令让他静态的去访问TImeProxy类里的before()这个方法.最后生成到Tank_0.class这个类中,
然后我们运行这个程序,就会看到before…
在到Tank_0类中,可以看到move()这个方法中多了一句话:TimeProxy.before()
而TimeProxy类就是代理,它里面有一个before()方法
package com.mashibing.dp.ASM
/*TimeProxy类*/
public class TimeProxy{
public static void before() {
System.out.println("before...");
}
}