ASM
背景
寫文章特別喜歡寫背景,感覺如果不寫背景就沒法回憶出來當時為什么要搞這個東西。好了,因為之前參與的一個項目的故障演練模塊覺得做的只是基于spring的Bean的代理做的,這樣對業(yè)務的侵入性比較強,如果業(yè)務沒有依賴于spring腫么辦呢?現(xiàn)在我們來看看ASM是怎么做的
ASM可以干什么
大名鼎鼎的CGLIB其實底層就是ASM。通過ASM的字節(jié)碼操作,可以動態(tài)創(chuàng)建新的類型,可以為類增加新的功能呢。雖然可以使用CGLIB這些高級的庫也可以完成大量的工作,但是如果直接使用ASM還是有很多好處的,例如ASM的性能是最好的,靈活度是最好的,功能也是最為強大的,可以將操作粒度控制到每一條指令。
簡單的進行字節(jié)碼織入操作
例如我們只有一個簡單的Account類,該類也只有一個方法operation方法。
public class Account {
public void operation() {
System.out.println("operation ...");
}
}
現(xiàn)在我們要在這個操作之前進行一定的驗證,例如加入一些檢查權(quán)限的操作checkSecurity()。我們將添加一個名稱為SecurityChecker的類。這個類中的方法可以幫助我們進行一些權(quán)限校驗。
public class SecurityChecker {
public static boolean checkSecurity() {
System.out.println("SecurityChecker.checkSecurity ...");
if ((System.currentTimeMillis() & 0x1) == 0) {
return false;
} else {
return true;
}
}
}
我們在不修改原來Account的代碼的前提下,如何增加校驗操作呢?我們可以直接修改類的字節(jié)碼進行代碼織入操作,從而改變Account代碼的執(zhí)行狀態(tài)。
我們先看一下單獨運行Account類的執(zhí)行效果把~

可以看到只是打印出來了operation ...
進行改寫字節(jié)碼
通過如下代碼我們可以將代碼的字節(jié)碼進行修改,并且覆蓋掉原來的編譯好的字節(jié)碼,從而改變類的執(zhí)行狀態(tài)。
首先我們需要幾個類對象,第一個
負責Class改寫的適配器類
AddSecurityCheckClassAdapter繼承自ClassVisitor負責Method改寫的適配器類
AddSecurityCheckMethodAdapter繼承自MethodVisitor負責調(diào)用Adapter的
SecurityWeaveGeneratior
AddSecurityCheckClassAdapter
負責修改類文件中的字節(jié)碼
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中的字節(jié)碼
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
讀取類信息,進行字節(jié)碼織入
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();
}
}
運行結(jié)果

還是運行原來的main方法,自動加入了檢查安全的操作。
對比一下前后的字節(jié)碼
前:

后:

對比前后,明顯可以看到在字節(jié)碼中增加了我們操作ASM寫入的相關字節(jié)碼。
寫在最后,最近生病是在身體乏力,寫的文章自己認為也只講了簡單的如何使用后面再好好補補,這個地方會和前面的服務保護有一定的串聯(lián)!請多多期待!