在ASM的核心组件中,Opcodes接口定义了一些常量,尤其是版本号、访问标识符、字节码等信息。ClassReader用于读取Class文件,它的作用时进行Class文件的解析,并可以接受一个ClassVisitor,ClassReader会将解析过程中产生的类的部分信息,比如访问提示符、字段、方法逐个送入ClassVisitor,Class Visitor在接受到对应的类信息后,可以进行各自的处理。
ClassVisitor有一个重要的子类为ClassWriter,它负责进行Class文件的输出和形成。ClassVisitor在进行字段和方法的处理的时候,会委托给FieldVisitor和MethodVisitor进行处理。因此在类的处理过程中,会创建相应的FieldVisitor和MethodVisitor对象。FieldVisitor和FieldVisitor也各自有一个重要的子类,FieldWriter和MethodWriter。当classWriter进行字段和方法的处理时,也是依赖这两个类进行的。
示例1--ASM入门helloworld
/**
* author:yinweicheng
*/
package edu.hrbeu.asm;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.InvocationTargetException;
/**
* 继承classloader,方便立即被系统加载
* 实现Opcodes,方便访问全局变量
*/
public class AsmHelloWorld extends ClassLoader implements Opcodes{
/**
*
* @param args 程序参数
* @throws InvocationTargetException 类加载异常
* @throws IllegalAccessException 初始化实例失败
*/
public static void main(final String[] args) throws InvocationTargetException, IllegalAccessException {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
//设置类的基本信息
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
//生成example的构造函数
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mw.visitInsn(RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
//生成main主函数
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mw.visitLdcInsn("hello world!");
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mw.visitInsn(RETURN);
mw.visitMaxs(0,0);
mw.visitEnd();
//生成class文件流的二进制表示
byte[] code = cw.toByteArray();
//将生成的文件流载入系统,并通过反射调用main方法
AsmHelloWorld loader = new AsmHelloWorld();
Class exampleClass = loader.defineClass("Example", code,0, code.length);
exampleClass.getMethods()[0].invoke(null, new Object[]{null});
}
}
既然我们可以通过asm生成字节码文件,我们当然能够通过asm重构class文件。例如当前有一个账户account,要想在不修改account的前提下,对用户先进行安全检查,然后在进行操作。
示例--asm实现面向切面编程
account:
/**
* author:lenovo
* create_date:2018/07/11
* project:asm
**/
package edu.hrbeu.asm;
/**
*
*/
public class Account {
/**
*
*/
public void operation() throws InterruptedException {
Thread.sleep(1000);
System.out.println("operation...");
}
}
检查类:
/**
* author:lenovo
* create_date:2018/07/11
* project:asm
**/
package edu.hrbeu.asm;
/**
* class_name:SecurityChecker
* usage: 对account进行安全检查
**/
public class SecurityChecker {
/**
* 进行安全检查的方法
*
* @return 是否安全
*/
public static boolean checkSecurity() {
System.out.println("SecurityChecker.checkSercurity...");
if ((System.currentTimeMillis() & 0x1) == 0) {
System.out.println("check fail");
return false;
} else
return true;
}
}
重构方法:
/**
* author:lenovo
* create_date:2018/07/12
* project:asm
**/
package edu.hrbeu.asm;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* class_name:AddSecurityCheckMethodAdapter
* usage: 添加方法检查,将检查方法添加到运行方法上
**/
public class AddSecurityCheckMethodAdapter extends MethodVisitor {
/**
* 继承构造方法
*
* @param mv :methodvisitor
*/
public AddSecurityCheckMethodAdapter(final MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
public void visitCode() {
Label continueLabel = new Label();
visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/asm/SecurityChecker", "checkSecurity", "()Z");
visitJumpInsn(Opcodes.IFNE, continueLabel);
visitInsn(Opcodes.RETURN);
visitLabel(continueLabel);
super.visitCode();
}
}
/**
* author:lenovo
* create_date:2018/07/11
* project:asm
**/
package edu.hrbeu.asm;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* class_name:AddSecurityCheckClassAdapter
* useage: 添加类检查,重构运行方法
**/
public class AddSecurityCheckClassAdapter extends ClassVisitor {
/**
* 继承构造方法
*
* @param classVisitor :定义类观察器
*/
public AddSecurityCheckClassAdapter(final ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
/**
* @param access : 访问标志
* @param name : 名称索引
* @param desc :描述符索引
* @param signature :标志位
* @param exceptions :异常表
* @return :返回MethodVisitor
*/
public MethodVisitor visitMethod(final int access, final String name, final String desc,
final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("operation")) {
wrappedMv = new AddSecurityCheckMethodAdapter(mv);
}
}
return wrappedMv;
}
}
/**
* author:lenovo
* create_date:2018/07/11
* project:asm
**/
package edu.hrbeu.asm;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* class_name:SecurityWeaveGenerator
* usage:
**/
public class SecurityWeaveGenerator {
/**
* 通过asm重构检查类account的class文件,IDEA的class文件路径与eclipse路径不同
*
* @param args args
* @throws IOException 读写异常
*/
public static void main(final String[] args) throws IOException {
String classname = Account.class.getName();
ClassReader cr = new ClassReader(classname);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
File file = new File("out/production/asm/" + classname.replaceAll("\\.", "/") + ".class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
在对account的class文件进行重构后,在运行operation方法之前首先要运行checkSecurity方法,这样创建一个演示类运行operation方法查看输入结果。
/**
* author:lenovo
* create_date:2018/07/12
* project:asm
**/
package edu.hrbeu.asm;
/**
* class_name:RunAccountMain
* useage:
**/
public class RunAccountMain {
/**
* @param args
*/
public static void main(String[] args) {
Account account = new Account();
account.operation();
}
}
输出结果:
D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=10556:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
SecurityChecker.checkSercurity...
operation...
Process finished with exit code 0
可以看到,在运行operation方法前首先运行了checkSecurity方法。相当于aop中的前置通知,那么怎么实现环绕通知呢?假设要对用户的操作时间进行统计,怎么在不修改account的基础上进行呢?
添加计算时间类:
/**
* author:lenovo
* create_date:2018/07/12
* project:asm
**/
package edu.hrbeu.surround;
/**
* class_name:TimeStat
* usage:
**/
public class TimeStat {
/**
* 计时
*/
static ThreadLocal<Long> t = new ThreadLocal<Long>();
/**
* 设置开始时间
*/
public static void start() {
t.set(System.currentTimeMillis());
}
/**
*输出结束时间
*/
public static void end() {
long time = System.currentTimeMillis() - t.get();
System.out.println(Thread.currentThread().getStackTrace()[2] + "spend:" + time);
}
}
/**
* author:lenovo
* create_date:2018/07/12
* project:asm
**/
package edu.hrbeu.surround;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* class_name:TimeStatClassAdapter
* usage:
**/
public class TimeStatClassAdapter extends ClassVisitor {
public TimeStatClassAdapter(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
public MethodVisitor visitMethod(final int access, final String name, final String desc,
final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("operation")) {
wrappedMv = new TimeStatMethodAdapter(mv);
}
}
return wrappedMv;
}
}
/**
* author:lenovo
* create_date:2018/07/12
* project:asm
**/
package edu.hrbeu.surround;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* class_name:TimeStatMethodAdapter
* usage:
**/
public class TimeStatMethodAdapter extends MethodVisitor implements Opcodes {
public TimeStatMethodAdapter(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
public void visitCode() {
visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "start", "()V");
super.visitCode();
}
public void visitInsn(int opcode) {
if (opcode >= IRETURN && opcode <= RETURN) {
visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "end", "()V");
}
mv.visitInsn(opcode);
}
}
修改weaveGenerate类,重新覆写class文件,运行后的输出结果为:
D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=11956:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
operation...
edu.hrbeu.asm.Account.operation(Unknown Source)spend:1000
Process finished with exit code 0