本文首發於本人的博客, 歡迎關注java
ASM 是一個 Java 字節碼操做和分析框架, 可直接用二進制的方式(無需編譯/反編譯)修改已存在的類或動態生成新的類.web
要開發像 ASM 這樣的框架, 主要面臨如下幾個問題:框架
既然是一個字節碼的操做和分析框架, 那麼就要提供對字節碼進行各類各樣的操做, 且容許用戶自定義操做類型. 如在 ASM 中, 除了內置對字節碼進行輸出和轉換等默認實現以外, 還要容許用戶對字節碼進行加/解密等各類自定義的操做, 對於公開的商業代碼這多是相當重要的.ide
雖然構成一個類的字節碼的各個元素有弱的層次關係: 表明類的字節碼可能包含 N 個表明字段/方法/註解的字節碼, 表明字段/方法的字節碼又可能包含 N 個表明註解的字節碼. 對這些元素所進行的操做, 應該如何安放. 一種直接的方法是在表明每種字節碼元素的類中定義相應的方法, 如 print()
用於輸出, transform()
用於轉換, 但在字節碼元素的類層次結構中加入這些毫無聯繫的操做方法, 會使代碼變得難以理解和維護.this
做用在多種類對象上的操做, 訪問者能夠在不修改其操做對象所屬類的狀況下, 增長對其操做方式.spa
ASM 源代碼中主要使用了訪問者模式來解決以上問題:設計
以對類的字段的字節碼進行操做爲例, ASM 中定義了 org.objectweb.asm.FieldVisitor
用來表示對字段的操做:code
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
爲例:orm
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 個註解, 字段和方法, 用於表示註解, 字段和方法的類型差別比較大, 可是對這些類型的操做須要相互結合, 才能對一個完整的類字節碼進行操做.
表明字節碼的類和對其進行的操做毫無關係, 換句話說, 一個類自己的字節碼與別人如何操做它無關, 這些方法應該被抽離到訪問對象中, 而不是強行定義在表明字節碼的類中.