之前三篇文章介紹了 .class 文件的結(jié)構(gòu)菱涤、JVM 對 .class 文件加載以及在 JVM 中是怎么執(zhí)行程序的,接下來的文章會介紹 ASM 的使用劫哼,ASM 是運(yùn)用訪問者模式設(shè)計的皂吮,本篇文章就介紹一下訪問者模式的概念以及其在 ASM 中的應(yīng)用。
一. 概述 & 定義
- 定義:封裝某些作用于某種數(shù)據(jù)結(jié)構(gòu)中各元素的操作厂镇,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些數(shù)據(jù)元素的新的操作
- 意圖:主要將數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)操作分離
- 主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作的解耦
- 適用場景:
- 假如一個對象中存在著一些與本對象不相干(或者關(guān)系較弱)的操作,可以使用訪問者模式把這些操作封裝到訪問者中去左刽,這樣便避免了這些不相干的操作污染這個對象。
- 假如一組對象中酌媒,存在著相似的操作欠痴,可以將這些相似的操作封裝到訪問者中去,這樣便避免了出現(xiàn)大量重復(fù)的代碼
- 訪問者模式適用于對功能已經(jīng)確定的項(xiàng)目進(jìn)行重構(gòu)的時候適用秒咨,因?yàn)楣δ芤呀?jīng)確定喇辽,元素類的數(shù)據(jù)結(jié)構(gòu)也基本不會變了;如果是一個新的正在開發(fā)中的項(xiàng)目雨席,在訪問者模式中菩咨,每一個元素類都有它對應(yīng)的處理方法,每增加一個元素類都需要修改訪問者類,修改起來相當(dāng)麻煩抽米。
二. 示例
如果老師教學(xué)反饋得分大于等于85分特占、學(xué)生成績大于等于90分,則可以入選成績優(yōu)秀獎云茸;如果老師論文數(shù)目大于8是目、學(xué)生論文數(shù)目大于2,則可以入選科研優(yōu)秀獎标捺。
在這個例子中懊纳,老師和學(xué)生就是Element,他們的數(shù)據(jù)結(jié)構(gòu)穩(wěn)定不變亡容。從上面的描述中嗤疯,我們發(fā)現(xiàn),對數(shù)據(jù)結(jié)構(gòu)的操作是多變的闺兢,一會兒評選成績身弊,一會兒評選科研,這樣就適合使用訪問者模式來分離數(shù)據(jù)結(jié)構(gòu)和操作列敲。
2.1 創(chuàng)建抽象元素
public interface Element {
void accept(Visitor visitor);
}
2.2 創(chuàng)建具體元素
創(chuàng)建兩個具體元素 Student 和 Teacher阱佛,分別實(shí)現(xiàn) Element 接口
public class Student implements Element {
private String name;
private int grade;
private int paperCount;
public Student(String name, int grade, int paperCount) {
this.name = name;
this.grade = grade;
this.paperCount = paperCount;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
......
}
public class Teacher implements Element {
private String name;
private int score;
private int paperCount;
public Teacher(String name, int score, int paperCount) {
this.name = name;
this.score = score;
this.paperCount = paperCount;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
......
}
2.3 創(chuàng)建抽象訪問者
public interface Visitor {
void visit(Student student);
void visit(Teacher teacher);
}
2.4 創(chuàng)建具體訪問者
創(chuàng)建一個根據(jù)分?jǐn)?shù)評比的具體訪問者 GradeSelection,實(shí)現(xiàn) Visitor 接口
public class GradeSelection implements Visitor {
@Override
public void visit(Student student) {
if (student != null && student.getGrade() >= 90) {
System.out.println(student.getName() + "的分?jǐn)?shù)是" + student.getGrade() + "戴而,榮獲了成績優(yōu)秀獎凑术。");
}
}
@Override
public void visit(Teacher teacher) {
if (teacher != null && teacher.getScore() >= 85) {
System.out.println(teacher.getName() + "的分?jǐn)?shù)是" + teacher.getScore() + ",榮獲了成績優(yōu)秀獎所意。");
}
}
}
2.5 訪問者代碼調(diào)用
public class VisitorClient {
public static void main(String[] args) {
Element element = new Student("lijiankun24", 90, 3);
Visitor visitor = new GradeSelection();
element.accept(visitor);
}
}
上述代碼即是一個簡單的訪問者模式的示例代碼淮逊,輸出如下所示:
上述代碼可以分為三步:
1. 創(chuàng)建一個元素類的對象
2. 創(chuàng)建一個訪問類的對象
3. 元素對象通過 Element#accept(Visitor visitor) 方法傳入訪問者對象
三. ASM 中的訪問者模式
ASM 庫是 Visitor 模式的典型應(yīng)用。
3.1 ASM 中幾個重要的類
在 ASM 庫中存在以下幾個重要的類:
- ClassReader:它將字節(jié)數(shù)組或者 class 文件讀入到內(nèi)存當(dāng)中扶踊,并以樹的數(shù)據(jù)結(jié)構(gòu)表示泄鹏,樹中的一個節(jié)點(diǎn)代表著 class 文件中的某個區(qū)域⊙砗模可以將 ClassReader 看作是 Visitor 模式中的訪問者的實(shí)現(xiàn)類
-
ClassVisitor(抽象類):ClassReader 對象創(chuàng)建之后备籽,調(diào)用 ClassReader#accept() 方法,傳入一個 ClassVisitor 對象分井。在 ClassReader 中遍歷樹結(jié)構(gòu)的不同節(jié)點(diǎn)時會調(diào)用 ClassVisitor 對象中不同的 visit() 方法车猬,從而實(shí)現(xiàn)對字節(jié)碼的修改。在 ClassVisitor 中的一些訪問會產(chǎn)生子過程尺锚,比如 visitMethod 會產(chǎn)生 MethodVisitor 的調(diào)用珠闰,visitField 會產(chǎn)生對 FieldVisitor 的調(diào)用,用戶也可以對這些 Visitor 進(jìn)行自己的實(shí)現(xiàn)瘫辩,從而達(dá)到對這些子節(jié)點(diǎn)的字節(jié)碼的訪問和修改伏嗜。
在 ASM 的訪問者模式中坛悉,用戶還可以提供多種不同操作的 ClassVisitor 的實(shí)現(xiàn),并以責(zé)任鏈的模式提供給 ClassReader 來使用承绸,而 ClassReader 只需要 accept 責(zé)任鏈中的頭節(jié)點(diǎn)處的 ClassVisitor裸影。 - ClassWriter:ClassWriter 是 ClassVisitor 的實(shí)現(xiàn)類,它是生成字節(jié)碼的工具類八酒,它一般是責(zé)任鏈中的最后一個節(jié)點(diǎn)空民,其之前的每一個 ClassVisitor 都是致力于對原始字節(jié)碼做修改,而 ClassWriter 的操作則是老實(shí)得把每一個節(jié)點(diǎn)修改后的字節(jié)碼輸出為字節(jié)數(shù)組羞迷。
3.2 ASM 的工作流程
ASM 大致的工作流程是:
- ClassReader 讀取字節(jié)碼到內(nèi)存中界轩,生成用于表示該字節(jié)碼的內(nèi)部表示的樹,ClassReader 對應(yīng)于訪問者模式中的元素
- 組裝 ClassVisitor 責(zé)任鏈衔瓮,這一系列 ClassVisitor 完成了對字節(jié)碼一系列不同的字節(jié)碼修改工作浊猾,對應(yīng)于訪問者模式中的訪問者 Visitor
- 然后調(diào)用 ClassReader#accept() 方法,傳入 ClassVisitor 對象热鞍,此 ClassVisitor 是責(zé)任鏈的頭結(jié)點(diǎn)葫慎,經(jīng)過責(zé)任鏈中每一個 ClassVisitor 的對已加載進(jìn)內(nèi)存的字節(jié)碼的樹結(jié)構(gòu)上的每個節(jié)點(diǎn)的訪問和修改
- 最后,在責(zé)任鏈的末端薇宠,調(diào)用 ClassWriter 這個 visitor 進(jìn)行修改后的字節(jié)碼的輸出工作