轉(zhuǎn)自【阿里云云棲號(hào)】
一、前言
寫(xiě)這篇文章的時(shí)候我在想可能大部分程序員包括你我浪腐,常常都在忙于業(yè)務(wù)開(kāi)發(fā)或奔波在日常維護(hù)與修復(fù)BUG的路上纵揍,當(dāng)不能從中吸取技術(shù)營(yíng)養(yǎng)與改變現(xiàn)狀后,就像一臺(tái)恒定運(yùn)行的機(jī)器议街,逃不出限定宇宙速度的一個(gè)圈里泽谨。可能你也會(huì)有自己的難處,平時(shí)加班太晚沒(méi)有時(shí)間學(xué)習(xí)吧雹、周末家里瑣事太多沒(méi)有精力投入骨杂,放假計(jì)劃太滿沒(méi)有空閑安排⌒劬恚總之腊脱,學(xué)習(xí)就會(huì)被擱置。而當(dāng)一年年的過(guò)去后龙亲,當(dāng)自己的年齡與能力不成匹配后又會(huì)后悔沒(méi)有給多投入一些時(shí)間學(xué)習(xí)成長(zhǎng)陕凹。
尤其是一線編碼的技術(shù)人,除了我們所能看到的在技術(shù)框架里(SSM)開(kāi)發(fā)的業(yè)務(wù)代碼鳄炉,你是否有遇到過(guò)學(xué)習(xí)瓶頸杜耙,而這種瓶頸又是你自己不知道自己不會(huì)什么,就像下面這些技術(shù)列表里拂盯,你有了解多少佑女;
- javaagent
- asm
- jvmti
- javaassit
- netty
- 算法,搜索引擎
- cglib
- 混沌工程
- 中間件開(kāi)發(fā)
- 高級(jí)測(cè)試谈竿;壓力測(cè)試团驱、鏈路測(cè)試、流量回放空凸、流量染色
- 故障系列嚎花;突襲、重現(xiàn)呀洲、演練
- 分布式的數(shù)據(jù)一致性
- 文件操作紊选;es、hive
- 注冊(cè)中心道逗;zookeeper翰苫、Eureka
- 互聯(lián)網(wǎng)工程開(kāi)發(fā)技術(shù)棧奔缠;spring牌借、mybaits了赌、網(wǎng)關(guān)、rpc(thrift, grpc, dubbo)吏夯、mq此蜈、緩存redis、分庫(kù)分表锦亦、定時(shí)任務(wù)舶替、分布式事物令境、限流杠园、熔斷、降級(jí)
- 數(shù)據(jù)庫(kù)binlog解析
- 架構(gòu)設(shè)計(jì)舔庶;DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)抛蚁、微服務(wù)陈醒、服務(wù)治理
- 容器;k8s, docker
- 分布式存儲(chǔ)瞧甩;ceph
- 服務(wù)istio
- 壓測(cè) jmter
- Jenkins-部署java代碼項(xiàng)目 + ansible
- 全鏈路監(jiān)控钉跷,分布式追蹤
- 語(yǔ)音識(shí)別、語(yǔ)音合成
- lvs nginx haproxy iptables
- hadoop mapreduce hive sqoop hbase flink kylin druid
好肚逸,現(xiàn)在開(kāi)始就搞一下其中的一個(gè)技術(shù)點(diǎn) ASM爷辙,看看它的真面目。那么學(xué)習(xí)之前先看下他有什么用途朦促;
1.類的代理膝晾,如cglib
2.混沌工程
3.反向工程
4.結(jié)合 javaagent 做到非入侵式監(jiān)控,方法耗時(shí)务冕、日志血当、機(jī)器性能等等
5.破解
為了更方便的學(xué)習(xí)ASM禀忆,我將《ASM4使用手冊(cè)》以及一些技術(shù)點(diǎn)整理成在線文檔臊旭,可以隨時(shí)方便查閱(asm.itstack.org);
二离熏、環(huán)境配置
1.jdk 1.8
2.idea 2019.3.1
3.asm-commons 6.2.1
三、工程信息
- itstack-demo-asm-01:字節(jié)碼編程撤奸,HelloWorld
- itstack-demo-asm-02:字節(jié)碼編程,兩數(shù)之和
- itstack-demo-asm-03:字節(jié)碼增強(qiáng)喊括,輸出入?yún)?/li>
- itstack-demo-asm-04:字節(jié)碼增強(qiáng)胧瓜,調(diào)用外部方法
四、HelloWorld還可以這樣寫(xiě)
你所熟悉的HelloWorld是不這樣郑什;
那你有嘗試反解析下他的類查看下匯編指令嗎,javap -c HelloWorld
如果你還感興趣其他指令,可以參考這個(gè)字節(jié)碼指令表:Go!
好申窘! 以上呢弯蚜,是我很熟悉的一段代碼了,那么現(xiàn)在我們把這段代碼用ASM方式寫(xiě)出來(lái)剃法;
import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;
private static byte[] generate() {
ClassWriter classWriter = new ClassWriter(0);// 定義對(duì)象頭碎捺;版本號(hào)、修飾符、全類名收厨、簽名晋柱、父類、實(shí)現(xiàn)的接口classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "org/itstack/demo/asm/AsmHelloWorld", null, "java/lang/Object", null);// 添加方法诵叁;修飾符雁竞、方法名、描述符拧额、簽名碑诉、異常MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);// 執(zhí)行指令;獲取靜態(tài)屬性methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");// 加載常量 load constantmethodVisitor.visitLdcInsn("Hello World");// 調(diào)用方法methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);// 返回methodVisitor.visitInsn(Opcodes.RETURN);// 設(shè)置操作數(shù)棧的深度和局部變量的大小methodVisitor.visitMaxs(2, 1);// 方法結(jié)束methodVisitor.visitEnd();// 類完成classWriter.visitEnd();// 生成字節(jié)數(shù)組return classWriter.toByteArray();
}
以上的代碼侥锦,“小朋友联贩,你是否有很多問(wèn)好???^1024”,其實(shí)以上的代碼都是來(lái)自于 ASM 框架的代碼捎拯,這里面所有的操作與我們使用使用 javap -c XXX 所反解析出的字節(jié)碼是一樣的泪幌,只不過(guò)是反過(guò)來(lái)使用指令來(lái)編寫(xiě)代碼。
1.定義一個(gè)類的生成 ClassWriter
2.設(shè)定版本署照、修飾符祸泪、全類名、簽名建芙、父類没隘、實(shí)現(xiàn)的接口,其實(shí)也就是那句禁荸;public class HelloWorld
3.接下來(lái)開(kāi)始創(chuàng)建方法右蒲,方法同樣需要設(shè)定;修飾符赶熟、方法名瑰妄、描述符等。這里面有幾個(gè)固定標(biāo)識(shí)映砖;
4.執(zhí)行指令;獲取靜態(tài)屬性邑退。主要是獲得 System.out
5.加載常量 load constant竹宋,輸出我們的HelloWorld methodVisitor.visitLdcInsn("Hello World");
6.最后是調(diào)用輸出方法并設(shè)置空返回,同時(shí)在結(jié)尾要設(shè)置操作數(shù)棧的深度和局部變量的大小
這樣輸出一個(gè) HelloWorld 是不還是蠻有意思的地技,雖然你可能覺(jué)得這編碼起來(lái)實(shí)在太難了吧蜈七,也非常難理解。首先如果你看過(guò)我的專欄莫矗,用《Java寫(xiě)一個(gè)Jvm虛擬機(jī)》飒硅,那么你可能會(huì)感受到這里面的知識(shí)點(diǎn)還是不那么陌生的砂缩。另外這里的編寫(xiě),ASM還提供了插件狡相,可以方便的讓你開(kāi)發(fā)字節(jié)碼梯轻。接下來(lái)就介紹一下使用方式食磕。
五尽棕、有插件的幫助字節(jié)碼開(kāi)發(fā)也不是很難
對(duì)于新人來(lái)說(shuō)如果用字節(jié)碼增強(qiáng)開(kāi)發(fā)一些東西確實(shí)挺難,尤其是一些復(fù)雜的代碼塊使用字節(jié)碼指令操作還是很有難度的彬伦。那么滔悉,其實(shí)也是有簡(jiǎn)單辦法就是使用 ASM 插件。這個(gè)插件可以很輕松的讓你看到一段代碼的指令碼以及如何用ASM去開(kāi)發(fā)单绑。
1.安裝插件(ASM Bytecode Outline)
2.測(cè)試使用
是不是看到有插件的幫助下搂橙,心里有所激動(dòng)了歉提,至少寫(xiě)這樣的東西有了抓手。這樣你就可以很方便的去操作一些增強(qiáng)字節(jié)碼的功能了区转。
六苔巨、用字節(jié)碼寫(xiě)出一個(gè)兩數(shù)之和計(jì)算
好!有了上面的插件废离,也有了一些基礎(chǔ)知識(shí)的了解侄泽。那么我們開(kāi)發(fā)一個(gè)計(jì)算兩數(shù)之和的方法,之后運(yùn)行計(jì)算結(jié)果蜻韭。
這是我們的目標(biāo)
使用字節(jié)碼編程方式實(shí)現(xiàn)
- 上面有兩個(gè)括號(hào) {}悼尾,第一個(gè)是用于生成一個(gè)空的構(gòu)造函數(shù)
- 接下來(lái)的指令就比較簡(jiǎn)單了,首先使用 ILOAD 進(jìn)行數(shù)值的兩次壓棧也就是弄到操作數(shù)棧里去操作肖方,接下來(lái)開(kāi)始執(zhí)行 IADD闺魏,將兩數(shù)相加。
- 最后返回結(jié)果 IRETURN ,注意是返回的 I 類型俯画。到此這段方法快就實(shí)現(xiàn)完成了舷胜。反編譯后如下;
這段執(zhí)行操作和我們?cè)谑褂?java 的反射操作一樣活翩,也是比較容易的烹骨。此時(shí)我們是調(diào)用了新的字節(jié)碼類,同時(shí)還將字節(jié)碼輸出方便我們查看生成的 class 類材泄。
七沮焕、在原有方法上字節(jié)碼增強(qiáng)監(jiān)控耗時(shí)
到這我們基本了解到通過(guò)字節(jié)碼編程,可以動(dòng)態(tài)的生成一個(gè)類拉宗。但是在實(shí)際使用的過(guò)程中峦树,我們可能有的時(shí)候是需要修改一個(gè)原有的方法辣辫,在開(kāi)始和結(jié)尾添加一些代碼,來(lái)監(jiān)控這個(gè)方法的耗時(shí)魁巩。這也是非侵入式監(jiān)控的最基本模型急灭。
整體的代碼塊有點(diǎn)大,我們可以分為塊來(lái)看谷遂,如下葬馋;
ClassReader cr = new ClassReader(MyMethod.class.getName()); 讀取原有類,也是字節(jié)碼增強(qiáng)的開(kāi)始ClassVisitor cv = new ProfilingClassAdapter(cw, MyMethod.class.getSimpleName()); 開(kāi)始增強(qiáng)字節(jié)碼onMethodEnter肾扰,onMethodExit畴嘶,在方法進(jìn)入和方法退出時(shí)添加耗時(shí)執(zhí)行的代碼。
測(cè)試結(jié)果:
直接運(yùn)行TestMonitor.java集晚;
八窗悯、字節(jié)碼控制打印方法的入?yún)?/strong>
那么除了可以監(jiān)控方法的執(zhí)行耗時(shí),還可以將方法的入?yún)⑿畔⑦M(jìn)行打印出來(lái)偷拔。這樣就可以在一些異常情況下蒋院,看到日志信息。
其他代碼與上面相同莲绰,這里只列一下修改的地方
從這里可以看到欺旧,在方法進(jìn)入時(shí)候使用指令碼 GETSTATIC,獲取輸出對(duì)象類
- 接下來(lái)使用 ALOAD钉蒲,從局部變量1中裝載引用類型值入棧
- 最后輸出入?yún)⑿畔?/li>
- 測(cè)試結(jié)果:
直接運(yùn)行TestMonitor.java切端;
九、用字節(jié)碼增強(qiáng)調(diào)用外部方法好顷啼!那么執(zhí)行到這踏枣,我們可以想到如果只是將一些信息打印到控制臺(tái)還是沒(méi)有辦法做業(yè)務(wù)的,我們需要在這個(gè)時(shí)候?qū)⒏鞣N屬性信息調(diào)用外部的類钙蒙,進(jìn)行發(fā)送到服務(wù)端茵瀑。比如使用;mq躬厌、日志等马昨。
定義日志信息輸出類
十、總結(jié)
- 高級(jí)編程技術(shù)的內(nèi)容還不止于此扛施,不要只為了一時(shí)的功能實(shí)現(xiàn)鸿捧,而放棄深挖深究的機(jī)會(huì)。也許就是你不斷的增強(qiáng)拓展個(gè)人的知識(shí)技能疙渣,才讓你越來(lái)越與眾不同匙奴。
- ASM 這種字節(jié)碼編程的應(yīng)用是非常廣的,但可能確實(shí)平時(shí)看不到的妄荔,因?yàn)樗际桥c其他框架結(jié)合一起作為支撐服務(wù)使用泼菌。像這樣的技術(shù)還有很多谍肤,比如 javaassit、netty等等哗伯。
- 對(duì)于真的要學(xué)習(xí)一樣技術(shù)時(shí)荒揣,不要只看爽文,但爽文也確實(shí)給了你敲門磚焊刹。當(dāng)你要徹底的掌握某個(gè)知識(shí)的時(shí)候系任,最重要的是成體系的學(xué)習(xí)!壓榨自己的時(shí)間伴澄,做有意義的事赋除,是3-7年開(kāi)發(fā)人員最正確的事