ASM
背景
写文章特别喜欢写背景,感觉如果不写背景就没法回忆出来当时为什么要搞这个东西。好了,因为之前参与的一个项目的故障演练模块觉得做的只是基于spring的Bean的代理做的,这样对业务的侵入性比较强,如果业务没有依赖于spring肿么办呢?现在我们来看看ASM是怎么做的
ASM可以干什么
大名鼎鼎的CGLIB其实底层就是ASM。通过ASM的字节码操作,可以动态创建新的类型,可以为类增加新的功能呢。虽然可以使用CGLIB这些高级的库也可以完成大量的工作,但是如果直接使用ASM还是有很多好处的,例如ASM的性能是最好的,灵活度是最好的,功能也是最为强大的,可以将操作粒度控制到每一条指令。
简单的进行字节码织入操作
例如我们只有一个简单的Account类
,该类也只有一个方法operation
方法。
public class Account {
public void operation() {
System.out.println("operation ...");
}
}
现在我们要在这个操作之前进行一定的验证,例如加入一些检查权限的操作checkSecurity()
。我们将添加一个名称为SecurityChecker
的类。这个类中的方法可以帮助我们进行一些权限校验。
public class SecurityChecker {
public static boolean checkSecurity() {
System.out.println("SecurityChecker.checkSecurity ...");
if ((System.currentTimeMillis() & 0x1) == 0) {
return false;
} else {
return true;
}
}
}
我们在不修改原来Account
的代码的前提下,如何增加校验操作呢?我们可以直接修改类的字节码进行代码织入操作,从而改变Account代码的执行状态。
我们先看一下单独运行Account类的执行效果把~
可以看到只是打印出来了operation ...
进行改写字节码
通过如下代码我们可以将代码的字节码进行修改,并且覆盖掉原来的编译好的字节码,从而改变类的执行状态。
首先我们需要几个类对象,第一个
负责Class改写的适配器类
AddSecurityCheckClassAdapter
继承自ClassVisitor负责Method改写的适配器类
AddSecurityCheckMethodAdapter
继承自MethodVisitor负责调用Adapter的
SecurityWeaveGeneratior
AddSecurityCheckClassAdapter
负责修改类文件中的字节码
public class AddSecurityCheckClassAdapter extends ClassVisitor {
public AddSecurityCheckClassAdapter(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
@Override
public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
MethodVisitor mv = super.visitMethod(i, s, s1, s2, strings);
MethodVisitor wrappedMv = mv;
if (mv != null){
if (s.equals("operation")){
wrappedMv = new AddSecurityCheckMethodAdapter(mv);
}
}
return wrappedMv;
}
}
AddSecurityCheckMethodAdapter
负责修改某个method中的字节码
public class AddSecurityCheckMethodAdapter extends MethodVisitor {
public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitCode() {
Label continueLabel = new Label();
visitMethodInsn(Opcodes.INVOKESTATIC, "com/wsqandgy/asm/SecurityChecker", "checkSecurity", "()Z");
visitJumpInsn(Opcodes.IFNE, continueLabel);
visitInsn(Opcodes.RETURN);
visitLabel(continueLabel);
super.visitCode();
}
}
SecurityWeaveGeneratior
读取类信息,进行字节码织入
public class SecurityWeaveGeneratior {
public static void main(String[] args) throws Exception {
String className = Account.class.getName();
ClassReader classReader = new ClassReader(className);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
File file = new File("/Users/gongyan/Documents/home_code/tools/apache/target/classes/" + className.replaceAll("\\.", "/") + ".class");
if (file.exists()){
System.out.println("exists");
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(data);
fileOutputStream.close();
}
}
运行结果
还是运行原来的main方法,自动加入了检查安全的操作。
对比一下前后的字节码
前:
后:
对比前后,明显可以看到在字节码中增加了我们操作ASM写入的相关字节码。
写在最后,最近生病是在身体乏力,写的文章自己认为也只讲了简单的如何使用后面再好好补补,这个地方会和前面的服务保护有一定的串联!请多多期待!