什么是元數據(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
注解編程的必備知識,它是ClassMetadata
和AnnotatedTypeMetadata
的子接口,具有兩者共同能力,並且新增了訪問注解的相關方法。可以簡單理解為它是對注解的抽象。
經常這么使用得到注解里面所有的屬性值:
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流資源組裝訪問元數據的門面接口
類關系圖
MetadataReader接口方法
public interface MetadataReader {
/**
* 返回class文件的IO資源引用
*/
Resource getResource();
/**
* 為基礎class讀取基本類元數據,返回基礎類的元數據。
*/
ClassMetadata getClassMetadata();
/**
*為基礎類讀取完整的注釋元數據,包括注釋方法的元數據。返回基礎類的完整注釋元數據
*/
AnnotationMetadata getAnnotationMetadata();
}
MetadataReader接口提供三個方法:
- 返回class文件的IO資源引用
- 返回基礎類的元數據
- 返回基礎類的完整注釋元數據
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的元數據集。
類關系圖
MetadataReaderFactory接口方法
public interface MetadataReaderFactory {
/**
* 根據class名稱創建MetadataReader
*/
MetadataReader getMetadataReader(String className) throws IOException;
/**
* 根據class的Resource創建MetadataReader
*/
MetadataReader getMetadataReader(Resource resource) throws IOException;
}
MetadataReaderFactory接口提供兩個方法:
- 根據class名稱生成MetadataReader
- 根據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));
}
}