ASM
背景
寫文章特別喜歡寫背景蚓胸,感覺如果不寫背景就沒法回憶出來當(dāng)時(shí)為什么要搞這個(gè)東西。好了马昨,因?yàn)橹皡⑴c的一個(gè)項(xiàng)目的故障演練模塊覺得做的只是基于spring的Bean的代理做的病蛉,這樣對業(yè)務(wù)的侵入性比較強(qiáng),如果業(yè)務(wù)沒有依賴于spring腫么辦呢窟哺?現(xiàn)在我們來看看ASM是怎么做的
ASM可以干什么
大名鼎鼎的CGLIB其實(shí)底層就是ASM泻轰。通過ASM的字節(jié)碼操作,可以動(dòng)態(tài)創(chuàng)建新的類型且轨,可以為類增加新的功能呢浮声。雖然可以使用CGLIB這些高級的庫也可以完成大量的工作,但是如果直接使用ASM還是有很多好處的旋奢,例如ASM的性能是最好的泳挥,靈活度是最好的,功能也是最為強(qiáng)大的黄绩,可以將操作粒度控制到每一條指令羡洁。
簡單的進(jìn)行字節(jié)碼織入操作
例如我們只有一個(gè)簡單的Account類
,該類也只有一個(gè)方法operation
方法爽丹。
public class Account {
public void operation() {
System.out.println("operation ...");
}
}
現(xiàn)在我們要在這個(gè)操作之前進(jìn)行一定的驗(yàn)證筑煮,例如加入一些檢查權(quán)限的操作checkSecurity()
辛蚊。我們將添加一個(gè)名稱為SecurityChecker
的類。這個(gè)類中的方法可以幫助我們進(jìn)行一些權(quán)限校驗(yàn)真仲。
public class SecurityChecker {
public static boolean checkSecurity() {
System.out.println("SecurityChecker.checkSecurity ...");
if ((System.currentTimeMillis() & 0x1) == 0) {
return false;
} else {
return true;
}
}
}
我們在不修改原來Account
的代碼的前提下袋马,如何增加校驗(yàn)操作呢?我們可以直接修改類的字節(jié)碼進(jìn)行代碼織入操作秸应,從而改變Account代碼的執(zhí)行狀態(tài)虑凛。
我們先看一下單獨(dú)運(yùn)行Account類的執(zhí)行效果把~
可以看到只是打印出來了operation ...
進(jìn)行改寫字節(jié)碼
通過如下代碼我們可以將代碼的字節(jié)碼進(jìn)行修改,并且覆蓋掉原來的編譯好的字節(jié)碼软啼,從而改變類的執(zhí)行狀態(tài)桑谍。
首先我們需要幾個(gè)類對象,第一個(gè)
負(fù)責(zé)Class改寫的適配器類
AddSecurityCheckClassAdapter
繼承自ClassVisitor負(fù)責(zé)Method改寫的適配器類
AddSecurityCheckMethodAdapter
繼承自MethodVisitor負(fù)責(zé)調(diào)用Adapter的
SecurityWeaveGeneratior
AddSecurityCheckClassAdapter
負(fù)責(zé)修改類文件中的字節(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
負(fù)責(zé)修改某個(gè)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
讀取類信息祸挪,進(jìn)行字節(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();
}
}
運(yùn)行結(jié)果
還是運(yùn)行原來的main方法锣披,自動(dòng)加入了檢查安全的操作。
對比一下前后的字節(jié)碼
前:
后:
對比前后贿条,明顯可以看到在字節(jié)碼中增加了我們操作ASM寫入的相關(guān)字節(jié)碼雹仿。
寫在最后,最近生病是在身體乏力整以,寫的文章自己認(rèn)為也只講了簡單的如何使用后面再好好補(bǔ)補(bǔ)胧辽,這個(gè)地方會(huì)和前面的服務(wù)保護(hù)有一定的串聯(lián)!請多多期待公黑!