曹工說Spring Boot源碼(23)-- ASM又立功了,Spring原來是這麼遞歸獲取註解的元註解的

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git

曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下github

曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?正則表達式

曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取bean算法

曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的spring

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)apache

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)json

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)bootstrap

曹工說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源碼系列,離上一篇,快有2周時間了,這兩週,原本是打算繼續寫這個系列的;結果中間腦熱,就去實踐了一把動態代理,實現了一個mini-dubbo這樣一個rpc框架,擴展性仍是至關好的,今天看了下spring mvc的設計,思路差很少,都是框架提供默認的組件(好比handlermapping),而後程序裏自定義了的話,就覆蓋默認組件。

而後,由於mini-dubbo實現過程當中的一些其餘問題,以及工做上的須要,寫了netty實現的http 鏈接池,這個系列還沒講完,留着後邊再補,否則咱們的源碼系列就耽擱過久了,今天咱們仍是接着回來弄源碼系列。

今天這講,主題是:給你一個class,怎麼讀取其上的註解,須要考慮註解的元註解(能夠理解註解上的註解)

讀取class上的註解

常規作法

咱們的Class類,就有不少獲取annotation的方法,以下:

可是,這個有一個問題是,沒法遞歸獲取。

好比,你們使用spring的,都知道,controller這個註解上,是註解了component的。

若是你在一個標註了@controller註解的類的class上,去獲取註解,是拿不到Component這一層的。

爲啥要拿Component這一層呢?你能夠想一下,最開始寫spring的做者,是隻定義了Component這個註解的,業務邏輯也只能處理Component這個註解;後來呢,又多定義了@controller,@service這幾個,可是,難道要把全部業務邏輯的地方都去改一改?很明顯,你不會,大佬更不會,直接解析@controller註解,看看它的元註解有沒有@component就好了,有的話,直接複用以前的邏輯。

那麼,如何進行遞歸解析呢?

遞歸解析類上註解--方法1

咱們要獲取的class,長這樣:

package org.springframework.test;

@CustomController
public class TestController {
}


@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Controller
public @interface CustomController {
}

這個方法,是從spring 源碼裏摘抄的,在內部實現中,基本就這個樣子:

我這個版本是4.0,在:org.springframework.bootstrap.sample.Test#recusivelyCollectMetaAnnotations

public static void getAnnotationByClass(String className) throws ClassNotFoundException {
    Class<?> clazz = Class.forName(className);
    Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
    for (Annotation metaAnnotation : clazz.getAnnotations()) {
        recusivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation);
    }
}


private static void recusivelyCollectMetaAnnotations(Set<String> visited, Annotation annotation) {
    if (visited.add(annotation.annotationType().getName())) {
        for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) {
            //遞歸
            recusivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
        }
    }
}

我試了下,這個方法在新版本里,方法名變了,核心仍是差很少,spring 5.1.9能夠看這個類:

org.springframework.core.type.classreading.AnnotationAttributesReadingVisitor#recursivelyCollectMetaAnnotations

輸出以下:

java.lang.annotation.Documented
java.lang.annotation.Retention
java.lang.annotation.Target
org.springframework.stereotype.Controller
org.springframework.stereotype.Component

遞歸解析類上註解--方法2

這個是我本身實現的,要複雜一些,固然,是有理由的:

import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;

public static void main(String[] args) throws IOException, ClassNotFoundException {
        SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
        LinkedHashSet<String> result = new LinkedHashSet<>();

        getAnnotationSet(result, "org.springframework.test.TestController", simpleMetadataReaderFactory);
    }

    public static void getAnnotationSet(LinkedHashSet<String> result, String className, SimpleMetadataReaderFactory simpleMetadataReaderFactory) throws IOException {
        boolean contains = result.add(className);
        if (!contains) {
            return;
        }

        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader(className);
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();

        if (!CollectionUtils.isEmpty(annotationTypes)) {
            for (String annotationType : annotationTypes) {
                // 遞歸
                getAnnotationSet(result, annotationType, simpleMetadataReaderFactory);
            }
        }

    }

估計有的同窗要罵人了,取個註解,搞一堆莫名其妙的工具類幹嗎?由於,spring就是這麼玩的啊,方法1,是spring的實現,不假。可是,那個已是最內層了,人家外邊還封裝了一堆,封裝出來,基本就是方法2看到的那幾個類。

spring抽象出的註解獲取的核心接口

你們看看,就是下面這個,類圖以下:

其大體的功能,看下圖就知道了:

這個接口,一共2個實現,簡單來講,一個是經過傳統的反射方式來獲取這些信息,一個是經過asm的方式。

二者的優劣呢,你們能夠看看小馬哥的書,裏面提到的是,asm方式的性能,遠高於反射實現,由於無需加載class,直接解析class文件的字節碼。

咱們這裏也是主要講asm方式的實現,你們看到了上面這個asm實現的類,叫:AnnotationMetadataReadingVisitor,它的類結構,以下:

從上圖能夠大體知道,其繼承了ClassMetadataReadingVisitor,這個類,負責去實現ClassMetaData接口;它本身呢,就本身負責實現AnnotationMetadata接口。

咱們呢,不是很關心類的相關信息,只聚焦註解的獲取。

AnnotationMetadataReadingVisitor如何實現AnnotationMetadata接口

AnnotationMetadata接口,咱們最關注的就是下面這2個方法:

// 獲取直接註解在當前class上的註解
   Set<String> getAnnotationTypes();

   // 獲取某個直接註解的元註解,好比你這裏傳個controller進去,就能給你拿到controller這個註解的元註解
   Set<String> getMetaAnnotationTypes(String annotationType);

你們能夠看到,它呢,給了2個方法,而不是一個方法來獲取全部,可能有其餘考慮吧,咱們接着看。

getAnnotationTypes的實現

這個方法,獲取直接註解在target class上的註解。

那看看這個方法在AnnotationMetadataReadingVisitor的實現吧:

public Set<String> getAnnotationTypes() {
    return this.annotationSet;
}

尷尬,看看啥時候給它賦值的:

@Override
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
    String className = Type.getType(desc).getClassName();
    this.annotationSet.add(className);
    return new AnnotationAttributesReadingVisitor(className, this.attributeMap,
                                                  this.metaAnnotationMap, this.classLoader, this.logger);
}

方法名字,見名猜意思,:visit註解,可能還使用了visitor設計模式,可是這個方法又是何時被調用的呢

asm簡介

簡單介紹下asm框架,官網:

https://asm.ow2.io/

https://asm.ow2.io/asm4-guide.pdf

官網說明以下:

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused on performance. Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).

ASM is used in many projects, including:

簡單來講,就是:

asm是一個字節碼操做和分析的框架,可以用來修改已存在的class,或者動態生成class,直接以二進制的形式。ASM提供一些通用的字節碼轉換和分析算法,經過這些算法,能夠構建複雜的字節碼轉換和代碼分析工具。ASM提供和其餘字節碼框架相似的功能,可是其專一於性能。由於它被設計和實現爲,儘量的小,儘量的快。

ASM被用在不少項目,包括:

OpenJDK,生成lambda調用;

Groovy和Kotlin的編譯器

Cobertura和Jacoco,經過探針,檢測代碼覆蓋率

CGLIB,動態生成代理類,也用在Mockito和EasyMock中

Gradle,運行時動態生成類

這裏補充一句,ASM爲啥說它專一於性能,由於,要動態生成類、動態進行字節碼轉換,若是性能太差的話,還有人用嗎? 爲啥要足夠小,足夠小由於它也但願本身用在一些內存受限的環境中。

查看了asm的官方文檔,發現一個有趣的知識,asm這個名字,來源於c語言裏面的__asm__關鍵字,這個關鍵字能夠在c語言裏用匯編來實現某些功能。

另外,其官方文檔裏提到,解析class文件的過程,有兩種模型,一種是基於事件的,一種是基於對象的,能夠類比xml解析中的sax和dom模型,sax就是基於事件的,一樣也是和asm同樣,使用visitor模式。

visitor模式呢,個人簡單理解,就是主程序定義好了一切流程,好比我會按照順序來訪問一個class,先是class name,就去調用visitor的對應方法,此時,visitor能夠作些處理;我訪問到field時,也會調用visitor的對應方法...以此類推。

asm怎麼讀取class

針對每一個class,asm是把它看成一個Resource,其大概的解析步驟以下:

import org.springframework.asm.ClassReader;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;

SimpleMetadataReader(Resource resource, ClassLoader classLoader, MetadataReaderLog logger) throws IOException {
        // 1.
        InputStream is = new BufferedInputStream(resource.getInputStream());
        ClassReader classReader = new ClassReader(is);
        // 2.
        AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader, logger);
        // 3.
        classReader.accept(visitor, ClassReader.SKIP_DEBUG);

        this.annotationMetadata = visitor;
        // (since AnnotationMetadataReader extends ClassMetadataReadingVisitor)
        this.classMetadata = visitor;
        this.resource = resource;
    }

各講解點:

  1. 讀取class resource爲輸入流,做爲構造器參數,new一個asm的ClassReader出來;

  2. 新建一個AnnotationMetadataReadingVisitor類的實例,這個繼承了ClassVisitor抽象類,這個visitor裏面定義了一堆的回調方法:

    public abstract class ClassVisitor {
     public ClassVisitor(int api);
    
     public ClassVisitor(int api, ClassVisitor cv);
    
     public void visit(int version, int access, String name,
     String signature, String superName, String[] interfaces);
    
     public void visitSource(String source, String debug);
    
     public void visitOuterClass(String owner, String name, String desc);
        // 解析到class文件中的註解時回調本方法
     AnnotationVisitor visitAnnotation(String desc, boolean visible);
    
     public void visitAttribute(Attribute attr);
    
     public void visitInnerClass(String name, String outerName,String innerName,int access);
        // 解析到field時回調
     public FieldVisitor visitField(int access, String name, String desc,String signature, Object value);
    
        // 解析到method時回調
     public MethodVisitor visitMethod(int access, String name, String desc,
     String signature, String[] exceptions);
    
     void visitEnd();
    }

    這其中,方法的訪問順序以下:

    visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
    ( visitInnerClass | visitField | visitMethod )*
    visitEnd
    
    表明:
    visit必須最早訪問;
    接着是最多一次的visitSource,再接着是最多一次的visitOuterClass;
    接着是任意屢次的visitAnnotation | visitAttribute ,這兩個,順序隨意;
    再接着是,任意屢次的visitInnerClass | visitField | visitMethod ,順序隨意
    最後,visitEnd

    這個順序的? * () 等符號,其實相似於正則表達式的語法,對吧,仍是比較好理解的。

    而後呢,我對visitor的理解,如今感受相似於spring裏面的event listener機制,好比,spring的生命週期中,發佈的事件,有以下幾個,其實也是有順序的:

    這裏還有官網提供的一個例子:

    public class ClassPrinter extends ClassVisitor {
        public ClassPrinter() {
            super(ASM4);
        }
        public void visit(int version, int access, String name,
         String signature, String superName, String[] interfaces) {
         System.out.println(name + " extends " + superName + " {");
        }
        public void visitSource(String source, String debug) {
        }
        public void visitOuterClass(String owner, String name, String desc) {
        }
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
         return null;
        }
        public void visitAttribute(Attribute attr) {
        }
        public void visitInnerClass(String name, String outerName,String innerName, int access)  {
        }
        public FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {
         System.out.println(" " + desc + " " + name);
         return null;
        }
        public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {
         System.out.println(" " + name + desc);
         return null;
        }
        public void visitEnd() {
         System.out.println("}");
        }
    }
  3. 將第二步的visitor策略,傳遞給classReader,classReader開始進行解析

getAnnotationTypes的回調處理

咱們接着回到getAnnotationTypes的實現,你們看了上面2個圖,應該大體知道visitAnnotation的實現了:

@Override
    public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
        String className = Type.getType(desc).getClassName();
        this.annotationSet.add(className);
        return new AnnotationAttributesReadingVisitor(className, this.attributeMap,
                this.metaAnnotationMap, this.classLoader, this.logger);
    }

這裏每訪問到一個註解,就會加入到field: annotationSet中。

註解上的元註解,如何讀取

你們再看看上面的代碼,咱們返回了一個AnnotationAttributesReadingVisitor,這個visitor會在:asm訪問註解的具體屬性時,其中的以下方法被回調。

@Override
    public void doVisitEnd(Class<?> annotationClass) {
        super.doVisitEnd(annotationClass);
        List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
        if(attributes == null) {
            this.attributesMap.add(this.annotationType, this.attributes);
        } else {
            attributes.add(0, this.attributes);
        }
        Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
        // 1 
        for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
            // 2
            recusivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation);
        }
        if (this.metaAnnotationMap != null) {
            this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
        }
    }
    // 3 
    private void recusivelyCollectMetaAnnotations(Set<String> visited, Annotation annotation) {
        if(visited.add(annotation.annotationType().getName())) {
            this.attributesMap.add(annotation.annotationType().getName(),
                    AnnotationUtils.getAnnotationAttributes(annotation, true, true));
            // 獲取本註解上的元註解
            for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations())             {    // 4 遞歸調用本身
                recusivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
            }
        }
    }
  1. 獲取註解的元註解,好比,獲取controller註解上的註解;這裏就能取到Target、Retention、Documented、Component

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Controller
  2. 循環處理這些元註解,由於這些元註解上,可能還有元註解,好比,在處理Target時,發現其上還有Documented、Retention、Target幾個註解,看到了吧,target註解還註解了target,在這塊的遞歸處理時,很容易棧溢出。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
  3. 遞歸處理上面的這些註解

具體的處理,基本就是這樣。文章開頭的遞歸,就是摘抄的這裏的代碼。

通過最終的處理後,能夠看看最後的效果,這裏截取的就是AnnotationMetadataReadingVisitor這個對象:

總結

這個就是spring 註解驅動的基石,實際上,spring不是一開始就這麼完備的,在以前的版本,並不支持遞歸獲取,spring也是慢慢一步一步發展壯大的。

感謝spring賞飯吃!

下一講,會講解component-scan掃描bean時,怎麼掃描類上的註解的。

相關文章
相關標籤/搜索