一、ASM简介
ASM(全称:ASMifier Class Visitor)是一个java字节码操纵框架,ASM 提供了许多 API 和工具,可以直接以二进制形式读取和修改类文件、动态生成类或者增强既有类的功能。
1、 ASM 主要作用
asm用于生成、编辑、分析java的class文件
◆ 字节码生成
可以通过 ASM 生成 Java 类的字节码,可以用于生成代理类、动态生成类等场景。
◆ 字节码修改
可以通过 ASM 对已有的类字节码进行修改,实现一些类增强、方法拦截等功能。
◆ 字节码分析
可以通过 ASM 对已有的类字节码进行分析,实现一些类结构的分析和转换。
2、核心API
ASM框架中的核心类有以下几个:
◆ ClassReader
该类用来解析编译过的class字节码文件。
◆ ClassWriter
该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字
节码文件。
◆ ClassAdapter
该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。
3、工作原理
要了解Asm的工作原理,首先需要对java的字节码文件有一定了解。
◆ class文件结构
一个Class文件都对应着唯一的一个类或接口的定义信息,Class 文件是一组以 8 个字节为基础单位的二进制流。calss文件由“无符号数” 和 “表” 两种数据类型组成,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。
无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值;
表是由多个无符号数或其他表作为数据项构成的复合数据类型,以“_info”结尾。表用于描述有层次关系的复合结构的数据。
主要的字节码数据类型见3-1图表格。
(图3-1)
◆ ASM运行机制
基于上述字节码的结构特点,ASM的ClassReader
这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码,对字节码树进行遍历,在遍历过程中对字节码进行修改。
ClassReader的accept会动态绑定ClassVisitor的实现类,依次
调用接口的方法,字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader
知道如何调用各种 visit 函数。
ClassAdapter
类实现了 ClassVisitor
接口所定义的所有函数,当新建一个 ClassAdaptor
对象的时候,需要传入一个实现了 ClassVisitor
接口的对象,作为职责链中的下一个访问者Visitor,这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从ClassAdaptor
类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。
二、代码实操
1、生成字节码
现在我们通过asm生成一个Student类,类定义一个name属性及其getter setter方法
public class Student{
private String name;
private int age;
public void setName(String name) {
this.name= name;
}
public String getName() {
return name;
}
}
asm 生成类字节码
ClassWriter classWriter = new ClassWriter(0);
/// 定义类名Student 包名使用/分隔
classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/entity/Student", null, "java/lang/Object", null);
classWriter.visitSource("Student.java", null);
/// 定义name属性
FieldVisitor fieldVisitor = classWriter.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fieldVisitor.visitEnd();
/// 定义age属性
fieldVisitor = classWriter.visitField(ACC_PRIVATE, "age", "I", null, null);
fieldVisitor.visitEnd();
/// 定义空参构造器
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label initLabelStart = new Label();
methodVisitor.visitLabel(initLabelStart);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V" );
methodVisitor.visitInsn(RETURN);
Label initLabelEnd = new Label();
methodVisitor.visitLabel(initLabelEnd);
methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, initLabelStart, initLabelEnd, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
/// 定义setName方法
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
methodVisitor.visitCode();
Label setNameLabel0 = new Label();
methodVisitor.visitLabel(setNameLabel0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitFieldInsn(PUTFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");
Label setNameLabel1 = new Label();
methodVisitor.visitLabel(setNameLabel1);
methodVisitor.visitInsn(RETURN);
Label setNameLabel2 = new Label();
methodVisitor.visitLabel(setNameLabel2);
methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, setNameLabel0, setNameLabel2, 0);
methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, setNameLabel0, setNameLabel2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
/// 定义getName方法
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
methodVisitor.visitCode();
Label getNameLabel0 = new Label();
methodVisitor.visitLabel(getNameLabel0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");
methodVisitor.visitInsn(ARETURN);
Label getNameLabel1 = new Label();
methodVisitor.visitLabel(getNameLabel1);
methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, getNameLabel0, getNameLabel1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] bytes = classWriter.toByteArray();
将字节流数组生成class文件
try{
FileOutputStream fos = new FileOutputStream("F:/profile/asm/Student.class");
fos.write(bytes);
fos.close();
}catch (Exception e){
e.fillInStackTrace();
}
用idea或其他反编译工具打开,可以看到,生成源码和预想要求完全吻合
2、分析字节码
编写适配器逻辑
public class TargetClassAdapter extends ClassAdapter {
private final static Logger LOGGER = LoggerFactory.getLogger(TargetClassAdapter.class);
public TargetClassAdapter(ClassVisitor cv) {
super(cv);
}
/**访问类属性*/
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object exceptions) {
FieldVisitor fieldVisitor = cv.visitField(access, name, desc, signature, exceptions);
LOGGER.info("visitField <--------> {} {} {} {} {} ",access,name,desc,signature,exceptions);
return fieldVisitor;
}
/**访问类方法*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
LOGGER.info("visitMethod <--------> {} {} {} {} {} ",access,name,desc,signature,exceptions);
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
return methodVisitor;
}
}
访问类的属性、方法
ClassReader classReader = new ClassReader(bytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
setMethod(classWriter);
ClassVisitor classVisitor = new TargetClassAdapter(classWriter);
classReader.accept(classVisitor,ClassReader.SKIP_DEBUG);
这可以将类的属性和方法全部获取
三、ASM应用
在java的许多框架里面,都能找到ASM的身影,比如AOP编程就可以利visitMethod对指定方法就行拦截,做前置后置增强,还有比如常用的插件Lombok就是利用ASM添加的setter getter方法。MyBatis的Mapper接口实现是通过动态代理实现,现在可以使用ASM动态创建实现了字节码来实现。
1、定义接口
定义StudentMapper接口
package org.example.cn.mapper;
import java.util.List;
public interface StudentMapper {
List<String> findStudentList();
}
2、生成实现类
使用ASM生成实现类StudentMapperImpl
ClassWriter classWriter = new ClassWriter(0);
/// 参数列表第3个参数表示继承的父类,第4个参数表示实现的接口
classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/mapper/StudentMapperImpl", null, "java/lang/Object", new String[]{"org/example/cn/mapper/StudentMapper"});
classWriter.visitSource("StudentMapperImpl.java", null);
/// 定义构造器
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label initLabel0 = new Label();
methodVisitor.visitLabel(initLabel0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
methodVisitor.visitInsn(RETURN);
Label initLabel1 = new Label();
methodVisitor.visitLabel(initLabel1);
/// 局部变量表,槽位的0处放的是this变量
methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, initLabel0, initLabel1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
/// 重写findStudentList方法
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "findStudentList", "()Ljava/util/List;", "()Ljava/util/List<Ljava/lang/String;>;", null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
/// new ArrayList()
methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");
methodVisitor.visitVarInsn(ASTORE, 1);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
/// 压栈
methodVisitor.visitVarInsn(ALOAD, 1);
/// 赋值
methodVisitor.visitLdcInsn("\u9648\u7476");
///调用add方法
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
/// 出栈
methodVisitor.visitInsn(POP);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u674e\u73b0");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
methodVisitor.visitInsn(POP);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(11, label3);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u91d1\u6668");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
methodVisitor.visitInsn(POP);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(12, label4);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(ARETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("list", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", label1, label5, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] bytes = classWriter.toByteArray();
字节码jjava源码
public class StudentMapperImpl implements StudentMapper {
public List<String> findStudentList() {
List<String> list = new ArrayList();
list.add("陈瑶");
list.add("李现");
list.add("金晨");
return list;
}
}
3、加载实现类
自定义类加载器加载StudentMapperImpl字节码
public class MyClassLoader extends ClassLoader{
/// 类名全路径
private final String className;
/// 字节码
private final byte[] bytes;
public MyClassLoader( String className,byte[] bytes){
this.className = className;
this.bytes = bytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(className,bytes,0,bytes.length);
}
}
加载类
String className = "org.example.cn.mapper.StudentMapperImpl";
MyClassLoader myClassLoader = new MyClassLoader(className, bytes);
Class<?> clz = myClassLoader.loadClass(className);
4、调用实现类
◆ 方法1
利用构造器直接反射创建实例
/// Class<?> aClass = Class.forName("org.example.cn.mapper.StudentMapperImpl");
StudentMapper studentMapper = (StudentMapper) clz.getConstructor().newInstance();
List<String> studentList = studentMapper.findStudentList();
System.out.println("studentList---"+studentList);
◆ 方法2
动态代理实例化接口
StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(myClassLoader, new Class[]{StudentMapper.class}, (proxy, method, args1) -> method.invoke(clz.getConstructor().newInstance(), args1));
List<String> studentList = studentMapper.findStudentList();
System.out.println("studentList---"+studentList);
运行结果
ASM 操作字节码功能强大,但是有一定难度,需要对jvm字节码比较熟悉,用起来才会游刃有余,这种黑科技更适合造轮子框架,平常业务开发基本上很难碰到合适的应用场景。