前言
衆所周知,spring
從 2.5 版本以後開始支持使用註解代替繁瑣的 xml 配置,到了 springboot
更是全面擁抱了註解式配置。平時在使用的時候,點開一些常見的等註解,會發現往往在一個註解上總會出現一些其他的註解,比如 @Service
:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
大部分情況下,我們可以將 @Service
註解等同於 @Component
註解使用,則是因爲 spring 基於其 JDK 對元註解的機制進行了擴展。
在 java 中,元註解是指可以註解在其他註解上的註解,spring 中通過對這個機制進行了擴展,實現了一些原生 JDK 不支持的功能,比如允許在註解中讓兩個屬性互爲別名,或者將一個帶有元註解的子註解直接作爲元註解看待,或者在這個基礎上,通過 @AliasFor
或者同名策略讓子註解的值覆蓋元註解的值。
本文將基於 spring 源碼 5.2.x
分支,解析 spring 如何實現這套功能的。
這是系列的第三篇文章,將詳細介紹 Spring 是如何在經過搜索與屬性映射後,將處理後的註解合成爲合併註解的。
相關文章:
一、合併註解
我們在前文了解用於搜索註解的合併註解聚合 MergedAnnotations
與用於完成註解屬性映射的 AnnotationTypeMappings
和AnnotationTypeMapping
,現在我們需要知道在 MergedAnnotations
這個容器中,AnnotationTypeMappings
和AnnotationTypeMapping
是如何轉爲一個我們所需要的合併註解 MergedAnnotation
的。
與前文一樣,我們以 AnnotatedElementUtils.findMergedAnnotations
方法作爲入口:
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
// 1、下述任意情況下直接獲取元素上聲明的註解:
// a.查找的註解屬於java、javax或者org.springframework.lang包
// b.被處理的元素屬於java包,或被java包中的對象聲明,或者就是Ordered.class
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return element.getDeclaredAnnotation(annotationType);
}
// 2、將元素上的全部註解合成MergedAnnotation
return findAnnotations(element)
// 3、從MergedAnnotation獲取與該類型對應的MergedAnnotations
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
// 4、根據MergedAnnotation通過動態代理生成一個註解實例
.synthesize(MergedAnnotation::isPresent).orElse(null);
}
我們在上文順着 MergedAnnotations.get
一路找到 TypeMappedAnnotations.MergedAnnotationFinder
的 process
方法,在這裏我們目睹了一個普通的註解的元註解被解析爲 AnnotationTypeMappings
與 AnnotationTypeMapping
的過程:
private MergedAnnotation<A> process(
Object type, int aggregateIndex, @Nullable Object source, Annotation annotation) {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
}
// 將該註解上的元註解解析爲由多個AnnotationTypeMapping聚合的AnnotationTypeMappings
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
for (int i = 0; i < mappings.size(); i++) {
// 獲取元註解對應的AnnotationTypeMapping
AnnotationTypeMapping mapping = mappings.get(i);
if (isMappingForType(mapping, annotationFilter, this.requiredType)) {
// 使用AnnotationTypeMapping創建一個合併註解
MergedAnnotation<A> candidate = TypeMappedAnnotation.createIfPossible(
mapping, source, annotation, aggregateIndex, IntrospectionFailureLogger.INFO);
if (candidate != null && (this.predicate == null || this.predicate.test(candidate))) {
if (this.selector.isBestCandidate(candidate)) {
return candidate;
}
updateLastResult(candidate);
}
}
}
return null;
}
其他部分在上文已經詳細的分析了,因此此處我們僅需關注 TypeMappedAnnotation.createIfPossible
這一個方法:
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
AnnotationTypeMapping mapping, @Nullable Object source, Annotation annotation,
int aggregateIndex, IntrospectionFailureLogger logger) {
return createIfPossible(mapping, source, annotation,
ReflectionUtils::invokeMethod, aggregateIndex, logger);
}
該方法是 AnnotationTypeMapping
轉爲 MergedAnnotation
的關鍵。
1、合併註解的創建
TypeMappedAnnotation
是 MergedAnnotation
一個通用實現,在大部分情況下,我們所說的合併註解其實指的就是這個類。
通過它的構造方法我們得以瞭解其創建過程:
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
@Nullable Object source, @Nullable Object rootAttributes, ValueExtractor valueExtractor,
int aggregateIndex, @Nullable int[] resolvedRootMirrors) {
// 當前合併註解對應的AnnotationTypeMapping
this.mapping = mapping;
// 類加載器
this.classLoader = classLoader;
// 當前註解的數據源
this.source = source;
// AnnotationTypeMapping對應的root註解
this.rootAttributes = rootAttributes;
// 通過屬性方法對象獲得屬性值的方法,一般是ReflectionUtils::invokeMethod
this.valueExtractor = valueExtractor;
// 該註解對應的聚合索引
this.aggregateIndex = aggregateIndex;
// 是否使用映射過的屬性值
this.useMergedValues = true;
// 屬性過濾器
this.attributeFilter = null;
// 通過MirrorSets解析得到的確認屬性
this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors :
mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor));
this.resolvedMirrors = (getDistance() == 0 ? this.resolvedRootMirrors :
mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
}
可以看得出,TypeMappedAnnotation
基本可以認爲是 AnnotationTypeMapping
的包裝類,它以一個 AnnotationTypeMapping
實例作爲數據源,從而提供一些關於映射後的屬性的相關功能。
它本身並沒有很多特殊的邏輯,我們僅需要關注通過它合成註解的代理對象,以及後續調用代理對象時,是如何從映射過的屬性獲取值的。
2、合併註解的合成
回到 AnnotatedElementUtils.findMergedAnnotations
,我們可以看到,在通過 MergedAnnotations
獲得了一個 MergedAnnotation
對象——實際上是 TypeMappedAnnotation
對象——之後,又調用了 MergedAnnotation.synthesize
方法,將 MergedAnnotation
轉成了一個調用方指定類型的註解對象。
該方法先調用了 AbstractMergedAnnotation
的 synthesize
方法:
public Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException {
return (condition.test(this) ? Optional.of(synthesize()) : Optional.empty());
}
隨後再調用了實現類 TypeMappedAnnotation
的 synthesize
方法:
public A synthesize() {
if (!isPresent()) {
throw new NoSuchElementException("Unable to synthesize missing annotation");
}
// 如果已經合成過就直接返回緩存的實例
A synthesized = this.synthesizedAnnotation;
if (synthesized == null) {
// 合成註解
synthesized = createSynthesized();
this.synthesizedAnnotation = synthesized;
}
return synthesized;
}
繼續點開 createSynthesized
:
protected A createSynthesized() {
// 滿足下述條件時,直接返回根註解:
// 1.若當前註解的屬性與根註解相同
// 2.且該合成註解還沒有被合成過
// 3.該註解存在映射後的屬性
if (getType().isInstance(this.rootAttributes) && !isSynthesizable()) {
return (A) this.rootAttributes;
}
// 使用動態代理創建一個代理註解
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
}
private boolean isSynthesizable() {
// Already synthesized?
if (this.rootAttributes instanceof SynthesizedAnnotation) {
return false;
}
return this.mapping.isSynthesizable();
}
而 SynthesizedMergedAnnotationInvocationHandler
是一個用於 JDK 動態代理的 InvocationHandler
,我們不需要完全站看,僅需看看它的構造函數與 InvocationHandler.invoke
就能明白它的運作機制了:
final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> implements InvocationHandler {
// 合併註解實例
private final MergedAnnotation<?> annotation;
// 代理註解的類型
private final Class<A> type;
// 代理註解的註解方法
private final AttributeMethods attributes;
// 註解屬性的值緩存
private final Map<String, Object> valueCache = new ConcurrentHashMap<>(8);
// hashcode
@Nullable
private volatile Integer hashCode;
// toString的值
@Nullable
private volatile String string;
private SynthesizedMergedAnnotationInvocationHandler(MergedAnnotation<A> annotation, Class<A> type) {
Assert.notNull(annotation, "MergedAnnotation must not be null");
Assert.notNull(type, "Type must not be null");
Assert.isTrue(type.isAnnotation(), "Type must be an annotation");
this.annotation = annotation;
this.type = type;
this.attributes = AttributeMethods.forAnnotationType(type);
}
}
至此,合併註解的合成機制已經很明確了:
- 先通過
AnnotationTypeMapping
創建TypeMappedAnnotation
; - 通過
TypeMappedAnnotation.synthesize
創建一個對應的類型的註解對象; - 如果該
TypeMappedAnnotation
無需合成,則直接返回對應的原始註解對象,否則返回基於該合併註解生成的JDK動態代理對象;
二、合併註解的取值
承接上文,當我們使用 MergedAnnotation.synthesize
方法時,我們可能會得到兩種對象:
- 第一種就是原始的註解對象,這個沒什麼好說的;
- 第二種就是通過
SynthesizedMergedAnnotationInvocationHandler
生成的註解代理對象;
而通過註解代理對象取值時,這些方法會被代理到 SynthesizedMergedAnnotationInvocationHandler
中存放的 MergedAnnotation
對象上,從而讓這個代理對象通過原始註解的屬性,獲得與原始註解不一樣的屬性值。
1、方法代理
當我們調用代理對象的屬性值時,它會在 SynthesizedMergedAnnotationInvocationHandler
中,通過 invoke
代理到對應的方法上:
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 代理 equals 方法
if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
// 代理 hashCode 方法
if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
// 代理 toString 方法
if (ReflectionUtils.isToStringMethod(method)) {
return annotationToString();
}
// 代理 isAnnotationTypeMethod 方法
if (isAnnotationTypeMethod(method)) {
return this.type;
}
// 代理獲取註解屬性的方法
if (this.attributes.indexOf(method.getName()) != -1) {
return getAttributeValue(method);
}
throw new AnnotationConfigurationException(String.format(
"Method [%s] is unsupported for synthesized annotation type [%s]", method, this.type));
}
2、註解屬性的獲取
這裏我們代理對象是如何獲取註解屬性值的:
private Object getAttributeValue(Method method) {
// 緩存屬性值
Object value = this.valueCache.computeIfAbsent(method.getName(), attributeName -> {
// 獲取代理方法的返回值類型
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
// 從 MergedAnnotation 獲取對應屬性值
return this.annotation.getValue(attributeName, type).orElseThrow(
() -> new NoSuchElementException("No value found for attribute named '" + attributeName +
"' in merged annotation " + this.annotation.getType().getName()));
});
// Clone non-empty arrays so that users cannot alter the contents of values in our cache.
if (value.getClass().isArray() && Array.getLength(value) > 0) {
value = cloneArray(value);
}
return value;
}
這裏的 MergedAnnotation.getValue
最終在經過多次跳轉後,調到 TypeMappedAnnotation.getAttributeValue
上:
protected <T> T getAttributeValue(String attributeName, Class<T> type) {
// 獲取方法在AttributeMethods中的下標
int attributeIndex = getAttributeIndex(attributeName, false);
// 若方法存在,則通過下標調用
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
}
private <T> T getValue(int attributeIndex, Class<T> type) {
// 獲取屬性對應的方法
Method attribute = this.mapping.getAttributes().get(attributeIndex);
// 獲取屬性值,若有必要則進行屬性映射
Object value = getValue(attributeIndex, true, false);
if (value == null) {
// 如果沒有值則嘗試獲得默認值
value = attribute.getDefaultValue();
}
// 進行類型適配
return adapt(attribute, value, type);
}
而這邊的 getValue
方法就是真正要獲取屬性值的地方。
private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
AnnotationTypeMapping mapping = this.mapping;
// 是否要獲取合併後的屬性
if (this.useMergedValues) {
// 如果有必要,屬性是否會被來自根註解的屬性覆蓋
int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
// 如果不會被來自根註解的屬性覆蓋,並且允許使用子註解屬性覆蓋該屬性
if (mappedIndex == -1 && useConventionMapping) {
mappedIndex = this.mapping.getConventionMapping(attributeIndex);
}
// 如果上述兩情況只要有任意一點符合,則令attributeIndex變爲根/子註解中覆蓋屬性的下標
if (mappedIndex != -1) {
mapping = mapping.getRoot();
attributeIndex = mappedIndex;
}
}
// 若有必要,且當前屬性下標對應的屬性在註解內還存在別名屬性,則通過MirrorSets獲得唯一確定的屬性的下標
if (!forMirrorResolution) {
attributeIndex =
(mapping.getDistance() != 0 ? this.resolvedMirrors : this.resolvedRootMirrors)[attributeIndex];
}
if (attributeIndex == -1) {
return null;
}
// 如果自己就是根節點,則從自己身上獲取
if (mapping.getDistance() == 0) {
Method attribute = mapping.getAttributes().get(attributeIndex);
Object result = this.valueExtractor.extract(attribute, this.rootAttributes);
return (result != null ? result : attribute.getDefaultValue());
}
// 如果自己不是根節點,則從元註解上獲取
return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
}
@Nullable
private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorResolution) {
Object value = null;
if (this.useMergedValues || forMirrorResolution) {
value = this.mapping.getMappedAnnotationValue(attributeIndex, forMirrorResolution);
}
if (value == null) {
Method attribute = this.mapping.getAttributes().get(attributeIndex);
value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
}
return value;
}
Object getMappedAnnotationValue(int attributeIndex, boolean metaAnnotationsOnly) {
int mappedIndex = this.annotationValueMappings[attributeIndex];
if (mappedIndex == -1) {
return null;
}
AnnotationTypeMapping source = this.annotationValueSource[attributeIndex];
if (source == this && metaAnnotationsOnly) {
return null;
}
return ReflectionUtils.invokeMethod(source.attributes.get(mappedIndex), source.annotation);
}
這一步有點複雜,主要是根據不同的情況,通過 AnnotationTypeMapping
中的幾個屬性映射數組,包括 aliasMappings
、conventionMappings
,annotationValueMappings
與 annotationValueSource
來確定最終用於取值的 AnnotationTypeMapping
對象與調用的方法在 AttributeMethods
中的下標:
- 如果要合併屬性值,則:
- 若該屬性被
root
中的同名屬性覆蓋,即aliasMappings
數組對應下標不爲-1
,則記錄該root
屬性下標; - 若該屬性不被
root
中同名屬性覆蓋,則確定是否被子註解中的同名屬性覆蓋,即conventionMappings
數組對應下標不爲-1
,則記錄該覆蓋屬性下標;
- 若該屬性被
- 若允許別名,則在上述基礎上,繼續進行處理:
- 若當前註解已經是根註解,則從
resolvedRootMirrors
上,獲得該屬性在同一註解下關聯的別名屬性中,唯一確定的有效屬性的下標; - 若當前註解不是根註解,則從
resolvedRootMirrors
上,獲得該屬性在同一註解下關聯的別名屬性中,唯一確定的有效屬性的下標;
- 若當前註解已經是根註解,則從
- 若當前註解是根註解,則使用從根註解對象
rootAttributes
上根據屬性下標獲取對應方法,然後通過反射調用獲取屬性值; - 若當前註解不是根註解,則:
- 若不支持屬性覆蓋以及別名,則直接和獲取該註解屬性下標對應方法,並通過反射調用獲取屬性值;
- 若支持屬性覆蓋以及別名,則通過屬性下標從
annotationValueSource
找到對應的註解對象,再通過annotationValueMappings
找到要在該註解中調用的屬性下標,然後在通過屬性下標找到對應的屬性方法後,通過反射調用獲取屬性值;
至此,獲取屬性值的方法流程也走完了。
總結
在這一章,我們瞭解了當通過 MergedAnnotations
獲得註解並解析得到 AnnotationTypeMapping
後,AnnotationTypeMapping
是如何再轉爲我們所需的 MergedAnnotation
,以及在此之後,MergedAnnotation
又是如何生成我們最終所需要的代理註解的。
簡而言之,當解析註解的元註解獲得所需的 AnnotationTypeMapping
後,MergedAnnotation
會判斷 AnnotationTypeMapping
是否發生過屬性映射,如果沒有則返回該映射對象對應的原始註解,否則就通過 SynthesizedMergedAnnotationInvocationHandler
生成一個對應類型的 JDK 動態代理對象。
當我們通過代理對象去調用註解的方法,獲取註解的屬性的時候,SynthesizedMergedAnnotationInvocationHandler
會把方法代理到對應的內部方法中,而獲取屬性時,還會通過 MergedAnnotation.getValue
,最終繞到 AnnotationTypeMapping
中獲取被映射後的屬性值。
題外話
至此,關於 Spring 註解機制的三篇文章已經全部寫完了,在瞭解了它是實現原理的同時,筆者也深刻的認識到了其功能的強大,尤其是 MergeAnnotation
真切的彌補了 java 註解不支持繼承所帶來的的一些痛點,這點筆者在自己的項目嘗試了全面擁抱 spring 的增強註解後深有感觸。
出於鞏固知識,也出於爲想要在非 Spring 環境下享受這個功能的同學考慮,作者決定搞一套類似的開源項目,目前也已經有了一些成果:
- 筆者嘗試爲常用的開源工具類庫
hutool
提了一個 PR ,也有幸被作者大佬採納了,有需要的同學可以在Hutool-5.8.5
及以上版本使用該功能; - 由於考慮有些項目並沒有引入
Hutool
,以及跟作者溝通後覺得這個特性並不適合搬到即將發佈的Hutool-6.x
,因此筆者單獨將作爲一個註解工具類庫開源了,有興趣的同學可以瞭解一下powerful-annotation
;