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字节码意思

Spring最深处——ASM_编程

可使用IDEA下的一个Plugins(插件)下安装一个jclasslib Bytecode viewer插件

安装完成后,点击菜单栏中View下的Show Bytecode With jclasslib

如下图所示这10项是一个class文件中所有包含的内容

Spring最深处——ASM_编程_02

2.使用ASM读取一个类

在官方文档 2.2.2中

如何解析一个类呢,我们需要自定义一个ClassPrinter继承ClassVisitor这个类

ASM给我们提供一个接口,我们可以写自定义一个ClassVistor的访问者,访问Class中的每一个节点,如:版本、名称、方法等,可以访问的很多,这里只是一部分

我们去重写visit方法,程序一但执行,当我们去访问class文件的时候,在visit中就会打印出其内容,

当我们把一个类都访问了一遍,执行visitEnd()方法;例:

Spring最深处——ASM_编程_03

自建一个测试类

如:

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"));//读取测试类内容

执行结果

Spring最深处——ASM_编程_04

可以看到把T1这个类的内容读取了出来

3.使用ASM写一个类

参考官方文档 2.2.3

Spring最深处——ASM_编程_05
我们自己将其进行一些修改

Spring最深处——ASM_编程_06

/*自定义一个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"类型

运行结果:

Spring最深处——ASM_编程_07

4.使用ASM转换一个类

当我们学会如何使用ASM读和写后,把原来的类读出来,读出来后的内容加上自己写的动态代理生成新的内容,直接生成在内存中.

  1. 参考官方文档2.2.4 Transforming classes(改变一个类)

Spring最深处——ASM_编程_08

/*
*如果我们直接使用,就是将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

如下图:

Spring最深处——ASM_编程_09

让我们自己来试一下

如下图所示,首先是读取一个类,图中所读取的类是Tank.class

Spring最深处——ASM_编程_10

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这个类中,

Spring最深处——ASM_编程_11
然后我们运行这个程序,就会看到before…

在到Tank_0类中,可以看到move()这个方法中多了一句话:TimeProxy.before()

Spring最深处——ASM_编程_12

而TimeProxy类就是代理,它里面有一个before()方法

package com.mashibing.dp.ASM
/*TimeProxy类*/
public class TimeProxy{
	public static void before() {
		System.out.println("before...");
	}
}