設計模式 - ASM 中的訪問者模式

weixin_33686714發表於2018-10-28

本文首發於本人的部落格, 歡迎關注

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 個註解, 欄位和方法, 用於表示註解, 欄位和方法的型別差異比較大, 但是對這些型別的操作需要相互結合, 才能對一個完整的類位元組碼進行操作.

類中需要新增與類本身毫無關係的方法

代表位元組碼的類和對其進行的操作毫無關係, 換句話說, 一個類本身的位元組碼與別人如何操作它無關, 這些方法應該被抽離到訪問物件中, 而不是強行定義在代表位元組碼的類中.

相關文章