Spring 中的 MetaData 接口

什麼是元數據(MetaData)

先直接貼一個英文解釋:

Metadata is simply data about data. It means it is a description and context of the data. It helps to organize, find and understand data。

上面介紹的大概意思是:元數據是關於數據的數據,元數據是數據的描述和上下文,它有助於組織,查找,理解和使用數據。

常用的元數據類型有:

  • 標題和說明;
  • 標籤和類別;
  • 誰創造的,何時創造的;
  • 誰最後修改時間,什麼時候修改;
  • 誰可以訪問或更新。

下面舉兩個列子:

每次使用當今的相機拍照時,都會收集並保存一堆元數據:

  • 日期和時間
  • 文檔名稱
  • 相機設置
  • 地理位置等

這些數據就是元數據,使用這些數據可以更好的使用照片,比如使用日期和時間信息可以做照片時光機功能(百度網盤好像就有這個功能),使用地理位置信息可以知道你去過哪裏。

再看一個列子。

對於一篇博客

每個博客文章都有標準的元數據字段,這些元數據包括:

  • 標題,
  • 作者,
  • 發佈時間
  • 類別,
  • 標籤。

使用這些元數據可以進行博客的搜索、文章的分類展示管理等。

更多的列子,請參考我的一篇翻譯文章:什麼是元數據。

好了,到這邊你應該已經知道什麼是元數據MetaData並瞭解元數據的作用和功能了。下面就來看看在Spring中元數據是指代啥。

Spring中的MeatData

從上面的類圖中,我們看到Spring中和MetaData相關的頂層接口有兩個:ClassMetadata和AnnotatedTypeMetadata

ClassMetadata

ClassMetadata,顧名思義,就是表示 Java 中類的元數據。那麼類的元數據有哪些呢,打開ClassMetadata的源代碼(代碼就不貼了),大致有下面這些:

  • 類名;
  • 是否是註解;
  • 是否是接口;
  • 是否抽象類;
  • 父類;
  • 實現的接口等;

詳細信息自己可以翻看下源代碼,這邊要抓住的重點就是要知道ClassMetadata表示的是一個類的元數據。可以和第一節中我舉的兩個列子類比下。

從上面的類圖中可以看出,ClassMetadata有一個實現類是StandardClassMetadata,這個類是基於反射實現獲取類元數據的,這個也是類名中“Standard”的含義。

查看源代碼你可以發現這個類唯一的一個構造函數已經被標註@Deprecated了,所以這個類已經不建議直接使用了。

AnnotatedTypeMetadata

這個接口表示的是註解元素(AnnotatedElement)的元數據。那什麼是註解元素呢?

我們常見的Class、Method、Constructor、Parameter等等都屬於它的子類都屬於註解元素。簡單理解:只要能在上面標註註解的元素都屬於這種元素。

public interface AnnotatedTypeMetadata {

    // 此元素是否標註有此註解,annotationName:註解全類名
    boolean isAnnotated(String annotationName);

    //取得指定類型註解的所有的屬性 - 值(k-v)
    // annotationName:註解全類名
    // classValuesAsString:若是true表示 Class用它的字符串的全類名來表示。這樣可以避免Class被提前加載
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName);
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
    
    // 支持重複註解
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
    
    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

}

這個接口的繼承樹如下:

兩個子接口相應的都提供了標準實現以及基於ASM的Visitor模式實現。

ASM 是一個通用的 Java 字節碼操作和分析框架。它可以用於修改現有類或直接以二進制形式動態生成類。 ASM 雖然提供與其他 Java 字節碼框架如 Javassist,CGLIB類似的功能,但是其設計與實現小而快,且性能足夠高。

AnnotationMetadata

這是理解Spring註解編程的必備知識,它是ClassMetadataAnnotatedTypeMetadata的子接口,具有兩者共同能力,並且新增了訪問註解的相關方法。可以簡單理解爲它是對註解的抽象。

經常這麼使用得到註解裏面所有的屬性值:
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annoMetadata, annType);

public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {
 
	//拿到當前類上所有的註解的全類名(注意是全類名)
	Set<String> getAnnotationTypes();
	// 拿到指定的註解類型
	//annotationName:註解類型的全類名
	Set<String> getMetaAnnotationTypes(String annotationName);
	
	// 是否包含指定註解 (annotationName:全類名)
	boolean hasAnnotation(String annotationName);
	//這個厲害了,用於判斷註解類型自己是否被某個元註解類型所標註
	//依賴於AnnotatedElementUtils#hasMetaAnnotationTypes
	boolean hasMetaAnnotation(String metaAnnotationName);
	
	// 類裏面只有有一個方法標註有指定註解,就返回true
	//getDeclaredMethods獲得所有方法, AnnotatedElementUtils.isAnnotated是否標註有指定註解
	boolean hasAnnotatedMethods(String annotationName);
	// 返回所有的標註有指定註解的方法元信息。注意返回的是MethodMetadata 原理基本同上
	Set<MethodMetadata> getAnnotatedMethods(String annotationName);
}

MethodMetadata

方法的元數據接口

public interface MethodMetadata extends AnnotatedTypeMetadata {
 
	String getMethodName();
	// Return the fully-qualified name of the class that declares this method.
	String getDeclaringClassName();
    
	// Return the fully-qualified name of this method's declared return type.
	String getReturnTypeName();

	boolean isAbstract();
	boolean isStatic();
	boolean isFinal();
	boolean isOverridable();
}

這個接口有兩個實現:

  • StandardMethodMetadata:基於反射的標準實現;
  • MethodMetadataReadingVisitor:基於ASM的實現的,繼承自ASM``的org.springframework.asm.MethodVisitor採用Visitor的方式讀取到元數據。

元數據,是框架設計中必須的一個概念,所有的流行框架裏都能看到它的影子,包括且不限於Spring、SpringBoot、SpringCloud、MyBatis、Hibernate等。它能模糊掉具體的類型,能讓數據輸出變得統一,能解決Java抽象解決不了的問題,比如運用得最廣的便是註解,因爲它不能繼承無法抽象,所以用元數據方式就可以完美行成統一的向上抽取讓它變得與類型無關,也就是常說的模糊效果,這便是框架的核心設計思想。

不管是ClassMetadata還是AnnotatedTypeMetadata都會有基於反射和基於ASM的兩種解決方案,他們能使用於不同的場景:

  • 標準反射:它依賴於Class,優點是實現簡單,缺點是使用時必須把Class加載進來
  • ASM:無需提前加載Class入JVM,所有特別特別適用於形如Spring應用掃描的場景(掃描所有資源,但並不是加載所有進JVM/容器~)

MetadataReader

spring 對MetadataReader的描述爲:Simple facade for accessing class metadata,as read by an ASM.大意是通過ASM讀取class IO流資源組裝訪問元數據的門面接口

類關係圖

img

MetadataReader接口方法

public interface MetadataReader {

    /**
     * 返回class文件的IO資源引用
     */
    Resource getResource();

    /**
     * 爲基礎class讀取基本類元數據,返回基礎類的元數據。
     */
    ClassMetadata getClassMetadata();

    /**
     *爲基礎類讀取完整的註釋元數據,包括註釋方法的元數據。返回基礎類的完整註釋元數據
     */
    AnnotationMetadata getAnnotationMetadata();
}

MetadataReader接口提供三個方法:

  1. 返回class文件的IO資源引用
  2. 返回基礎類的元數據
  3. 返回基礎類的完整註釋元數據

SimpleMetadataReader

final class SimpleMetadataReader implements MetadataReader {

    //class類IO流資源引用
    private final Resource resource;

     //class類元數據
    private final ClassMetadata classMetadata;
     //class類完整註釋元數據
    private final AnnotationMetadata annotationMetadata;

    /**
     * 構建函數,用於通過過ASM字節碼操控框架讀取class讀取class資源流
     */
    SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
        // 獲取class類IO流
        InputStream is = new BufferedInputStream(resource.getInputStream());
        ClassReader classReader;
        try {
            //通過ASM字節碼操控框架讀取class
            classReader = new ClassReader(is);
        }
        catch (IllegalArgumentException ex) {
        }
        finally {
            is.close();
        }

        //註解元數據讀取訪問者讀取註解元數據
        AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
        classReader.accept(visitor,ClassReader.SKIP_DEBUG);
        //註解元數據
        this.annotationMetadata = visitor;
        //class元數據
        this.classMetadata = visitor;
        this.resource = resource;
    }


    @Override
    public Resource getResource() {
        return this.resource;
    }

    @Override
    public ClassMetadata getClassMetadata() {
        //返回當前類元數據
        return this.classMetadata;
    }

    @Override
    public AnnotationMetadata getAnnotationMetadata() {
        //返回當前類的註解元數據
        return this.annotationMetadata;
    }

}

SimpleMetadataReader 爲MetadataReader的默認實現,在創建SimpleMetadataReader通過ASM字節碼操控框架讀取class讀取class資源流生成classMetadata與annotationMetadata

MetadataReaderFactory

MetadataReaderFactory接口 ,MetadataReader的工廠接口。
允許緩存每個MetadataReader的元數據集。

類關係圖

img

MetadataReaderFactory接口方法

public interface MetadataReaderFactory {
    /**
     * 根據class名稱創建MetadataReader
     */
    MetadataReader getMetadataReader(String className) throws IOException;

    /**
     * 根據class的Resource創建MetadataReader
     */
    MetadataReader getMetadataReader(Resource resource) throws IOException;

}

MetadataReaderFactory接口提供兩個方法:

  1. 根據class名稱生成MetadataReader
  2. 根據class的Resource生成MetadataReader

SimpleMetadataReaderFactory

public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
    // 資源加載器,此類根據路徑將給定的path生成IO流資源
    private final ResourceLoader resourceLoader;
    @Override
    public MetadataReader getMetadataReader(String className) throws IOException {
        try {
            //根據classname生成class對應的資源路徑
            String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
            //獲取classname的IO流資源     
            Resource resource = this.resourceLoader.getResource(resourcePath);
            //調用資源創建MetadataReader
            return getMetadataReader(resource);
        }
        catch (FileNotFoundException ex) {
        }
    }
    
    /**
     *  根據class資源創建MetadataReader 默認實現
     */
    @Override
    public MetadataReader getMetadataReader(Resource resource) throws IOException {
        return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
    }

}

SimpleMetadataReaderFactory類爲MetadataReaderFactory的簡單實現,默認實現了MetadataReaderFactory的兩個方法

  • 在getMetadataReader(String className) 方法中根據className創建class的Resource,然後調用getMetadataReader(Resource resource)
  • 在getMetadataReader(Resource resource) 方法中默認創建了SimpleMetadataReader

CachingMetadataReaderFactory

public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory {
    // 默認的緩存大小
    public static final int DEFAULT_CACHE_LIMIT = 256;
    // 內存緩存列表,Resource-MetadataReader的映射緩存
    @Nullable
    private Map<Resource, MetadataReader> metadataReaderCache;
    
    @Override
    public MetadataReader getMetadataReader(Resource resource) throws IOException {
        if (this.metadataReaderCache instanceof ConcurrentMap) {
        
            MetadataReader metadataReader = this.metadataReaderCache.get(resource);
            if (metadataReader == null) {
                metadataReader = super.getMetadataReader(resource);
                //緩存到本地緩存
                this.metadataReaderCache.put(resource, metadataReader);
            }
            return metadataReader;
        }
        else if (this.metadataReaderCache != null) {
            synchronized (this.metadataReaderCache) {
                MetadataReader metadataReader = this.metadataReaderCache.get(resource);
                if (metadataReader == null) {
                    metadataReader = super.getMetadataReader(resource);
                //緩存到本地緩存   this.metadataReaderCache.put(resource, metadataReader);
                }
                return metadataReader;
            }
        }
        else {
            return super.getMetadataReader(resource);
        }
    }
}

CachingMetadataReaderFactory 類在SimpleMetadataReaderFactory的基礎上增加了緩存功能,對Resource-MetadataReader的映射做了本地緩存

ConcurrentReferenceCachingMetadataReaderFactory

這個工廠和CachingMetadataReaderFactory 的功能一致,只是這個工廠內部的緩存支持併發。

一個簡單的使用例子

講了這麼多,最後用一個簡單的例子來結束這篇文章。

public static void main(String[] args) throws Exception {
    ResourceLoader resourceLoader = new DefaultResourceLoader();
    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(resourceLoader);
    MetadataReader metadataReader = factory.getMetadataReader(SpringIOCTest.class.getName());
    ClassMetadata classMetadata = metadataReader.getClassMetadata();
    Method[] methods = ClassMetadata.class.getMethods();
    for (Method method : methods) {
        System.out.println(method.getName() + ":" + method.invoke(classMetadata));
    }
}

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章