眾所周知,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 不支援的功能,比如允許在註解中讓兩個屬性互為別名,或者將一個帶有元註解的子註解直接作為元註解看待,或者在這個基礎上,通過 @AlisaFor
或者同名策略讓子註解的值覆蓋元註解的值。
筆者今天將基於 spring 5.2.x
的原始碼研究 spring 如何實現這套功能的。
入口
我們從最常用的 AnnotatedElementUtils#findMergedAnnotation
方法開始。
在 spring 中, 常見的get
表示從某個元素直接宣告的註解中獲取註解,而 find
語意表示從一個元素的直接以及間接宣告的註解中查詢註解。換而言之,即從包括該元素的註解、註解的元註解、介面或類的複雜層級結構中查詢。MergedAnnotation
則表示一個存在層級結構的根註解聚合得到的「合併註解」,這個註解的各項屬性將會因為根註解和元註解的層級結構而有所不同。
findMergedAnnotation
從語意上理解,就是從一個元素以及全部他的介面或父類別中,獲取指定型別的註解,然後將這些註解和註解上可能存在的元註解聚合為合併註解並返回。
該方法實現如下:
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);
}
此處實際一個四步:
find
;MergedAnnotations
;annotationType
對應的合成 MergedAnnotations
;MergedAnnotation
通過動態代理生成一個註解範例;在第一步,確定是否可以直接從元素上直接獲取宣告的註解從而避免效能消耗較大的 find
。
此處通過註解過濾器 AnnotationFilter
來過濾註解,該類是一個函數式介面,用於匹配傳入的註解範例、型別或名稱。
@FunctionalInterface
public interface AnnotationFilter {
// 根據範例匹配
default boolean matches(Annotation annotation) {
return matches(annotation.annotationType());
}
// 根據型別匹配
default boolean matches(Class<?> type) {
return matches(type.getName());
}
// 根據名稱匹配
boolean matches(String typeName);
}
AnnotationFilter
預設提供三個可選的靜態範例:
PLAIN
:類是否屬於 java.lang
、org.springframework.lang
包;JAVA
:類是否屬於 java
、javax
包;ALL
:任何類;此處過濾器選擇了 PLAIN
,即當查詢的註解屬於 java.lang
、org.springframework.lang
包的時候就不進行查詢,而是直接從被查詢的元素直接宣告的註解中獲取。這個選擇不難理解,java.lang
包下提供的都是諸如@Resource
或者 @Target
這樣的註解,而springframework.lang
包下提供的則都是 @Nonnull
這樣的註解,這些註解基本不可能作為有特殊業務意義的元註解使用,因此預設忽略也是合理的。
實際上,PLAIN
也是大部分情況下的使用的預設過濾器。
若要查詢的註解不屬於 java.lang
、org.springframework.lang
包,還需要確認被處理的元素。
這裡使用了AnnotationsScanner
工具類,它的作用跟名字一樣,就是從各種 AnnotatedElement
以及複雜的巢狀層級中掃描並解析註解。
此處AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)
這一段程式碼如下:
static boolean hasPlainJavaAnnotationsOnly(@Nullable Object annotatedElement) {
if (annotatedElement instanceof Class) {
// 1.1 如果是類,則宣告它不能是java包下的,或者Ordered.class
return hasPlainJavaAnnotationsOnly((Class<?>) annotatedElement);
}
else if (annotatedElement instanceof Member) {
// 1.2 如果是類成員,則宣告它的類不能是java包下的,或者Ordered.class
return hasPlainJavaAnnotationsOnly(((Member) annotatedElement).getDeclaringClass());
}
else {
return false;
}
}
// 1.1
static boolean hasPlainJavaAnnotationsOnly(Class<?> type) {
return (type.getName().startsWith("java.") || type == Ordered.class);
}
// 1.2
static boolean hasPlainJavaAnnotationsOnly(Class<?> type) {
return (type.getName().startsWith("java.") || type == Ordered.class);
}
簡而言之,就是被查詢的元素如果是或者屬於 java
包下的類以及 Ordered.class
,則不進行查詢。
由於 java
包下的程式碼都是標準庫,自定義的元註解不可能加到原始碼中,因此只要類屬於 java
包,則我們實際上是可以認為它是不可能有符合 spring 語意的元註解的。
總結一下查詢註解這一步操作:
當任意下述任意條件時,不進行 find
,則是直接從元素上宣告的註解中獲取註解:
java.lang
、org.springframework.lang
包;java
包下的類以及 Ordered.class
;當上述條件皆不符合時,繼續進行 find
,也就是下述過程。
findAnnotations
會經過多層的呼叫,實際上最終目的是建立一個 MergedAnnotations
,並且確定它的四個屬性:
element
,即要被查詢的元素;searchStrategy
,即 MergedAnnotations.SearchStrategy
列舉;repeatableContainers
,即@Repeatable
指定的對應容器註解;annotationFilter
,同上文,用於過濾註解;這裡返回的是 MergedAnnotations
的實現類 TypeMappedAnnotations
:
// AnnotatedElementUtils
private static MergedAnnotations findAnnotations(AnnotatedElement element) {
// 1、設定重複註解容器:空容器
// 2、設定查詢策略:查詢類、全部父類別以及其父介面
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
}
// MergedAnnotations
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
RepeatableContainers repeatableContainers) {
// 3、設定註解過濾器:過濾屬於`java`、`javax`或者`org.springframework.lang`包的註解
return from(element, searchStrategy, repeatableContainers, AnnotationFilter.PLAIN);
}
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
Assert.notNull(repeatableContainers, "RepeatableContainers must not be null");
Assert.notNull(annotationFilter, "AnnotationFilter must not be null");
return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers, annotationFilter);
}
// TypeMappedAnnotations
// 4、建立聚合註解:TypeMappedAnnotations
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
// 該元素若符合下述任一情況,則直接返回空註解:
// a.被處理的元素屬於java包、被java包中的物件宣告,或者就是Ordered.class
// b.只查詢元素直接宣告的註解,但是元素本身沒有宣告任何註解
// c.查詢元素的層級結構,但是元素本身沒有任何層級結構
// d.元素是橋接方法
if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) {
return NONE;
}
// 5、返回一個具體的實現類範例
return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter);
}
RepeatableContainers
抽象類表示某個可重複註解與他的某個容器註解之間的對應關係,即常見的下述寫法:
// 可重複的註解
@Repeatable(RepeatableContainerAnnotation.class)
@interface RepeatableAnnotation {}
// 可重複註解的容器註解
@interface RepeatableContainerAnnotation {
RepeatableAnnotation[] value() default {};
}
此處 RepeatableContainerAnnotation
就是 RepeatableAnnotation
的容器註解,他們就對應一個RepeatableContainers
範例。
實際場景中甚至可能還存在 RepeatableContainerAnnotation
的容器註解......以此類推,無限套娃。因此,RepeatableContainers
實際上是一個樹結構,通過 parent
變數持有當前容器註解與容器註解的容器註解的對應關係。
一個RepeatableContainers
中會通過 parent
成員變數持有他的容器註解,而容器註解同樣被封裝為 RepeatableContainers
,若它也存在對應容器註解,則它也會通過 parent
變數持有他的容器註解......以此類推。
它的結構大概如下:
// 頂層抽象類
public abstract class RepeatableContainers {
@Nullable
private final RepeatableContainers parent; // 容器註解
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.parent == null) {
return null;
}
return this.parent.findRepeatedAnnotations(annotation); // 返回父節點的findRepeatedAnnotations方法返回值
}
}
// 實現類
private static class ExplicitRepeatableContainer extends RepeatableContainers {
private final Class<? extends Annotation> repeatable; // 可重複的註解
private final Class<? extends Annotation> container; // 容器註解
private final Method valueMethod; // 容器註解的value方法
// 獲取可重複註解
@Override
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
// 若容器註解的value方法返回值就是可重複註解,說明容器註解就是該可重複註解的直接容器
if (this.container.isAssignableFrom(annotation.annotationType())) {
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
}
// 否則說明存在巢狀結構,當前容器註解實際上放的也是一個容器註解,繼續遞迴直到找到符合條件的容器註解為止
return super.findRepeatedAnnotations(annotation);
}
}
// 實現類
private static class StandardRepeatableContainers extends RepeatableContainers {
private static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>();
private static final Object NONE = new Object();
private static StandardRepeatableContainers INSTANCE = new StandardRepeatableContainers();
StandardRepeatableContainers() {
super(null);
}
@Override
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
if (method != null) {
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
}
return super.findRepeatedAnnotations(annotation);
}
@Nullable
private static Method getRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
Object result = cache.computeIfAbsent(annotationType,
StandardRepeatableContainers::computeRepeatedAnnotationsMethod);
return (result != NONE ? (Method) result : null);
}
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
// 只有一個名為value的屬性
if (methods.hasOnlyValueAttribute()) {
Method method = methods.get(0);
Class<?> returnType = method.getReturnType();
// 返回值是可重複註解型別的陣列,並且可重複註解上存在@Repeatable註解
if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType();
if (Annotation.class.isAssignableFrom(componentType) &&
componentType.isAnnotationPresent(Repeatable.class)) {
return method;
}
}
}
return NONE;
}
}
在預設情況下,返回一個名為 NONE
的範例,該容器註解範例表示查詢的註解不存在對應容器註解。
查詢策略MergedAnnotations.SearchStrategy
是一個內部的列舉類,他提供以下選項:
DIRECT
:只查詢元素上直接宣告的註解,不包括通過@Inherited
繼承的註解;
INHERITED_ANNOTATIONS
:只查詢元素直接宣告或通過@Inherited
繼承的註解;
SUPERCLASS
:查詢元素直接宣告或所有父類別的註解;
TYPE_HIERARCHY
:查詢元素、所有父類別以及實現的父介面的全部註解;
TYPE_HIERARCHY_AND_ENCLOSING_CLASSES
:查詢封閉類以及其子類。
封閉類是 JDK17 的新特性,可參考 詳解 Java 17中的新特性:「密封類」,本章將不過多涉及該內容;
當不指定時,預設的查詢策略為TYPE_HIERARCHY
,即查詢元素、所有父類別以及實現的父介面的全部註解。
同上,這裡使用了預設的 PLAIN
過濾器,用於過濾屬於 java.lang
、org.springframework.lang
包的註解。
MergedAnnotations
本身實現了Iterable
介面,用於表示一組處於聚合狀態的 MergedAnnotation
,而MergedAnnotation
就是對應我們實際上的合併註解,舉個例子:
假如我們有個 Foo.class
,類上存在 @AnnotationA
與 @AnnotationB
兩個註解,這兩個註解又都有一大堆的元註解。此時 @AnnotationA
與 @AnnotationB
則各表示一個 MergedAnnotation
,而 MergedAnnotations
表示 Foo.class
上的兩個MergedAnnotation
。
MergedAnnotations
提供了四個比較重要的靜態方法:
get
:用於從聚合註解中獲取某個指定型別的合併註解;stream
:用於從聚合註解中獲取多個指定型別的合併註解構成的 stream 流;isPresent
:某個型別的合併註解是否在該聚合中存在;from
:解析某個帶有註解的元素獲得對應的聚合註解;TypeMappedAnnotations
則為該介面的主要實現類,這一步最終返回的就是一個TypeMappedAnnotations
的範例。
總結一下findAnnotations
這一步操作,根本目的就是獲得一個TypeMappedAnnotations
實現,步驟如下:
NONE
,即註解沒有對應的容器註解;PLAIN
,預設過濾屬於java
、javax
或者org.springframework.lang
包的註解;TypeMappedAnnotations
範例;在第三步,將通過 MergedAnnotations#get
方法獲得指定型別對應的合併註解 MergedAnnotation
範例,這裡我們以實現類 TypeMappedAnnotations
為例:
@Override
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector) {
// 1、若該註解無法通過過濾,即該註解若屬於 `java.lang`、`org.springframework.lang` 包,則直接返回空註解
if (this.annotationFilter.matches(annotationType)) {
return MergedAnnotation.missing();
}
// 2、使用MergedAnnotationFinder掃描並獲取註解
MergedAnnotation<A> result = scan (
annotationType, new MergedAnnotationFinder<>(annotationType, predicate, selector)
);
return (result != null ? result : MergedAnnotation.missing());
}
@Nullable
private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) {
if (this.annotations != null) {
// a.若指定了查詢的註解,則掃描這些註解以及其元註解的層級結構
R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations);
return processor.finish(result);
}
if (this.element != null && this.searchStrategy != null) {
// b.未指定查詢的註解,則直接掃描元素以及其父類別、父介面的層級結構
return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor);
}
return null;
}
在建立合併註解 MergedAnnotation
時,需要傳入一個選擇器MergedAnnotationSelector
。
MergedAnnotationSelector
本質上就是一個比較器,用於從兩個註解中選擇出一個權重更高的註解,此處的「權重」實際就是指註解離被查詢元素的距離,距離越近權重就越高,舉個例子:
假如現在有個被查詢元素 Foo.class
,他上面有一個註解@A
,@A
上還有一個元註解 @B
,此時@A
距離Foo.class
的距離是 0 ,即@A
是在 Foo.class
上直接宣告的,而@B
距離Foo.class
的距離就是 1 ,當 @A
與 @B
二選一的時候,距離更近的@A
的權重就更高,換而言之,就是更匹配。
他在MergedAnnotationSelectors
中提供了Nearest
和FirstDirectlyDeclared
兩個預設的實現,也基本都遵循這個規則:
// 距離優先選擇器
private static class Nearest implements MergedAnnotationSelector<Annotation> {
@Override
public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
return annotation.getDistance() == 0; // 若註解是否被元素直接宣告
}
@Override
public MergedAnnotation<Annotation> select(
MergedAnnotation<Annotation> existing, MergedAnnotation<Annotation> candidate) {
// 若候選註解離元素的距離比當前註解更近,則返回候選註解,否則返回當前註解
if (candidate.getDistance() < existing.getDistance()) {
return candidate;
}
return existing;
}
}
// 直接宣告註解選擇器
private static class FirstDirectlyDeclared implements MergedAnnotationSelector<Annotation> {
@Override
public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
return annotation.getDistance() == 0; // 若註解是否被元素直接宣告
}
// 若當前註解沒有被元素直接宣告,而候選註解被元素直接宣告時返回候選註解,否則返回已有註解
@Override
public MergedAnnotation<Annotation> select(
MergedAnnotation<Annotation> existing, MergedAnnotation<Annotation> candidate) {
if (existing.getDistance() > 0 && candidate.getDistance() == 0) {
return candidate;
}
return existing;
}
}
回到 MergedAnnotations#get()
方法,這裡出現了一個新類 MergedAnnotationFinder
,它是TypeMappedAnnotations
中的一個內部類,它實現了 AnnotationsProcessor
介面,是註解處理器的一種,會在查詢到註解後被回撥。
當呼叫他的 doWithAnnotations
方法時,他將會把入參的註解包括對應的所有元註解解析為一堆 AnnotationTypeMapping
,然後遍歷並篩選出所需要的註解型別對應的 AnnotationTypeMapping
,再封裝為一堆對應的 MergedAnnotation
,最後再用選擇器從裡面選擇出最匹配的 MergedAnnotation
並返回。
對應程式碼如下:
private class MergedAnnotationFinder<A extends Annotation>
implements AnnotationsProcessor<Object, MergedAnnotation<A>> {
// 要查詢的註解型別
private final Object requiredType;
// 過濾器
@Nullable
private final Predicate<? super MergedAnnotation<A>> predicate;
// 選擇器,作用類似於比較器,用於從兩個註解中獲得一個權重更高的註解範例
private final MergedAnnotationSelector<A> selector;
// 最終的返回結構
@Nullable
private MergedAnnotation<A> result;
MergedAnnotationFinder(Object requiredType, @Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector) {
this.requiredType = requiredType;
this.predicate = predicate;
// 若不指定選擇器,則預設使用MergedAnnotationSelectors.Nearest
// 當存在兩個相同註解式,選擇層級更低的,即離根註解更近的註解
this.selector = (selector != null ? selector : MergedAnnotationSelectors.nearest());
}
@Override
@Nullable
public MergedAnnotation<A> doWithAggregate(Object context, int aggregateIndex) {
return this.result;
}
@Override
@Nullable
public MergedAnnotation<A> doWithAnnotations(Object type, int aggregateIndex,
@Nullable Object source, Annotation[] annotations) {
for (Annotation annotation : annotations) {
// 找到至少一個不被過濾的、並且可以合成合併註解的註解範例
if (annotation != null && !annotationFilter.matches(annotation)) {
MergedAnnotation<A> result = process(type, aggregateIndex, source, annotation);
if (result != null) {
return result;
}
}
}
return null;
}
@Nullable
private MergedAnnotation<A> process(
Object type, int aggregateIndex, @Nullable Object source, Annotation annotation) {
// 1、若要查詢的註解可重複,則先找到其容器註解,然後獲取容器中的可重複註解並優先處理
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
}
// 2、解析註解與註解的對映關係
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
// 遍歷已解析好的AnnotationTypeMapping範例,並找到相同註解型別的AnnotationTypeMapping接著將其封裝為MergedAnnotation
// 然後繼續下一次尋找,若還有匹配的結果,則根據選擇器從中找到更合適的結果,最終返回一個最匹配結果
for (int i = 0; i < mappings.size(); i++) {
AnnotationTypeMapping mapping = mappings.get(i);
if (isMappingForType(mapping, annotationFilter, this.requiredType)) {
// 3、嘗試建立一個合併註解
MergedAnnotation<A> candidate = TypeMappedAnnotation.createIfPossible(
mapping, source, annotation, aggregateIndex, IntrospectionFailureLogger.INFO);
// 4、若合併註解建立成功,且過濾器匹配通過
if (candidate != null && (this.predicate == null || this.predicate.test(candidate))) {
// a.合併註解是最匹配的結果
if (this.selector.isBestCandidate(candidate)) {
return candidate;
}
// b.使用選擇器從上一結果和當前結果中選擇一個權重更高的註解,做為新的結果
updateLastResult(candidate);
}
}
}
return null;
}
private void updateLastResult(MergedAnnotation<A> candidate) {
MergedAnnotation<A> lastResult = this.result;
this.result = (lastResult != null ? this.selector.select(lastResult, candidate) : candidate);
}
@Override
@Nullable
public MergedAnnotation<A> finish(@Nullable MergedAnnotation<A> result) {
return (result != null ? result : this.result);
}
}
在 MergedAnnotationFinder#process()
方法中出現了 AnnotationTypeMappings
,該型別表示一個註解與其元註解之間關聯關係 AnnotationTypeMapping
的集合。直白點說,AnnotationTypeMappings
用於描述一個註解有哪些元註解,元註解又有哪些元註解。
AnnotationTypeMapping
是整個元註解機制實現的核心,除了註解關係的對映外,它還為屬性別名等機制提供支援,這部分內容將在後文更詳細的介紹。
AnnotationTypeMappings.forAnnotationType
靜態方法用於建立一個AnnotationTypeMappings
範例:
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
// 針對可重複註解的容器快取
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
return standardRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
}
// 針對不可重複註解的容器快取
if (repeatableContainers == RepeatableContainers.none()) {
return noRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
}
// 建立一個AnnotationTypeMappings範例
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType);
}
出於減少重複的解析操作的目的,AnnotationTypeMappings
類維護了 noRepeatablesCache
和 standardRepeatablesCache
兩個 Map 集合用於儲存已經解析好的註解類和其可重複註解容器的對映關係。
最終呼叫的 AnnotationTypeMappings
範例構造方法如下:
private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
AnnotationFilter filter, Class<? extends Annotation> annotationType) {
this.repeatableContainers = repeatableContainers; // 可重複註解的容器
this.filter = filter; // 過濾
this.mappings = new ArrayList<>(); // 對映關係
addAllMappings(annotationType); // 解析當前類以及其元註解的層次結構中涉及到的全部對映關係
this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet); // 對映關係解析完後對別名的一些校驗
}
顯而易見,addAllMappings()
方法就是最關鍵的步驟,這個方法將用於將元註解的型別跟宣告元註解的資料來源進行繫結。
舉個例子,假如現在有一個註解 @A
,上面還有一個元註解 @B
,@B
上又存在一個元註解 @C
則解析流程如下:
@A
,由於其已經是根註解了,故此時資料來源為 null
,將資料來源與他的元註解 @A
封裝為一個AnnotationTypeMapping
,這裡稱為 M1
。則 M1
即為元註解 @A
與資料來源的對映;M1
,然後獲其中元註解 @A
上的元註解 @B
,然後將資料來源 M1
與 @B
再封裝為一個AnnotationTypeMapping
,這裡稱為 M2
。則 M2
即為元註解 @B
與 M1
——或者說 @A
——的對映;最終,所有的註解的對映 M1
,M2
,M3
都被新增到了 AnnotationTypeMappings
的 mapping
集合,並且 M1
和 M2
以及 M3
也按照 @A
和 @B
的關係建立了關係,就像一個 LinkedHashMap
。
具體程式碼如下:
private void addAllMappings(Class<? extends Annotation> annotationType) {
// 廣度優先遍歷註解和元註解
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null); // 1.1 新增待解析的元註解
while (!queue.isEmpty()) {
AnnotationTypeMapping mapping = queue.removeFirst();
this.mappings.add(mapping);
// 繼續解析下一層
addMetaAnnotationsToQueue(queue, mapping); // 1.2 解析的元註解
}
}
// 1.1 新增待解析的元註解
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
try {
// 將資料來源、元註解型別和元註解範例封裝為一個AnnotationTypeMapping,作為下一次處理的資料來源
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann));
}
catch (Exception ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
if (failureLogger.isEnabled()) {
failureLogger.log("Failed to introspect meta-annotation " + annotationType.getName(),
(source != null ? source.getAnnotationType() : null), ex);
}
}
}
// 1.2 解析的元註解
private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) {
// 獲取當前註解上直接宣告的元註解
Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);
for (Annotation metaAnnotation : metaAnnotations) {
// 若已經解析過了則跳過,避免「迴圈參照」
if (!isMappable(source, metaAnnotation)) {
continue;
}
// a.若當前正在解析的註解是容器註解,則將內部的可重複註解取出解析
Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(metaAnnotation);
if (repeatedAnnotations != null) {
for (Annotation repeatedAnnotation : repeatedAnnotations) {
// 1.2.1 判斷是否已經完成對映
if (!isMappable(source, repeatedAnnotation)) {
continue;
}
addIfPossible(queue, source, repeatedAnnotation);
}
}
// b.若當前正在解析的註解不是容器註解,則將直接解析
else {
addIfPossible(queue, source, metaAnnotation);
}
}
}
// 1.2.1 判斷是否已經完成對映
private boolean isMappable(AnnotationTypeMapping source, @Nullable Annotation metaAnnotation) {
return (metaAnnotation != null && !this.filter.matches(metaAnnotation) &&
!AnnotationFilter.PLAIN.matches(source.getAnnotationType()) &&
!isAlreadyMapped(source, metaAnnotation));
}
private boolean isAlreadyMapped(AnnotationTypeMapping source, Annotation metaAnnotation) {
Class<? extends Annotation> annotationType = metaAnnotation.annotationType();
// 遞迴對映表,確定這個註解型別是否在對映表的樹結構中存在
// 這個做法相當於在迴圈參照中去重
AnnotationTypeMapping mapping = source;
while (mapping != null) {
if (mapping.getAnnotationType() == annotationType) {
return true;
}
mapping = mapping.getSource();
}
return false;
}
當獲得聚合註解 MergedAnnotations
後,再通過 get
方法獲取到指定的合併註解這個過程,需要經過下述四個過程。
MergedAnnotations
中根據傳入的註解過濾器AnnotationFilter
以及一些校驗,從指定的註解或資料來源的層級結構中獲取待解析的註解;MergedAnnotationFinder
,然後再為其建立一個註解選擇器 MergedAnnotationSelector
,該處理器將用於處理上一個步驟掃描到的待解析註解;MergedAnnotationFinder
處理:
AnnotationTypeMappings
,該表用於表示一個資料來源上所有註解以及元註解之間的層級關係;AnnotationTypeMappings
會根據廣度優先遍歷待解析的註解的層級結構,然後依次以資料來源上的某個註解作為根註解,然後它和它的某個元註解的對映關係封裝為AnnotationTypeMapping
;AnnotationTypeMapping
對應的註解仍然存在元註解,就繼續一層一層的遞迴,直到整個層級結構都被轉為AnnotationTypeMapping
並且記錄到 AnnotationTypeMappings
為止,此時每個根註解對應的AnnotationTypeMappings
都維持著一個類似連結串列的介面,元註解的AnnotationTypeMapping
會持有宣告它的資料來源註解的AnnotationTypeMapping
參照;MergedAnnotationFinder
將遍歷並遞迴所有AnnotationTypeMapping
,篩選出所有匹配的AnnotationTypeMapping
,接著再將其封裝為 MergedAnnotation
,最後使用MergedAnnotationSelector
從中選擇出一個最合適的——總體來說離根註解越近越合適——作為 get
的查詢結果。由於 spring 提供了基於 @AliasFor
註解的別名機制,允許註解內部的屬性互為別名,或者與它的元註解中的屬性互為別名,這一步同樣在AnnotationTypeMapping
建立時完成,由於涉及到內容較多,因此作為單獨的一節描述。
根據@AlisaFor
作用與註解內和註解外,造成的效果可以簡單分為兩種:
其中,映象效果依賴於 MirrorSet
,而覆寫的效果依賴於 AnnotationTypeMapping
中各種 mapping
結尾的變數,這些資料結構共同維護的同註解與不同註解間的屬性對映關係,這些都在AnnotationTypeMapping
的構造方法中完成:
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
this.source = source; // 宣告當前元註解型別的資料來源
this.root = (source != null ? source.getRoot() : this); // 根節點
this.distance = (source == null ? 0 : source.getDistance() + 1); // 距離根節點的距離
this.metaTypes = merge( // 到當前元註解為止前面合併了多少元註解
source != null ? source.getMetaTypes() : null,
annotationType);
// 當前元註解與型別
this.annotationType = annotationType;
this.annotation = annotation;
// 當前元註解的屬性
this.attributes = AttributeMethods.forAnnotationType(annotationType);
// 屬性別名與相關的值快取
this.mirrorSets = new MirrorSets();
this.aliasMappings = filledIntArray(this.attributes.size());
this.conventionMappings = filledIntArray(this.attributes.size());
this.annotationValueMappings = filledIntArray(this.attributes.size());
this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];
this.aliasedBy = resolveAliasedForTargets();
// 初始化別名屬性,為所有存在別名的屬性建立MirrorSet
processAliases();
// 為當前註解內互為併名的屬性建立屬性對映
addConventionMappings();
// 為跨註解互為別名的屬性建立屬性對映
addConventionAnnotationValues();
this.synthesizable = computeSynthesizableFlag();
}
在 AnnotationTypeMapping
建立時,將會通過反射把當前的元註解全部屬性的獲取方法解析出來,然後封裝為一個聚合屬性 AttributeMethods
並賦值給同名變數,在建構函式中對應程式碼 :
this.attributes = AttributeMethods.forAnnotationType(annotationType)
AttributeMethods
內部維護了一個方法的陣列,並以此提供基於下標或方法名稱存取註解屬性的能力。
這裡尤其需要注意的是,或許是出於效能考慮,spring 在元註解屬性對映這邊的程式碼實現,幾乎全部都是依靠陣列記錄變數,然後依靠下標來傳遞參照關係。
所以這裡需要特別記住一點,在後續幾乎所有陣列的下標,都與AttributeMethods
中屬性方法的陣列下標對應,即某個屬性在屬性方法陣列中的下標 index = 1
,則後續所有相關陣列下標為 1 的位置,都與該屬性有關。
首先,spring 將AttributeMethods
中所有的帶有@AliasFor
註解的屬性方法取出,然後解析註解並生成別名屬性對映表 aliasedBy
,這一段在建構函式中對應:
this.aliasedBy = resolveAliasedForTargets();
而 resolveAliasedForTargets
對應實現如下:
private Map<Method, List<Method>> resolveAliasedForTargets() {
Map<Method, List<Method>> aliasedBy = new HashMap<>();
for (int i = 0; i < this.attributes.size(); i++) {
// 遍歷當前註解的屬性方法,並獲取其中的帶有@AliasFor的方法
Method attribute = this.attributes.get(i);
AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute, AliasFor.class);
if (aliasFor != null) {
// 獲取別名指定的註解類中的方法,並建立別名屬性 -> [屬性1]的對映集合
Method target = resolveAliasTarget(attribute, aliasFor);
aliasedBy.computeIfAbsent(target, key -> new ArrayList<>()).add(attribute);
}
}
return Collections.unmodifiableMap(aliasedBy);
}
private Method resolveAliasTarget(Method attribute, AliasFor aliasFor) {
return resolveAliasTarget(attribute, aliasFor, true);
}
resolveAliasTarget
最終將獲得@AlisaFor
註解所指定的別名方法,具體如下:
private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean checkAliasPair) {
if (StringUtils.hasText(aliasFor.value()) && StringUtils.hasText(aliasFor.attribute())) {
throw new AnnotationConfigurationException(String.format(
"In @AliasFor declared on %s, attribute 'attribute' and its alias 'value' " +
"are present with values of '%s' and '%s', but only one is permitted.",
AttributeMethods.describe(attribute), aliasFor.attribute(),
aliasFor.value()));
}
// 1、若Annotation指定的是Annotation,則認為目標就是當前註解類
Class<? extends Annotation> targetAnnotation = aliasFor.annotation();
if (targetAnnotation == Annotation.class) {
targetAnnotation = this.annotationType;
}
// 2、獲取alisaFrom#attribute,若為空則再獲取alisaFrom#value
String targetAttributeName = aliasFor.attribute();
if (!StringUtils.hasLength(targetAttributeName)) {
targetAttributeName = aliasFor.value();
}
if (!StringUtils.hasLength(targetAttributeName)) {
targetAttributeName = attribute.getName();
}
// 3、從指定類中獲得別名指定指定的註解屬性對應的方法
Method target = AttributeMethods.forAnnotationType(targetAnnotation).get(targetAttributeName);
if (target == null) {
// a.校驗是否能找到別名方法
if (targetAnnotation == this.annotationType) {
throw new AnnotationConfigurationException(String.format(
"@AliasFor declaration on %s declares an alias for '%s' which is not present.",
AttributeMethods.describe(attribute), targetAttributeName));
}
throw new AnnotationConfigurationException(String.format(
"%s is declared as an @AliasFor nonexistent %s.",
StringUtils.capitalize(AttributeMethods.describe(attribute)),
AttributeMethods.describe(targetAnnotation, targetAttributeName)));
}
// b.校驗別名與原屬性對應的方法是否不為一個方法
if (target.equals(attribute)) {
throw new AnnotationConfigurationException(String.format(
"@AliasFor declaration on %s points to itself. " +
"Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
AttributeMethods.describe(attribute)));
}
// c.校驗別名與原屬性對應的方法返回值是否一致
if (!isCompatibleReturnType(attribute.getReturnType(), target.getReturnType())) {
throw new AnnotationConfigurationException(String.format(
"Misconfigured aliases: %s and %s must declare the same return type.",
AttributeMethods.describe(attribute),
AttributeMethods.describe(target)));
}
// d.若有必要,則再校驗宣告別名方法的註解是@AlisaFor指定的註解型別
if (isAliasPair(target) && checkAliasPair) {
AliasFor targetAliasFor = target.getAnnotation(AliasFor.class);
if (targetAliasFor != null) {
Method mirror = resolveAliasTarget(target, targetAliasFor, false);
if (!mirror.equals(attribute)) {
throw new AnnotationConfigurationException(String.format(
"%s must be declared as an @AliasFor %s, not %s.",
StringUtils.capitalize(AttributeMethods.describe(target)),
AttributeMethods.describe(attribute), AttributeMethods.describe(mirror)));
}
}
}
return target;
}
在這一步,他做了以下邏輯處理:
@AlisaFor#annotation
屬性保持預設值Annotation.class
,則認為別名屬性所在的註解就是當前解析的註解;alisaFrom#attribute
同名屬性,若alisaFrom#attribute
為空則獲取alisaFrom#value
同名方法;最終,完成這一步後,將構建出以別名方法作為 key
,當前註解中對應的原始屬性的方法作為 value
的別名屬性-原始屬性對映表 aliasedBy
。
在第二步在aliasedBy
中載入了當前註解中所有別名屬性與註解中原始屬性的對映關係後,將根據此進一步組織註解屬性與別名的對映關係。此步驟對應建構函式中的程式碼片段如下:
this.mirrorSets = new MirrorSets();
this.aliasMappings = filledIntArray(this.attributes.size());
this.conventionMappings = filledIntArray(this.attributes.size());
this.annotationValueMappings = filledIntArray(this.attributes.size());
this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];
// 初始化別名屬性,為所有存在別名的屬性建立MirrorSet
processAliases();
processAliases
是直接入口:
private void processAliases() {
List<Method> aliases = new ArrayList<>();
// 遍歷當前註解中的屬性,處理屬性與其相關的別名
for (int i = 0; i < this.attributes.size(); i++) {
aliases.clear(); // 複用集合避免重複建立
aliases.add(this.attributes.get(i));
// 1.收集註解
collectAliases(aliases);
if (aliases.size() > 1) {
// 2.處理註解
processAliases(i, aliases);
}
}
}
從功能來說,這段程式碼分為採集註解和處理註解兩部分:
蒐集以當前註解屬性作為別名的子註解屬性
private void collectAliases(List<Method> aliases) {
AnnotationTypeMapping mapping = this;
while (mapping != null) {
int size = aliases.size();
for (int j = 0; j < size; j++) {
List<Method> additional = mapping.aliasedBy.get(aliases.get(j)); // 獲取以該屬性作為別名的子類屬性
if (additional != null) {
aliases.addAll(additional);
}
}
mapping = mapping.source; // 繼續向宣告當前元註解的子註解遞迴
}
}
收集註解這一步,將以當前元註解的某個屬性為根屬性,從當前元註解向子註解遞迴,並最終收集到全部直接或間接以當前根屬性作為別名的子類屬性。
比如,假如A.name
的別名是 B.name
,B.name
的別名是 C.name
,則從 C.name
開始向子註解遞迴,最終在 aliases
集合中收集到的就是 [C.name, B.name, A.name]
。
當然,加入 A.name
還存在一個別名 A.alisaName
,則實際最終在 aliases
集合中收集到的就是 [C.name, C.alisaName, B.name, A.name]
。
處理註解
處理註解的 processAliases
增體流程依然是從當前元註解遞迴向子註解進行,並且處理過程中的邏輯大體分為三部分:
aliasMappings
中記錄根註解原始屬性的下標;MirrorSet
;MirrorSet
,構建各級註解中被作為別名屬性的屬性,與呼叫時實際對應的註解屬性及子類註解範例的對映表annotationValueMappings
annotationValueSource
;private void processAliases(int attributeIndex, List<Method> aliases) {
// 1.若根註解——即最小的子註解——存在以元註解屬性作為別名的原始屬性,則以根註解屬性覆蓋元註解中的屬性,並在該元註解的成員變數`aliasMappings` 中記錄根註解原始屬性的下標;
int rootAttributeIndex = getFirstRootAttributeIndex(aliases); // 若根註解中存在以aliases任意屬性作為別名的屬性,則返回跟註解的屬性方法下標
// 從當前元註解向子註解遞迴
AnnotationTypeMapping mapping = this;
while (mapping != null) {
// 若根註解中存在以aliases任意屬性作為別名的屬性,且當前處理的註解不是根註解
// 則將當前處理的註解aliasMappings與設定為根註解中對應屬性的值
// 即使用子註解的值覆蓋元註解的值
if (rootAttributeIndex != -1 && mapping != this.root) {
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
mapping.aliasMappings[i] = rootAttributeIndex; // 在aliasMappings記錄根註解元素屬性下標
}
}
}
// 2.為各級註解中同一註解內互為別名的欄位,以及根註解中不存在的、且不同註解間互為別名的欄位建立映象對映關係表MirrorSet
mapping.mirrorSets.updateFrom(aliases);
mapping.claimedAliases.addAll(aliases);
// 3.根據MirrorSet,構建各級註解中被作為別名屬性的屬性,與呼叫時實際對應的註解屬性及子類註解範例的對映表annotationValueMappings和annotationValueSource
if (mapping.annotation != null) {
int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
this.annotationValueSource[attributeIndex] = mapping;
}
}
}
mapping = mapping.source; // 向子註解遞迴
}
}
private int getFirstRootAttributeIndex(Collection<Method> aliases) {
// 獲取根註解的屬性,若根註解中存在以aliases任意屬性作為別名的屬性,則返回跟註解的屬性方法下標
AttributeMethods rootAttributes = this.root.getAttributes();
for (int i = 0; i < rootAttributes.size(); i++) {
if (aliases.contains(rootAttributes.get(i))) {
return i;
}
}
return -1;
}
第一部分沒什麼好說的,第二與第三部分存在巢狀的邏輯,因此將在下一小結詳細介紹。
承接上文,在 processAliases
中關於根註解中不存在的屬性的對映,在元註解的邏輯中是基於 MirrorSet
實現的,同一個註解類中不同屬性的對映關係的構建過程實際上就是構建 MirrorSet
的過程:
private void processAliases(int attributeIndex, List<Method> aliases) {
// 從當前元註解向子註解遞迴
AnnotationTypeMapping mapping = this;
while (mapping != null) {
// 1.若根註解——即最小的子註解——存在以元註解屬性作為別名的原始屬性,則以根註解屬性覆蓋元註解中的屬性,並在該元註解的成員變數`aliasMappings` 中記錄根註解原始屬性的下標;
// ... ...
// 構建 MirrorSet
mapping.mirrorSets.updateFrom(aliases);
mapping.claimedAliases.addAll(aliases);
// 3.根據MirrorSet,構建各級註解中被作為別名屬性的屬性,與呼叫時實際對應的註解屬性及子類註解範例的對映表annotationValueMappings和annotationValueSource
// ... ...
mapping = mapping.source; // 向子註解遞迴
}
}
由於 AnnotationTypeMapping
本身在初始化時也一併初始化了一個 MirrorSets
範例用於管理 MirrorSet
,因此在程式碼中直接呼叫updateFrom
即可:
// code in MirrorSets
void updateFrom(Collection<Method> aliases) {
MirrorSet mirrorSet = null;
int size = 0;
int last = -1;
// 遍歷當前元註解的全部屬性
for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i);
// 若當前元註解的屬性有被作為別名,則在MirrorSets.assigned陣列中與當前屬性方法相同下標的位置設定一個MirrorSet範例
if (aliases.contains(attribute)) {
size++;
if (size > 1) {
if (mirrorSet == null) {
mirrorSet = new MirrorSet();
this.assigned[last] = mirrorSet;
}
this.assigned[i] = mirrorSet;
}
last = i;
}
}
// 引數mirrorSet,並更新集合
if (mirrorSet != null) {
mirrorSet.update();
Set<MirrorSet> unique = new LinkedHashSet<>(Arrays.asList(this.assigned));
unique.remove(null);
this.mirrorSets = unique.toArray(EMPTY_MIRROR_SETS);
}
}
updateFrom
的邏輯簡單的概況一下,就是遍歷所有的元註解對應的AnnotationTypeMapping
,然後如果每個註解中存在作為別名的屬性,則在 ``AnnotationTypeMapping.MirrorSets.assigned陣列中與該屬性方法對應的陣列下標處設定一個
MirrorSet` 範例,表示該屬性是別名屬性。
然後再呼叫 MirrorSet#update
方法:
void update() {
this.size = 0;
Arrays.fill(this.indexes, -1);
for (int i = 0; i < MirrorSets.this.assigned.length; i++) {
if (MirrorSets.this.assigned[i] == this) {
this.indexes[this.size] = i;
this.size++;
}
}
}
可見 MirrorSet
會記錄該範例在 assigned
陣列中出現的位置,然後將其記錄在 indexes
陣列中,由於 assigned
陣列與 AttributeMethod
中屬性方法陣列一一對應,因此 indexes
陣列實際就是相同註解中互為別名的屬性,互為別名的屬性通過同一個 MirrorSet
範例繫結在了一起。
仍然承接上文,繼續解析 processAliases
的第三部分邏輯,這裡實際上還是分為兩部分:
原始碼如下:
private void processAliases(int attributeIndex, List<Method> aliases) {
// 1.若根註解——即最小的子註解——存在以元註解屬性作為別名的原始屬性,則以根註解屬性覆蓋元註解中的屬性,並在該元註解的成員變數`aliasMappings` 中記錄根註解原始屬性的下標;
// ... ...
// 2.為各級註解中同一註解內互為別名的欄位,以及根註解中不存在的、且不同註解間互為別名的欄位建立映象對映關係表MirrorSet
// ... ...
// 3.根據MirrorSet,構建各級註解中被作為別名屬性的屬性,與呼叫時實際對應的註解屬性及子類註解範例的對映表annotationValueMappings和annotationValueSource
if (mapping.annotation != null) {
int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
this.annotationValueSource[attributeIndex] = mapping;
}
}
}
mapping = mapping.source; // 向子註解遞迴
}
處理同註解內互為別名的欄位
這一步主要依靠 resolve
方法完成,先呼叫了 MirrorSets
的 resolve
:
int[] resolve(@Nullable Object source, @Nullable Object annotation, ValueExtractor valueExtractor) {
// 獲取當前註解的各屬性下標,預設每個屬性都從自身取值
int[] result = new int[attributes.size()];
for (int i = 0; i < result.length; i++) {
result[i] = i;
}
// 遍歷所有MirrorSet範例
for (int i = 0; i < size(); i++) {
MirrorSet mirrorSet = get(i);
// 從同一個註解中一堆互為別名的屬性中獲取一個最終有效的屬性的方法下標,然後所有的屬性都以這個屬性的值為準
int resolved = mirrorSet.resolve(source, annotation, valueExtractor);
for (int j = 0; j < mirrorSet.size; j++) {
result[mirrorSet.indexes[j]] = resolved;
}
}
return result;
}
// 1.2.2.1 獲取別名對映 in MirrorSets
Method get(int index) {
int attributeIndex = this.indexes[index];
return attributes.get(attributeIndex);
}
然後呼叫 MirrorSet
的 resolve
:
<A> int resolve(@Nullable Object source, @Nullable A annotation, ValueExtractor valueExtractor) {
int result = -1;
Object lastValue = null; // 最近一個的有效屬性值
// 遍歷與當前註解屬性屬性互為別名的全部屬性
for (int i = 0; i < this.size; i++) {
// 獲取屬性值
Method attribute = attributes.get(this.indexes[i]);
Object value = valueExtractor.extract(attribute, annotation);
boolean isDefaultValue = (value == null ||
isEquivalentToDefaultValue(attribute, value, valueExtractor));
// 如果屬性值是預設值,或者與最後有效值相同,則記錄該屬性下標後返回
// 以此類推,如果一組互為別名的屬性全部都是預設值,則前面的屬性——即離根註解最近的——的預設值會作為最終有效值
if (isDefaultValue || ObjectUtils.nullSafeEquals(lastValue, value)) {
if (result == -1) {
result = this.indexes[i];
}
continue;
}
// 如果屬性值不是預設值,並且與最近一個的有效屬性值不同, 則丟擲異常
// 這裡實際要求一組互為別名的屬性中,只允許一個屬性的值是非預設值
if (lastValue != null && !ObjectUtils.nullSafeEquals(lastValue, value)) {
String on = (source != null) ? " declared on " + source : "";
throw new AnnotationConfigurationException(String.format(
"Different @AliasFor mirror values for annotation [%s]%s; attribute '%s' " +
"and its alias '%s' are declared with values of [%s] and [%s].",
getAnnotationType().getName(), on,
attributes.get(result).getName(),
attribute.getName(),
ObjectUtils.nullSafeToString(lastValue),
ObjectUtils.nullSafeToString(value)));
}
result = this.indexes[i];
lastValue = value;
}
return result;
}
這裡的邏輯應該是比較清晰的,首先,如果同一個註解記憶體在多個互為別名的屬性,則需要有一個唯一有效的最終屬性,所有互為別名的屬性應當以這個最終屬性的值為準。
對應到程式碼中,則就是通過遍歷 MirrorSet
中互為別名的欄位,然後根據下述規則找到最終屬性:
然後返回這個最終屬性的下標。
將 MirrorSets
中的全部 MirrorSet
按上述過程處理後,我們會得到這個註解中每個屬性的最終屬性,對應到程式碼:
int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
實際上得到的 resolvedMirrors
就是與註解中屬性方法對應的最終屬性集合。
我們舉個例子,假如現在有 A,B,C,D,E 五個屬性,其中 A 和 B、C 和 D 互為別名,則經過 MirrorSets#resolve
方法最終得到的 resolvedMirrors
如下圖:
把resolvedMirrors
翻譯一下,就是 A 和 B 取值時都取 A 的值,C 和 D 取值時都取 C 的值,而 E 取值照樣取 E 的值。
處理不同註解中互為別名的屬性
理解了 resolvedMirrors
是個什麼玩意後,我們繼續回到 processAliases
方法的程式碼:
// 獲取當前元註解中互為別名的屬性對應的實際取值的最終屬性
// aliases表示從元註解到子註解中所有跨註解互為別名的屬性
int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
for (int i = 0; i < mapping.attributes.size(); i++) {
// 若當前註解中存在別名屬性 i
if (aliases.contains(mapping.attributes.get(i))) {
// 則該屬性取值時,從annotationValueSource[i]獲取目標註解,然後再從目標註解的resolvedMirrors[i]屬性獲取對應的值
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
this.annotationValueSource[attributeIndex] = mapping;
}
}
這裡的邏輯也很清晰,假如子註解 @A
的 x 屬性是其元註解 @B
的屬性 y 的別名時,當我們獲取 @B.y()
,則實際到的是 @A.x()
。
回到程式碼中,為了實現這個效果,這裡通過下標 i
表示別名屬性,然後再在 annotationValueSource[i]
記錄的實際取值的註解範例,接著又在 annotationValueMappings[i]
記錄了要從實際取值的註解範例中的那個屬性取值。
舉個例子,假如現在有註解 @B
和其元註解 @A
,@A
的屬性 X
是元註解 @B
屬性 C
的別名,則從 @A
開始解析後, 最終我們可以在 A.class
中得到下圖結果:
由於 @A.X()
存在跨註解別名 @B.C()
,則 X
的對應 AttributeMethods[0]
,則其下標0
對應的 annotationValueSource[0]
存放的就是 @B
的範例,然後 annotationValueMappings[0]
存放的就是 @B.C()
在 @B
中的方法索引 1
。
現在,通過 annotationValueMappings
,annotationValueSource
以及 AttributeMethods
這三個成員變數,任何一個使用@AlisaFor
註解設定了別名的屬性都可以找到真正對應的值。
使用 @AlisaFor
註解在 spring 中稱為顯式別名,對應的還有一個隱式別名,也就是只要子註解和元註解的屬性名稱相同,則就會使用子註解的屬性值覆蓋元註解的屬性值,即子註解的屬性會強制作為元註解屬性的別名。
這個隱式對映的優先順序高於顯式對映,換而言之,如果你在子註解為一個元註解通過@AlisaFor
指定了顯式別名,但是偏偏子註解中海油一個屬性與這個元註解中的屬性同名,則最終取值時,優先取子註解中的同名欄位,而不是通過 @AlisaFor
指定的別名欄位。
在 AnnotationTypeMapping
的建構函式中,共分為兩步:
// 為元註解與根註解同名的屬性強制設定別名
addConventionMappings();
// 為元註解與非根註解的子註解的同名的屬性設定別名
addConventionAnnotationValues();
為元註解與根註解同名的屬性強制設定別名
這一步將遍歷當前註解中的屬性,然後判斷是否在根註解中存在同名屬性,若存則直接將 conventionMappings
中對應下標的位置設定為根註解對應屬性的下標。
private void addConventionMappings() {
if (this.distance == 0) {
return;
}
AttributeMethods rootAttributes = this.root.getAttributes();
int[] mappings = this.conventionMappings;
for (int i = 0; i < mappings.length; i++) {
// 遍歷當前註解的屬性,判斷是否在根註解存在
String name = this.attributes.get(i).getName();
int mapped = rootAttributes.indexOf(name);
// 若存在,並且該屬性不為「value」
MirrorSet mirrors = getMirrorSets().getAssigned(i);
if (!MergedAnnotation.VALUE.equals(name) && mapped != -1) {
mappings[i] = mapped;
// 若該屬性還有別名,則讓該屬性和全部別名屬性都從根註解取值
if (mirrors != null) {
for (int j = 0; j < mirrors.size(); j++) {
mappings[mirrors.getAttributeIndex(j)] = mapped;
}
}
}
}
}
為元註解與非根註解的子註解的同名的屬性設定別名
這一步將從當前註解向不包括根註解在內的子註解遞迴:
annotationValueSource
和annotationValueMappings
設定為該子註解和該註解中同名屬性的方法下標;private void addConventionAnnotationValues() {
// 遍歷當前註解的全部屬性
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
boolean isValueAttribute = MergedAnnotation.VALUE.equals(attribute.getName());
AnnotationTypeMapping mapping = this;
// 從當前註解向非根註解的子註解遞迴
while (mapping != null && mapping.distance > 0) {
// 若當前方法在子註解中存在,則將annotationValueMappings和annotationValueSource替換為該子註解和子註解的屬性
// 由於替換前會比較annotationValueSource中註解距離根註解的距離,
// 所以之前設定的根註解屬性不受影響,因為跟註解距離為0,優先順序總是最高的
int mapped = mapping.getAttributes().indexOf(attribute.getName());
if (mapped != -1 && isBetterConventionAnnotationValue(i, isValueAttribute, mapping)) {
this.annotationValueMappings[i] = mapped;
this.annotationValueSource[i] = mapping;
}
mapping = mapping.source;
}
}
}
private boolean isBetterConventionAnnotationValue(int index, boolean isValueAttribute,
AnnotationTypeMapping mapping) {
if (this.annotationValueMappings[index] == -1) {
return true;
}
int existingDistance = this.annotationValueSource[index].distance;
return !isValueAttribute && existingDistance > mapping.distance;
}
設定當前註解的可合成標記
這一步很簡單,就是判斷當前註解是否可以被用於合成 MergedAnnotation
,依據是三點:
private boolean computeSynthesizableFlag() {
// 是否有屬性存在@AlisaFor註解
for (int index : this.aliasMappings) {
if (index != -1) {
return true;
}
}
if (!this.aliasedBy.isEmpty()) {
return true;
}
// 是否有屬性被子註解的別名覆蓋
for (int index : this.conventionMappings) {
if (index != -1) {
return true;
}
}
// 是否存在註解型別的屬性欄位
if (getAttributes().hasNestedAnnotation()) {
AttributeMethods attributeMethods = getAttributes();
for (int i = 0; i < attributeMethods.size(); i++) {
Method method = attributeMethods.get(i);
Class<?> type = method.getReturnType();
if (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation())) {
Class<? extends Annotation> annotationType =
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.getComponentType());
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
if (mapping.isSynthesizable()) {
return true;
}
}
}
}
return false;
}
屬性對映實際上是在 AnnotationTypeMapping
被建立時完成的,這過程分為下述五個步驟:
AttributeMethods
物件,該物件獲取並通過下標來存取屬性方法;AnnotationTypeMapping
將會遍歷 AttributeMethods
中的方法,若屬性方法上存在 @AliasFor
註解,則會解析註解,並通過反射獲取註解指定的類上的別名屬性對應的方法,並與當前註解中的對應屬性方法一併新增到名為 aliasBy
的 Map 集合中建立別名屬性和當前註解屬性的對映關係;aliasBy
中的別名屬性,然後拿著這個屬性繼續向子註解遞迴,一直到將子類中直接或間接作為該屬性別名的屬性全部收集完畢;MirrorSet
,然後從中選擇出最後實際用於取值的最終屬性,MirrorSet
關聯的一組互為別名的屬性取值時都從該最終屬性獲取值;annotationValueSource
和 annotationValueMappings
中與該屬性在 AttributeMethods
中下標對應的位置,記錄要呼叫哪個註解範例和該要在註解範例中最終呼叫的屬性;@AlisaFor
宣告的顯示別名後,將會為子註解與元註解中的同名屬性設定隱式別名:
conventionMappings
中;annotationValueSource
和 annotationValueMappings
,分別替換為存在同名屬性,且距離根註解最近的非根子註解與該子註解同名屬性的下標,;這一步對應的 MergedAnnotation.synthesize()
方法,藉助 JDK 的動態代理根據 MergedAnnotation
生成對應的註解範例:
@Override
public Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException {
return (condition.test(this) ? Optional.of(synthesize()) : Optional.empty());
}
@Override
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;
}
@Override
@SuppressWarnings("unchecked")
protected A createSynthesized() {
// 如果查詢的型別本身就已經是代理類了,就返回註解返回它本身
if (getType().isInstance(this.rootAttributes) && !isSynthesizable()) {
return (A) this.rootAttributes;
}
// 使用動態代理生成代理類
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
}
static <A extends Annotation> A createProxy(MergedAnnotation<A> annotation, Class<A> type) {
ClassLoader classLoader = type.getClassLoader();
InvocationHandler handler = new SynthesizedMergedAnnotationInvocationHandler<>(annotation, type);
// 為註解通過動態代理生成物件,生成的代理類實現SynthesizedAnnotation.class介面作為標識
Class<?>[] interfaces = isVisible(classLoader, SynthesizedAnnotation.class) ?
new Class<?>[] {type, SynthesizedAnnotation.class} : new Class<?>[] {type};
return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
方法代理
SynthesizedMergedAnnotationInvocationHandler
本身實現了 InvocationHandler
,當呼叫動態代理生成的註解範例的屬性方法時,將會通過 invoke
方法獲得代理的屬性方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (ReflectionUtils.isEqualsMethod(method)) { // 代理equals方法
return annotationEquals(args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) { // 代理hashCode方法
return annotationHashCode();
}
if (ReflectionUtils.isToStringMethod(method)) { // 代理toString方法
return annotationToString();
}
if (isAnnotationTypeMethod(method)) { // 代理annotationType方法
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));
}
這裡 SynthesizedMergedAnnotationInvocationHandler
分別提供了equals
、hashCode
、toString
、annotationType
、註解的屬性方法等五個不同類的代理方法。
獲取屬性值
這裡我們重點關注代理類是如何通過 getAttributeValue
獲取註解的屬性的。
private Object getAttributeValue(Method method) {
// 快取屬性值
Object value = this.valueCache.computeIfAbsent(method.getName(), attributeName -> {
// 獲取方法返回值型別
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
// 根據方法名與方法返回值確定要獲取的屬性
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
的 getAttributeValue
方法,這裡我們以其實現類 TypeMappedAnnotation
為例:
@Override
@Nullable
protected <T> T getAttributeValue(String attributeName, Class<T> type) {
int attributeIndex = getAttributeIndex(attributeName, false); // 1.通過屬性名獲取屬性在AttributeMethods中的下標
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null); // 2.根據下標獲取屬性值
}
@Nullable
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); // 型別轉換
}
@Nullable
private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
AnnotationTypeMapping mapping = this.mapping; // 預設從當前註解開始獲取對應都屬性
if (this.useMergedValues) {
// 1.嘗試從根註解中獲取值
// 1.1.a 若根註解中存在@AlisaFor顯式指定別名屬性,則獲取該屬性下標
int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
if (mappedIndex == -1 && useConventionMapping) {
// 1.1.b 若根註解中不存在@AlisaFor顯式指定別名屬性,則嘗試查詢作為隱式別名的同名屬性的下標
mappedIndex = this.mapping.getConventionMapping(attributeIndex);
}
// 1.2 若根註解中存在作為元註解別名的屬性,則從跟註解中獲取對應的屬性
if (mappedIndex != -1) {
mapping = mapping.getRoot();
attributeIndex = mappedIndex;
}
}
// 2.如果當前註解記憶體在互為別名的屬性,則從對映關係中獲取實際用於取值的最終屬性
if (!forMirrorResolution) {
attributeIndex =
(mapping.getDistance() != 0 ? this.resolvedMirrors : this.resolvedRootMirrors)[attributeIndex];
}
// 3. 根據下標從註解中獲取屬性值
if (attributeIndex == -1) {
return null;
}
// 3.a 從根註解中取值
if (mapping.getDistance() == 0) {
Method attribute = mapping.getAttributes().get(attributeIndex);
Object result = this.valueExtractor.extract(attribute, this.rootAttributes);
return (result != null ? result : attribute.getDefaultValue());
}
// 3.b 從元註解中取值
return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
}
@Nullable
private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorResolution) {
Object value = null;
if (this.useMergedValues || forMirrorResolution) {
// 根據該註解的屬性對映獲取值
// 即從annotationValueSource獲取對應註解範例,然後再從annotationValueMappings中獲取該註解對應的屬性下標
// 最終返回撥用結果
value = this.mapping.getMappedAnnotationValue(attributeIndex, forMirrorResolution);
}
// 如果根據屬性對映獲取到的值為null,再嘗試直接呼叫本身
if (value == null) {
Method attribute = this.mapping.getAttributes().get(attributeIndex);
value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
}
return value;
}
這邊的邏輯很清晰,即算上別名屬性的處理後共分為三步:
AnnotationTypeMapping.root
和 AnnotationTypeMapping.conventionMapping
獲取;AnnotationTypeMapping.annotationValueSource
和 AnnotationTypeMapping.annotationValueMappings
獲取;當我們希望通過一個已經組裝好的 MergedAnnotation
中獲取在註解層級中存在的某個特定的註解時,spring 會通過 JDK 代理將該 MergedAnnotation
變為介面代理類範例,這個代理類實現了我們指定的註解對應的介面。
而當我們向正常的註解那樣去獲取註解的屬性時,實際上代理類會將方法改為通過 MergedAnnotation#getAttribute
實現,該實現基於構建 MergedAnnotation
的 AnnotationTypeMapping
範例,它將根據屬性名獲取對應的屬性在 AttributeMethods
中的下標,然後根據下標以及之前解析得到的各種屬性對映關係,確定最終要呼叫哪個註解範例的哪個屬性方法,然後最終再返回改屬性值;
回顧整個流程,當我們打算從某個元素上獲取 spring 所支援的元註解時,大體步驟如下:
從指定元素上解析直接宣告的註解,然後聚合為 MergedAnnotations
,然後呼叫 MergedAnnotations#get
方法,嘗試獲取一個 MergedAnnotation
;
此時,針對元素上的每一個根註解,都會按廣度優先掃描並解析註解和其元註解間的對映關係,並將每一個註解都封裝為 AnnotationTypeMapping
,
並通過成員變數 root
與 source
分別維護對子註解和根註解的參照;
AnnotationTypeMapping
建立時,會先解析註解屬性與屬性間的對映關係,步驟包括:
將註解中的屬性封裝為 AttributeMethods
,此後註解中的屬性即對應AttributeMethods
中的方法下標;
解析帶有 @AlisaFor
註解的屬性,然後將收集在名為 alisaBy
的 Map 集合變數中,
再解析 alisaBy
變數中每一個存在別名的方法,從元註解遞迴到根註解,獲得註解間存在直接或間接別名關係的屬性,
同一註解內的屬性,通過 MirrorSet
建立屬性在AttributeMethods
中對應下標的對映關係,全部互為別名的屬性最終聚合為 MirrorSets
。
遍歷 MirrorSets
中的 MirrorSet
,然後最終從每一組互為別名的屬性中,選擇出其中最終用於取值的最終屬性;
在非根子註解中存在別名——包括@AlisaFor
指定或屬性名相同——的元註解屬性,通過 annotationValueSource
和 annotationValueMappings
陣列在對應元註解屬性的下標處,記錄子註解與子註解指定的別名屬性下標;
在根註解中存在對應同名屬性的元註解屬性,通過 conventionMappings
陣列在對應元註解屬性的下標處,記錄根註解指定的別名屬性下標;
根註解與其元註解都解析為 AnnotationTypeMapping
後,以根註解為單位將 AnnotationTypeMapping
聚合為 AnnotationTypeMappings
;
根據要獲取的註解型別,從 AnnotationTypeMappings
中篩選出對應的一批 AnnotationTypeMapping
,然後將其全部轉為 MergedAnnotation
,再使用選擇器 MergedAnnotationSelector
從這一批 MergedAnnotation
選擇出最終——一般是離根註解最近的那個——的結果;
呼叫 MergedAnnotation#synthesize
方法,藉助動態代理,生成指定註解型別的代理範例,此時即獲取到了所需的「元註解」;
當從代理範例中獲取屬性值時,對應的方法會被代理到 MergedAnnotation#getAttributeValue
方法,該方法將根據屬性名從 MergedAnnotation
對應的 AnnotationTypeMapping
的 AttributeMethods
得到指定屬性的下標,然後根據下標再從 AnnotationTypeMapping
中獲取真正的屬性值:
conventionMappings
陣列對應位置存放的根註解屬性下標,然後從根註解中獲取對應屬性值;annotationValueSource
和 annotationValueMappings
陣列存放的對應別名註解和要獲取的別名註解屬性下標,最後返回該別名註解中的對應別名屬性;