做者:小傅哥
博客:https://bugstack.cn - 彙總原創系列專題文章
html
沉澱、分享、成長,讓本身和他人都能有所收穫!
到本章爲止已經寫了四篇關於字節碼編程的內容,涉及了大部分的API方法。總體來講對 Javassist
已經有一個基本的使用認知。那麼在 Javassist
中不只提供了高級 API
用於建立和修改類、方法,還提供了低級 API
控制字節碼指令的方式進行操做類、方法。java
有了這樣的 javassist API
在一些特殊場景下就可使用字節碼指令控制方法。編程
接下來咱們經過字節碼指令模擬一段含有自定義註解的方法修改和生成。在修改的過程當中會將原有方法計算息費
的返回值替換成 0
,最後咱們使用這樣的技術去生成一段計算息費的方法。經過這樣的練習學會字節碼操做。api
itstack-demo-bytecode-1-05
,能夠關注公衆號:bugstack蟲洞棧
,回覆源碼下載獲取。你會得到一個下載連接列表,打開后里面的第17個「由於我有好多開源代碼」
,記得給個Star
!測試方法學習
@RpcGatewayClazz(clazzDesc = "用戶信息查詢服務", alias = "api", timeOut = 500) public class ApiTest { @RpcGatewayMethod(methodDesc = "查詢息費", methodName = "interestFee") public double queryInterestFee(String uId){ return BigDecimal.TEN.doubleValue(); // 模擬息費計算返回 } }
ClassPool pool = ClassPool.getDefault(); // 類、註解 CtClass ctClass = pool.get(ApiTest.class.getName()); // 經過集合獲取自定義註解 Object[] clazzAnnotations = ctClass.getAnnotations(); RpcGatewayClazz rpcGatewayClazz = (RpcGatewayClazz) clazzAnnotations[0]; System.out.println("RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz.clazzDesc()); System.out.println("RpcGatewayClazz.alias:" + rpcGatewayClazz.alias()); System.out.println("RpcGatewayClazz.timeOut:" + rpcGatewayClazz.timeOut());
ctClass.getAnnotations()
,能夠獲取全部的註解,進行操做輸出結果:測試
RpcGatewayClazz.clazzDesc:用戶信息查詢服務 RpcGatewayClazz.alias:api RpcGatewayClazz.timeOut:500
CtMethod ctMethod = ctClass.getDeclaredMethod("queryInterestFee"); RpcGatewayMethod rpcGatewayMethod = (RpcGatewayMethod) ctMethod.getAnnotation(RpcGatewayMethod.class); System.out.println("RpcGatewayMethod.methodName:" + rpcGatewayMethod.methodName()); System.out.println("RpcGatewayMethod.methodDesc:" + rpcGatewayMethod.methodDesc());
class
獲取的,這樣按照名稱能夠只獲取最須要的註解名稱。輸出結果:spa
RpcGatewayMethod.methodName:interestFee RpcGatewayMethod.methodDesc:查詢息費
MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); CodeIterator iterator = codeAttribute.iterator(); while (iterator.hasNext()) { int idx = iterator.next(); int code = iterator.byteAt(idx); System.out.println("指令碼:" + idx + " > " + Mnemonic.OPCODE[code]); }
JVM
執行的操做流程。輸出結果:rest
指令碼:0 > getstatic 指令碼:3 > invokevirtual 指令碼:6 > dreturn
ConstPool cp = methodInfo.getConstPool(); Bytecode bytecode = new Bytecode(cp); bytecode.addDconst(0); bytecode.addReturn(CtClass.doubleType); methodInfo.setCodeAttribute(bytecode.toCodeAttribute());
addDconst
,將 double 型0推送至棧頂addReturn
,返回 double 類型的結果此時的方法的返回值已經被修改,下面的是新的 class
類;code
@RpcGatewayClazz( clazzDesc = "用戶信息查詢服務", alias = "api", timeOut = 500L ) public class ApiTest { public ApiTest() { } @RpcGatewayMethod( methodDesc = "查詢息費", methodName = "interestFee" ) public double queryInterestFee(String var1) { return 0.0D; } }
0.0D
。若是你的程序被這樣操做,那麼仍是很危險的。因此有時候會進行一些混淆編譯,下降破解風險。ClassPool pool = ClassPool.getDefault(); // 建立類信息 CtClass ctClass = pool.makeClass("org.itstack.demo.javassist.HelloWorld"); // 添加方法 CtMethod mainMethod = new CtMethod(CtClass.doubleType, "queryInterestFee", new CtClass[]{pool.get(String.class.getName())}, ctClass); mainMethod.setModifiers(Modifier.PUBLIC); MethodInfo methodInfo = mainMethod.getMethodInfo(); ConstPool cp = methodInfo.getConstPool();
// 類添加註解 AnnotationsAttribute clazzAnnotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); Annotation clazzAnnotation = new Annotation("org/itstack/demo/javassist/RpcGatewayClazz", cp); clazzAnnotation.addMemberValue("clazzDesc", new StringMemberValue("用戶信息查詢服務", cp)); clazzAnnotation.addMemberValue("alias", new StringMemberValue("api", cp)); clazzAnnotation.addMemberValue("timeOut", new LongMemberValue(500L, cp)); clazzAnnotationsAttribute.setAnnotation(clazzAnnotation); ctClass.getClassFile().addAttribute(clazzAnnotationsAttribute);
AnnotationsAttribute
,建立自定義註解標籤Annotation
,建立實際須要的自定義註解,這裏須要傳遞自定義註解的類路徑addMemberValue
,用於添加自定義註解中的值。須要注意不一樣類型的值 XxxMemberValue
前綴不同;StringMemberValue、LongMemberValue setAnnotation
,最終設置自定義註解。若是不設置,是不能生效的。// 方法添加註解 AnnotationsAttribute methodAnnotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); Annotation methodAnnotation = new Annotation("org/itstack/demo/javassist/RpcGatewayMethod", cp); methodAnnotation.addMemberValue("methodName", new StringMemberValue("查詢息費", cp)); methodAnnotation.addMemberValue("methodDesc", new StringMemberValue("interestFee", cp)); methodAnnotationsAttribute.setAnnotation(methodAnnotation); methodInfo.addAttribute(methodAnnotationsAttribute);
addAttribute
上。// 指令控制 Bytecode bytecode = new Bytecode(cp); bytecode.addGetstatic("java/math/BigDecimal", "TEN", "Ljava/math/BigDecimal;"); bytecode.addInvokevirtual("java/math/BigDecimal", "doubleValue", "()D"); bytecode.addReturn(CtClass.doubleType); methodInfo.setCodeAttribute(bytecode.toCodeAttribute());
Javassist
中的指令碼經過,Bytecode 的方式進行添加。基本全部的指令你均可以在這裏使用,它有很是強大的 API
。addGetstatic
,獲取指定類的靜態域, 並將其壓入棧頂addInvokevirtual
,調用實例方法addReturn
,從當前方法返回double// 添加方法 ctClass.addMethod(mainMethod); // 輸出類信息到文件夾下 ctClass.writeFile();
Javassist
字節碼開發經常使用的內容。添加方法和輸出字節碼編程後的類信息。@RpcGatewayClazz( clazzDesc = "用戶信息查詢服務", alias = "api", timeOut = 500L ) public class HelloWorld { @RpcGatewayMethod( methodName = "查詢息費", methodDesc = "interestFee" ) public double queryInterestFee(String var1) { return BigDecimal.TEN.doubleValue(); } public HelloWorld() { } }
TryCatch
中的開始位置。javassist
字節碼編程自己經常使用的方法基本已經覆蓋完成,後續會集合 JavaAgent
作一些案例彙總,將知識點與實際場景進行串聯。