前言
- JVM在运行时,加载并执行class文件,这个class文件是由编写好的java源文件经过javac编译而得到。
- 有时候会遇到一种情况,前期编写程序时不知道要写什么类,只有到运行时,才能根据当时的程序执行状态知道要使用什么类。
- 常见的处理方式可以使用JDK中的动态代理
- 还有一个叫做ASM的库,能够直接生成class文件,它的api对于动态代理来说更加原生,每个api都和class文件格式中的特定部分吻合。
动态生成如下class文件:
public class AsmDemo11 {
public static void main(String[] var0) {
System.out.println("Hello ASM 123");
}
}
1.使用ASM框架动态生成java类和main方法
添加依赖:
//ASM依赖
implementation 'org.ow2.asm:asm:7.2'
implementation 'org.ow2.asm:asm-commons:7.2'
代码实现:
public class MyClass extends ClassLoader implements Opcodes {
public static void main(String[] args) throws Exception {
System.out.println("-----MyClass----");
//定义一个类模版
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_7, ACC_PUBLIC, "AsmDemo11", null, "java/lang/Object", null);
//TODO 1构造默认构造函数
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
"",
"()V",
null, null);
//生成构造函数字节码指令 -- 加载操作
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"",
"()V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
//TODO 2.构造main函数
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC,
"main",
"([Ljava/lang/String;)V",
null,
null);
//TODO 3.main方法中生成 System.out.println("Hello ASM");
//获取System类中的属性 System.out -- public static final PrintStream out;
mv.visitFieldInsn(GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;");
//栈帧中 属性入栈
mv.visitLdcInsn("Hello ASM 123");
//加载 println 方法
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
//todo 4.获取生成的class 文件对应的二进制流
byte[] codes = cw.toByteArray();
//将二进制流写入到本地磁盘上
FileOutputStream fos = new FileOutputStream("AsmDemo11.class");
fos.write(codes);
fos.close();
//反射调用
MyClass loader = new MyClass();
Class> defineClass = loader.defineClass("AsmDemo11", codes, 0, codes.length);
defineClass.getMethods()[0].invoke(null, new Object[]{null});
}
}
日志打印结果:
-----MyClass----
Hello ASM 123
代码解析
1.使用ClassWriter定义一个类
//定义一个类模版
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_7, ACC_PUBLIC, "AsmDemo11", null, "java/lang/Object", null);
- ClassWriter的visit方法定义一个类
- 参数1:V1_7 - 是生成class的版本号,对应class文件中的主版本号和次版本号,
- 参数2:ACC_PUBLIC - 表示该类的访问标识。这是一个public类,对应class文件的access_flags
- 参数3:生成类的类名,需要传入全类名。对应class文件中的this_class
- 参数4:表示泛型,传入null表示这不是一个泛型类。对那个class文件中的Signature属性(attribute)
- 参数5:当前类的父类的全限类名。该类直接继承自Object。对应class文件中的super_class
- 参数6:表示当前生成类的直接实现接口,为string[]类型,可以实现多个接口。这个类没有实现任何接口,所以传入null。敌营class文件中的interfaces
2.定义默认构造函数
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
"",
"()V",
null, null);
//生成构造函数字节码指令 -- 加载操作
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"",
"()V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
- 调用ClassWriter的visitMethod方法返回一个MethodVisitor对象,该对象定义了一个方法,对应class文件中的一个method_info
- 参数1:ACC_PUBLIC - 指定生成方法的访问标志,对应method_info中的access_flags
- 参数2:方法的方法名。构造方法名为< init >,对应method_info中的name_index,引用常量池中的方法名字符串
- 参数3:方法描述符,因为生成的构造方法无入参和无返回值,所以方法描述符为()V.对应method_info中的descriptor_index
- 参数4:泛型相关,传入nulli表示该方法不是泛型方法。对应method_info中的Signature属性
- 参数5:指定方法声明可能抛出的异常。传入null表示无异常声明抛出。对应method_info中的Exceptions属性
- 接着调用MethodVisitor中的方法,生成当前构造方法的字节码,对应method_info中的Code属性
- 调用visitVarInsn方法,生成aload指令,将第0个本地变量(也就是this)压入操作数栈
- 调用visitMethodInsn方法,生成invokespecial指令,调用父类(也就是Object)的构造方法
- 调用visitInsn 方法,生成return指令,方法返回
- 调用visitMaxs 方法,指定当前要生成方法的最大局部变量和最大操作数栈。对应Code属性中的max_stack和 max_locals
- 最后调用visitEnd 方法,表示当前要生成的构造方法已经创建完成
3.定义main方法,并生成main方法中的字节码指令
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC,
"main",
"([Ljava/lang/String;)V",
null,
null);
//TODO 3.main方法中生成 System.out.println("Hello ASM");
//获取System类中的属性 System.out -- public static final PrintStream out;
mv.visitFieldInsn(GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;");
//栈帧中 属性入栈
mv.visitLdcInsn("Hello ASM 123");
//加载 println 方法
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
2.ASM动态生成类和字段,并给字段加上注解
- 最后实现效果
package com.timmy.asmstudy;
import com.sun.istack.internal.NotNull;
public class Person {
@NotNull
public String name;
}
- 该类对应的字节码
package asm.com.timmy.asmstudy;
import java.util.*;
import org.objectweb.asm.*;
public class AsmDemoDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/timmy/asmstudy/AsmDemo", null, "java/lang/Object", null);
cw.visitSource("AsmDemo.java", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(3, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lcom/timmy/asmstudy/AsmDemo;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(6, l0);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello ASM");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(7, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 0);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
- 使用ASM动态生成类Person.java和该类里面的name属性
/**
* 动态生成如上诉Person类
* 1。使用ClassWrite创建定义Person类
* 2。生成构造函数
* 3。生成属性 name
*/
public class BeanTest extends ClassLoader {
public static void main(String[] args) throws Exception {
//定义类
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_7,
ACC_PUBLIC + ACC_SUPER,
"com/timmy/asmstudy/Person11",
null,
"java/lang/Object",
null);
//构造函数
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
"",
"()V",
null,
null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"",
"()V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
//构建属性name
FieldVisitor fv = cw.visitField(ACC_PUBLIC,
"name11",
"Ljava/lang/String;",
null,
null);
{
//为属性添加注解
AnnotationVisitor av0 = fv.visitAnnotation(
"Lcom/sun/istack/internal/NotNull;",
false);
av0.visitEnd();
}
fv.visitEnd();
//生成并加载调用
byte[] bytes = cw.toByteArray();
BeanTest beanTest = new BeanTest();
Class> aClass = beanTest.defineClass(null, bytes, 0, bytes.length);
//使用classLoader加载好后,存在内存中,通过反射创建对象
Object beanObj = aClass.getConstructor().newInstance();
//拿到属性 --并设置值
Field field = aClass.getField("name11");
field.set(beanObj, "zzzzzzz");
String nameVal = (String) field.get(beanObj);
System.out.println("name value:" + nameVal);
}
}
打印结果:
> Task :AsmStudy:BeanTest.main()
name value:zzzzzzz
3.ASM核心api解析
- ClassReader类:字节码的读取与分析引擎,采用事件读取机制,每当有事件发生时,调用注册的ClassVisitor,AnnotationVisitor,FieldVisitor,MethodVisitor做相应的处理
- ClassVisitor类:定义在读取Class字节码时会触发的事件,如类头解析完成,注解解析,字段解析,方法解析等
- AnnotationVisitor类:定义在解析注解时会触发的事件,如解析到一个基本值类型的注解,enum值类型的注解,注解值类型的注解等
- FieldVisitor类:定义在解析字段时触发的事件,如解析到字段相关的属性等
- MethodVisitor类:定义在解析方法时触发的事件,如方法上的注解,属性,代码等
- ClassWriter等:实现了ClassVisitor抽象类,用于拼接字节码
3.1.ClassReader
org.objectweb.asm.ClassReader
public class ClassReader {
//构造函数
public ClassReader(final byte[] b) {
this(b, 0, b.length);
}
public ClassReader(final byte[] b, final int off, final int len) {
...
}
//核心方法
public void accept(final ClassVisitor classVisitor, final int flags) {
accept(classVisitor, new Attribute[0], flags);
}
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
...
// visits the class declaration --1.访问class文件的描述,文件头,版本等信息
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
superClass, interfaces);
// visits the source and debug info --2.访问class资源信息
if ((flags & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// visits the module info and associated attributes --3.访问模块信息
if (module != 0) {
readModule(classVisitor, context, module,
moduleMainClass, packages);
}
// visits the outer class --4.访问外部类
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
enclosingDesc);
}
// visits the class annotations and type annotations --5.访问类的注解
if (anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), true));
}
}
...
// visits the attributes --6.访问类的属性
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
classVisitor.visitAttribute(attributes);
attributes = attr;
}
// visits the inner classes --7.访问内部类
if (innerClasses != 0) {
int v = innerClasses + 2;
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
classVisitor.visitInnerClass(readClass(v, c),
readClass(v + 2, c), readUTF8(v + 4, c),
readUnsignedShort(v + 6));
v += 8;
}
}
// visits the fields and methods --8-9.访问字段和方法
u = header + 10 + 2 * interfaces.length;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readField(classVisitor, context, u);
}
u += 2;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readMethod(classVisitor, context, u);
}
// visits the end of the class --10.访问类信息结束
classVisitor.visitEnd();
}
//访问类的字段
private int readField(final ClassVisitor classVisitor,
final Context context, int u) {
...
// visits the field declaration
FieldVisitor fv = classVisitor.visitField(access, name, desc,
signature, value);
if (fv == null) {
return u;
}
// visits the field annotations and type annotations
if (anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), false));
}
}
if (tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// visits the field attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
fv.visitAttribute(attributes);
attributes = attr;
}
// visits the end of the field
fv.visitEnd();
return u;
}
//访问类中的方法
private int readMethod(final ClassVisitor classVisitor,
final Context context, int u) {
...
// visits the method declaration
MethodVisitor mv = classVisitor.visitMethod(context.access,
context.name, context.desc, signature, exceptions);
if (mv == null) {
return u;
}
if (mv instanceof MethodWriter) {
MethodWriter mw = (MethodWriter) mv;
if (mw.cw.cr == this && signature == mw.signature) {
boolean sameExceptions = false;
if (exceptions == null) {
sameExceptions = mw.exceptionCount == 0;
} else if (exceptions.length == mw.exceptionCount) {
sameExceptions = true;
for (int j = exceptions.length - 1; j >= 0; --j) {
exception -= 2;
if (mw.exceptions[j] != readUnsignedShort(exception)) {
sameExceptions = false;
break;
}
}
}
if (sameExceptions) {
/*
* we do not copy directly the code into MethodWriter to
* save a byte array copy operation. The real copy will be
* done in ClassWriter.toByteArray().
*/
mw.classReaderOffset = firstAttribute;
mw.classReaderLength = u - firstAttribute;
return u;
}
}
}
// visit the method parameters
if (methodParameters != 0) {
for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
}
}
// visits the method annotations
if (dann != 0) {
AnnotationVisitor dv = mv.visitAnnotationDefault();
readAnnotationValue(dann, c, null, dv);
if (dv != null) {
dv.visitEnd();
}
}
if (anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), false));
}
}
if (tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
if (mpanns != 0) {
readParameterAnnotations(mv, context, mpanns, true);
}
if (impanns != 0) {
readParameterAnnotations(mv, context, impanns, false);
}
// visits the method attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
mv.visitAttribute(attributes);
attributes = attr;
}
// visits the method code
if (code != 0) {
mv.visitCode();
readCode(mv, context, code);
}
// visits the end of the method
mv.visitEnd();
return u;
}
}
- 源码解析
- 构造函数参数为byte[]数组,该数组即为字节码内容,后面操作的数据都是基于该数据
- accept()方法,第1个参数是ClassVisitor,这个方法中定义了ClassVisitor访问class文件内容的顺序
- classVisitor.visit() --访问class文件的描述,文件头,版本等信息
- classVisitor.visitSource() --访问class资源信息
- classVisitor.visitModule() --访问模块信息
- classVisitor.visitOuterClass() --访问外部类
- classVisitor.visitAnnotation() -- 访问类的注解
- classVisitor.visitAttribute() --访问类的属性
- classVisitor.visitInnerClass() --访问内部类
- readField() — 访问类的字段
- readMethod() — 访问类中的方法
- classVisitor.visitEnd() —类结构访问结束
- 类中字段访问流程 readField
- FieldVisitor fv = classVisitor.visitField() —访问类中的的字段,并返回一个FieldVisitor对象
- fv.visitAnnotation() —访问字段的注解
- fv.visitAttribute() —访问字段属性
- fv.visitEnd() — 字段访问结束
- 访问类中方法流程 — readMethod
- MethodVisitor mv = classVisitor.visitMethod() —开始访问类中的方法,并返回一个MethodVisitor对象
- mv.visitParameter() — 访问方法中的入参
- AnnotationVisitor dv = mv.visitAnnotationDefault() — 访问方法的注解
- mv.visitAttribute(attributes) —访问方法的属性
- mv.visitCode(); —ASM开始扫描这个方法
- readCode(mv, context, code); — 访问方法的指令
- mv.visitMaxs() — 在visitEnd之前反复调用,去定类方法在执行时堆栈大小
- mv.visitEnd(); — 访问方法结束
3.2.ClassWriter详解
- 源码ClassWriter继承自ClassVisitor,首先看ClassVisitor类中的重要方法
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
public ClassVisitor(final int api) {}
public ClassVisitor(final int api, final ClassVisitor classVisitor) {}
public void visit() {}
public void visitSource() {}
public ModuleVisitor visitModule() {}
public void visitNestHost(final String nestHost) {}
public void visitOuterClass() {}
public AnnotationVisitor visitAnnotation() {}
public AnnotationVisitor visitTypeAnnotation() {}
public void visitAttribute(final Attribute attribute) {}
public void visitNestMember(final String nestMember) {}
public void visitInnerClass() {}
public FieldVisitor visitField() {}
public MethodVisitor visitMethod() {}
public void visitEnd() {}
}
3.3.MethodVisitor源码
org.objectweb.asm.MethodVisitor
public abstract class MethodVisitor {
protected MethodVisitor mv;
public MethodVisitor(final int api) {}
public MethodVisitor(final int api, final MethodVisitor mv) {}
public void visitParameter(String name, int access) {}
public AnnotationVisitor visitAnnotationDefault() {}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {}
public AnnotationVisitor visitTypeAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {}
public AnnotationVisitor visitParameterAnnotation(int parameter,
String desc, boolean visible) {}
public void visitAttribute(Attribute attr) {}
public void visitCode() {}
public void visitFrame(int type, int nLocal, Object[] local, int nStack,
Object[] stack) {}
//visitInsn、visitVarInsn、visitMethodInsn等以Insn结尾的方法可以添加方法实现的字节码
public void visitInsn(int opcode) {}
public void visitIntInsn(int opcode, int operand) {}
//访问局部变量指令--局部变量指令是加载loads或存储stores局部变量的指令
// ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
public void visitVarInsn(int opcode, int var) {}
public void visitTypeInsn(int opcode, String type) {}
public void visitFieldInsn(int opcode, String owner, String name,
String desc) {}
@Deprecated
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {}
//访问方法的指令 - 方法指令是调用方法的指令
//要访问的类型指令的操作码。可以是INVOKEVIRTUAL,INVOKESPECIAL,INVOKESTATIC或INVOKEINTERFACE。
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {}
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
Object... bsmArgs) {}
public void visitJumpInsn(int opcode, Label label) {}
public void visitLabel(Label label) {}
public void visitLdcInsn(Object cst) {}
public void visitIincInsn(int var, int increment) {}
public void visitTableSwitchInsn(int min, int max, Label dflt,
Label... labels) {}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {}
public void visitMultiANewArrayInsn(String desc, int dims) {}
public AnnotationVisitor visitInsnAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {}
public void visitTryCatchBlock(Label start, Label end, Label handler,
String type) {}
public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {}
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {}
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
TypePath typePath, Label[] start, Label[] end, int[] index,
String desc, boolean visible) {}
public void visitLineNumber(int line, Label start) {}
public void visitMaxs(int maxStack, int maxLocals) {}
public void visitEnd() {}
}
Label
- Label的作用是为了条件跳转,label必须对应一条字节码指令,通过mv.visitLabel(label) 来调用,并且visitLabel的调用必须紧跟着label对象制定的指令
- 如label指向goto后
mv.visitJumpInsn(Opcodes.GOTO, end);