相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下web
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?spring
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanjson
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的設計模式
曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)api
曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)框架
曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)ide
曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中獲得了什麼(context:annotation-config 解析)
曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(此次來講說它的奇技淫巧)
曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中獲得了什麼(context:component-scan完整解析)
曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)
曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation集成
曹工說Spring Boot源碼(15)-- Spring從xml文件裏到底獲得了什麼(context:load-time-weaver 完整解析)
曹工說Spring Boot源碼(16)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【上】)
曹工說Spring Boot源碼(17)-- Spring從xml文件裏到底獲得了什麼(aop:config完整解析【中】)
曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)
曹工說Spring Boot源碼(19)-- Spring 帶給咱們的工具利器,建立代理不用愁(ProxyFactory)
曹工說Spring Boot源碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操做日誌
曹工說Spring Boot源碼(21)-- 爲了讓你們理解Spring Aop利器ProxyFactory,我已經拼了
曹工說Spring Boot源碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什麼了
曹工說Spring Boot源碼(23)-- ASM又立功了,Spring原來是這麼遞歸獲取註解的元註解的
工程結構圖:
上一篇,咱們講了spring是怎麼獲取class上的註解,以及註解的元註解的。在註解大行其道的今天,理解這些相對底層一點的知識,是絕對有必要的。另外,在上一講中,咱們提到了,spring其實最終也是利用ASM去讀取註解的,其中,還使用了訪問者設計模式。
訪問者設計模式有效地分離了對數據的訪問和和對數據的操做,由於class結構是很固定的,因此,visitor模式就尤爲適合。在訪問到特定數據時,就回調應用註冊的回調方法。ASM基本上就是在visitor這個設計模式的基礎上創建起來的。
今天,咱們的主題有兩個,1是簡單地瞭解下ASM,2是投入實戰,看看要怎麼去利用ASM + java的Intrumentation機制,來在java啓動時,就去修改class,實現簡單的aop功能。
本篇覆蓋第一個主題,下一個主題留帶下一篇(demo已經ok了)。
咱們目的是讀取如下測試類上的註解和全部的方法的名稱。
如下代碼demo見:https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/asm-demo/src/main/java/com/yn/onlyvisit
測試類
package com.yn.onlyvisit; @CustomAnnotationOnClass public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface CustomAnnotationOnClass { }
定義classVisitor,裏面實現visitor的回調方法
package com.yn.onlyvisit; import org.objectweb.asm.*; import org.objectweb.asm.commons.AdviceAdapter; import org.objectweb.asm.commons.AnalyzerAdapter; import org.objectweb.asm.util.ASMifier; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; import java.util.ArrayList; import java.util.List; public class MyClassVistor extends ClassVisitor { private List<String> methodList = new ArrayList<>(); private List<String> annotationOnClass = new ArrayList<>(); public MyClassVistor() { super(Opcodes.ASM6); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { //每訪問到一個方法,加入到field中 System.out.println("visitMethod: " + name); methodList.add(name); return super.visitMethod(access, name, desc, signature, exceptions); } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { // 訪問到類上註解,加入field annotationOnClass.add(descriptor); return super.visitAnnotation(descriptor, visible); } @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { System.out.println("field:" + name); return super.visitField(access, name, descriptor, signature, value); } public List<String> getMethodList() { return methodList; } public List<String> getAnnotationOnClass() { return annotationOnClass; } }
測試代碼
import org.objectweb.asm.ClassReader; import java.io.IOException; import java.util.List; public class TestClassVisit { public static void main(String[] args) throws IOException { // 使用classreader讀取目標類 ClassReader classReader = new ClassReader("com.yn.onlyvisit.Person"); // new一個visitor MyClassVistor classVisitor = new MyClassVistor(); // 傳入classreader classReader.accept(classVisitor,ClassReader.SKIP_DEBUG); // 此時,目標類已經讀取完畢,咱們能夠打印看看效果 List<String> methodList = classVisitor.getMethodList(); System.out.println(methodList); System.out.println(classVisitor.getAnnotationOnClass()); } }
輸出以下:
field:name
field:age
visitMethod:
visitMethod: getName
visitMethod: setName
visitMethod: getAge
visitMethod: setAge
[, getName, setName, getAge, setAge]
[Lcom/yn/onlyvisit/CustomAnnotationOnClass;]
注意,咱們限定的是,生成全新的class,爲何限定這麼死,由於還有一種是,在已經存在的類的基礎上,修改class。
生成全新class的場景也是常見的,好比cglib底層就使用了asm,代理類是動態生成的,對吧?雖然我還沒驗證,但基本就是目前要講的這種場景。
還有就是,fastjson裏也用了asm,至於裏面是不是生成全新class,留帶驗證。
asm的官方文檔,有下面這樣一個例子。
目標類以下,咱們的目標,就是生成這樣一個類的class:
package pkg; public interface Comparable extends Mesurable { int LESS = -1; int EQUAL = 0; int GREATER = 1; int compareTo(Object o); }
咱們只須要以下幾行代碼,便可完成該目標。
package com.yn.classgenerate; import org.objectweb.asm.ClassWriter; import java.io.*; import java.lang.reflect.Field; import static org.objectweb.asm.Opcodes.*; public class TestClassWriter { public static void main(String[] args) throws IOException { ClassWriter cw = new ClassWriter(0); cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1)).visitEnd(); cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null).visitEnd(); cw.visitEnd(); byte[] b = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\Target.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b); fos.close(); } }
執行上述代碼,在指定位置,就會生成一個Target.class,反編譯以後,以下:
上面那個demo,是否夠神奇?爲何這麼神奇呢,核心都在ClassWriter這個類。
這個類,你們能夠理解爲,一個class文件包含了不少東西,對吧?常量池、field集合、method集合、註解、class名、實現的接口集合等等,這個classWriter呢,其中就有不少field,分別來存儲這些東西。
注意的是,上圖中,有些字段,好比firstField,爲何不是集合呢?按理說,一個class裏不少field啊,由於,這裏用了鏈表結構來存儲field。咱們看這個field上的註釋。
/** * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their * {@link FieldWriter#fv} field. This field stores the first element of this list. */ private FieldWriter firstField;
看到了吧,鏈表結構。
因此,ClassWriter,你們必定要好好理解,這個ClassWriter,主要的使用方法就是:提供給你一堆方法,你能夠調用他們,來給裏面的field設置東西,好比,你要設置類名,那你就調用:
cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null);
要加個field,那就這樣:
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd();
如小標題所言,ClassWriter是實現了ClassVisitor的。
public class ClassWriter extends ClassVisitor
前面咱們說的那些,手動去調用的方法,也是來源於ClassVisitor的。
cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null);
該方法,來源於:
org.objectweb.asm.ClassVisitor#visit public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } }
那麼,接下來這段話,你們好好理解下:
前面的demo中,咱們手動調用了ClassWriter的各類visit方法,去生成class;可是,咱們又知道,ClassWriter的那些方法,來自於ClassVisitor,而:當咱們向下面這樣來編碼的時候,ClassVisitor的方法會自動被調用(忘了的,往前翻到:ASM的核心之讀取功能),那麼,咱們能夠實現以下的class複製功能了:
package com.yn.classgenerate; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import static org.objectweb.asm.Opcodes.ASM4; public class CopyClassVersion1 { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); //1 ClassWriter cw = new ClassWriter(0); //2 classReader.accept(cw, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
這裏的核心,就是要把classWriter,當成ClassVisitor,傳遞給ClassReader。
com.yn.classgenerate.CopyClass
這個類,classWriter的各個visit方法,不斷被回調,所以,com.yn.classgenerate.CopyClass
的各種field、method等,不斷被寫入classWriter中,因而,複製就這樣完成了。前面那個複製class的操做中,classreader是直接回調classWriter的,咱們其實也能夠在中間橫插一腳。
public class CopyClass { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); ClassWriter cw = new ClassWriter(0); // cv forwards all events to cw ClassVisitor cv = new ClassVisitor(ASM4, cw) { }; classReader.accept(cv, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
在上面這個例子中,咱們從classReader的下面這句開始看:
classReader.accept(cv, 0);
那麼,能夠知道,classReader是去回調cv,那麼cv是誰?
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
cv的構造函數裏,傳入了cw,cw呢,就是classwriter。
如今的鏈路是這樣的:
classReader --> cv --> cw。
上面這個鏈路中,classReader確定會回調cv,可是cv,怎麼就肯定它會當個二傳手呢?
看看ClassVisitor的構造函數:
public ClassVisitor(final int api, final ClassVisitor classVisitor) { if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { throw new IllegalArgumentException(); } this.api = api; this.cv = classVisitor; }
其把ClassVisitor保存到了一個域:cv中。這個cv如何被使用呢?咱們看看下面的方法:
org.objectweb.asm.ClassVisitor#visit public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } } public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { if (cv != null) { return cv.visitAnnotation(descriptor, visible); } return null; }
這就有意思了,若是cv不爲null,就調用cv去處理,這就是個delegate啊,代理啊。
上面的demo中,cv簡直是盡忠職守,本身在中間,絲絕不作什麼事,就是一個稱職的代理。但不是全部代理都須要這樣,甚至是不鼓勵這樣。
官網中有個demo,以下所示,能夠修改class的版本:
public class ChangeVersionAdapter extends ClassVisitor { public ChangeVersionAdapter(ClassVisitor classVisitor) { super(ASM4, classVisitor); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { cv.visit(V1_8, access, name, signature, superName, interfaces); } }
測試類:
public class TestChangeClassVersion { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new ChangeVersionAdapter(cw) { }; classReader.accept(cv, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
官網還畫了個圖,貼心:
經過這樣,classWriter中,版本號已經被改了,但它還被矇在鼓裏,可憐。
在ClassVisitor中,有幾個特殊的方法:
主要就是這幾個,你看他們的返回值,不太同樣,是xxxVistor,和ClassVisitor有點像?那就對了。
咱們看看fieldVisitor:
其結構和方法,都和ClassVisitor相似,也就是說,咱們能夠返回一個自定義的FieldVistor,而後,ASM框架,就會使用咱們返回的這個FieldVisitor去visit咱們的field的相關屬性,回調fieldVisitor中的相關方法。
那,怎麼刪除呢?返回null。
這麼簡單嗎,是的。
package com.yn.classgenerate; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import static org.objectweb.asm.Opcodes.ASM6; // 該demo來自官網文檔 public class RemoveMethodAdapter extends ClassVisitor { private String mName; private String mDesc; public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) { super(ASM6, cv); this.mName = mName; this.mDesc = mDesc; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals(mName) && desc.equals(mDesc)) { // 這樣就能夠了。 // do not delegate to next visitor -> this removes the method return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } }
asm的基本操做大概如此,這些比較粗淺,下一講咱們會實現一個有用一點的東西,會結合java的instrument機制來說。
你們要跟着個人demo一塊兒來實踐,https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/asm-demo
這樣才能學的勞。