本文首發於本人的部落格, 歡迎關注
ASM 是一個 Java 位元組碼操作和分析框架, 可直接用二進位制的方式(無需編譯/反編譯)修改已存在的類或動態生成新的類.
問題
要開發像 ASM 這樣的框架, 主要面臨以下幾個問題:
動態新增操作方法
既然是一個位元組碼的操作和分析框架, 那麼就要提供對位元組碼進行各種各樣的操作, 且允許使用者自定義操作型別. 如在 ASM 中, 除了內建對位元組碼進行輸出和轉換等預設實現之外, 還要允許使用者對位元組碼進行加/解密等各種自定義的操作, 對於公開的商業程式碼這可能是至關重要的.
何處安放操作方法
雖然構成一個類的位元組碼的各個元素有弱的層次關係: 代表類的位元組碼可能包含 N 個代表欄位/方法/註解的位元組碼, 代表欄位/方法的位元組碼又可能包含 N 個代表註解的位元組碼. 對這些元素所進行的操作, 應該如何安放. 一種直接的方法是在代表每種位元組碼元素的類中定義相應的方法, 如 print()
用於輸出, transform()
用於轉換, 但在位元組碼元素的類層次結構中加入這些毫無聯絡的操作方法, 會使程式碼變得難以理解和維護.
訪問者模式
作用在多種類物件上的操作, 訪問者可以在不修改其操作物件所屬類的情況下, 增加對其操作方式.
ASM 原始碼中主要使用了訪問者模式來解決以上問題:
動態新增操作方法
以對類的欄位的位元組碼進行操作為例, ASM 中定義了 org.objectweb.asm.FieldVisitor
用來表示對欄位的操作:
package org.objectweb.asm;
public abstract class FieldVisitor {
// ...
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
// ...
return null;
}
public AnnotationVisitor visitTypeAnnotation(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
// ...
return null;
}
public void visitAttribute(final Attribute attribute) {
// ...
}
public void visitEnd() {
// ...
}
}
複製程式碼
這樣一來, 當使用者需要新增對欄位進行操作的時候, 只需要繼承 FieldVisitor
, 實現相應的方法即可, 以 ASM 中列印欄位位元組碼的類 FieldWriter
為例:
package org.objectweb.asm;
final class FieldWriter extends FieldVisitor {
// ...
@Override
public void visitAttribute(final Attribute attribute) {
// Store the attributes in the <i>reverse</i> order of their visit by this method.
attribute.nextAttribute = firstAttribute;
firstAttribute = attribute;
}
void putFieldInfo(final ByteVector output) {
// ...
}
// ...
}
複製程式碼
FieldWriter
每訪問到一個欄位, 就在內部儲存該欄位(按訪問順序逆序), 最終呼叫 putFieldInfo
將所有欄位資訊列印到 ByteVector
中.
其中 org.objectweb.asm.FieldVisitor#visitAttribute
方法是對類屬性的操作, Attribute
本身的設計並沒有加入如 print
, transform
等方法, 而是將對 Attribute
的操作方法被抽離至 FieldVisitor
中, 否則就無法滿足"動態新增操作方法"的要求了.
通過應用訪問者模式, ASM 將對位元組碼操作的方法從封裝了位元組碼的類中抽離, 避免無關的方法汙染類的設計.
何處安放操作方法
從上面的程式碼可以看出, ASM 並沒有對各種類元素的位元組碼定義相應的類, 並且將對這些元素的操作直接放置在對應的訪問者 (Visitor) 中, 這正是訪問者模式典型應用: 統一管理對類層級中各種類的操作方法.
何時使用訪問者模式
物件層次結構對應類層次結構差異很大
一個類包含 N 個註解, 欄位和方法, 用於表示註解, 欄位和方法的型別差異比較大, 但是對這些型別的操作需要相互結合, 才能對一個完整的類位元組碼進行操作.
類中需要新增與類本身毫無關係的方法
代表位元組碼的類和對其進行的操作毫無關係, 換句話說, 一個類本身的位元組碼與別人如何操作它無關, 這些方法應該被抽離到訪問物件中, 而不是強行定義在代表位元組碼的類中.