Spring - 註解解析器

Spring中使用了大量自定義的註解,如果通過我們自定義的註解解析器獲取這些註解的值可能達不到預想的效果,必須使用Spring的AnnotationUtils類提供的方法來獲取才能正確解析。


一、自定義註解解析器解析Spring中的@AliasFor註解

@AliasFor註解是Spring中自定義的設置方法設置別名的註解,使用方式看下面例子。

1. 自定義註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Af {

    /**
     * 給屬性設置別名,要求:兩個屬性返回值類型一樣,默認值一樣,並且定義至少兩個屬性
     */
    @AliasFor("attribute")
    String value() default "";

    @AliasFor("value")
    String attribute() default "";
}

2. 使用註解

創建要使用該註解的業務類,並且給註解@Af的兩個屬性設置不一樣的值。

@Service
@Af(value = "aa", attribute = "bb")
public class AliasForServiceImpl implements AliasForService{

}

3. 創建測試類

使用Class類提供的getAnnotation()方法,獲取標註類上的註解實例,並獲取給註解設置的值。

@Slf4j
public class AnnotationTest {

    @Test
    public void aliasForTest() {
        Af af = AliasForServiceImpl.class.getAnnotation(Af.class);
        String res = String.format("AliasForServiceImpl Annotation " + "Af.value = [%s], Af.attribute = [%s]", af.value(), af.attribute());
        log.error(res);
    }
}

這裏使用了@Slf4j註解,這樣我們在某個類中使用log輸出日誌的時候,就可以不用顯示的定義log了,直接使用log.info()或其他輸出級別。相當於:

public class LogExample {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

}

輸出結果

09:30:00.873 [main] ERROR annotation.AnnotationTest - Class#getAnnotation(clazz) AliasForServiceImpl Annotation Af.value = [aa], Af.attribute = [bb]

我們看到給 @Af 的兩個屬性設置了不一樣的值,執行卻沒有報錯。原因是@AliasFor是Spring中自定義的註解,想要正確的使用該註解,就必須使用Spring提供的AnnotationUtils.java註解工具類獲取解析註解。

4. 使用Spring的AnnotationUtil獲取註解並輸出結果

@Test
public void annotationUtilsTest() {
    Af af = AnnotationUtils.getAnnotation(AliasForServiceImpl.class, Af.class);
    String res = String.format("AnnotationUtils#getAnnotation(targetClazz, AnnotationClazz) " +
            "AliasForServiceImpl Annotation " +
            "Af.value = [%s], Af.attribute = [%s]", af.value(), af.attribute());
    log.error(res);

}

輸出結果:

org.springframework.core.annotation.AnnotationConfigurationException: In annotation [com.komatsukat.spring.boot.annotation.Af] declared on class com.komatsukat.spring.boot.service.impl.AliasForServiceImpl and synthesized from [@com.komatsukat.spring.boot.annotation.Af(value=aa, attribute=bb)], attribute 'value' and its alias 'attribute' are present with values of [aa] and [bb], but only one is permitted.

    at org.springframework.core.annotation.AbstractAliasAwareAnnotationAttributeExtractor.getAttributeValue(AbstractAliasAwareAnnotationAttributeExtractor.java:103)
    at org.springframework.core.annotation.SynthesizedAnnotationInvocationHandler.getAttributeValue(SynthesizedAnnotationInvocationHandler.java:91)
    at org.springframework.core.annotation.SynthesizedAnnotationInvocationHandler.invoke(SynthesizedAnnotationInvocationHandler.java:80)
    at com.sun.proxy.$Proxy8.value(Unknown Source)

at annotation.AnnotationTest.annotationUtilsTest(AnnotationTest.java:32)

如願報錯,提示我們 “aa” 和 “bb”只能存在一個,我們再修改註解標註類的註解值:

@Af(value = "aa", attribute = "aa") 或者 @Af(value = "aa") 或者 @Af(attribute = "aa")

運行看結果,正確解析:

09:37:50.039 [main] ERROR annotation.AnnotationTest - AnnotationUtils#getAnnotation(targetClazz, AnnotationClazz) AliasForServiceImpl Annotation Af.value = [aa], Af.attribute = [aa]

5. 別名傳遞

Spring中很多地方用到了@AliasFor註解及別名傳遞,理解其原理對於理解Spring源碼有很大幫助,下面我們看下Spring源碼。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface ContextConfiguration {

    @AliasFor(value = "locations")
    String value() default "";

    @AliasFor(value = "value")
    String locations() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@ContextConfiguration
public @interface AfAlias {

    /**
     * groovyScripts 與 xmlFiles 互爲別名,隱試別名方式
     */
    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String groovyScripts() default "";

    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String xmlFiles() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@AfAlias
public @interface Af {

    /**
     *  attribute 與 value 互爲別名,因爲 groovyScripts 也是locations的別名,這是別名傳遞
     */
    @AliasFor(value = "groovyScripts", annotation = AfAlias.class)
    String attribute() default "";

    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String value() default "";
}

二、AnnotationUtils.getAnnotation(targetClazz, AnnotationClazz) 源碼解析

1. getAnnotation(AliasForServiceImpl.class, Af.class)

@Nullable
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
    try {
        A annotation = annotatedElement.getAnnotation(annotationType);
        if (annotation == null) {
            for (Annotation metaAnn : annotatedElement.getAnnotations()) {
                annotation = metaAnn.annotationType().getAnnotation(annotationType);
                if (annotation != null) {
                    break;
                }
            }
        }
        return (annotation != null ? synthesizeAnnotation(annotation, annotatedElement) : null);
    }
    catch (Throwable ex) {
        handleIntrospectionFailure(annotatedElement, ex);
        return null;
    }
}

首先通過AliasForServiceImpl.class對象的getAnnotation(annotationType) 獲取註解實例,如果獲取不到,則獲取該class類上所有的註解,通過註解實例獲取到註解類class:metaAnn.annotationType(),通過該註解類class獲取註解實例:getAnnotation(annotationType)。如果獲取到註解,會將該註解實例進行組合處理後返回(代理)。返回匹配到的第一個註解。

涉及到的知識點:
@Nullable:公共的Spring註解,相同環境下,被標註的元素可以爲Null,可以標註方法參數,方法返回值及屬性,重載方法(@OverRide)需要重複標註。

2. synthesizeAnnotation(annotation, annotatedElement)

將參數中傳入的註解annotation(Af.class),通過動態代理返回一個特殊處理過的註解對象。
如果annotation可組合的,則返回組合後的註解對象;如果爲null則返回null;其他情況不做處理,返回原註解。

看下面組合註解的代碼:

public static <A extends Annotation> A synthesizeAnnotation(
    A annotation, @Nullable AnnotatedElement annotatedElement) {

    return synthesizeAnnotation(annotation, (Object) annotatedElement);
}

@SuppressWarnings("unchecked")
static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) {
    if (annotation instanceof SynthesizedAnnotation) {
        return annotation;
    }

    Class<? extends Annotation> annotationType = annotation.annotationType();
    if (!isSynthesizable(annotationType)) {
        return annotation;
    }

    DefaultAnnotationAttributeExtractor attributeExtractor =
        new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
    InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);

    // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a
    // synthesizable annotation before (which needs to declare @AliasFor from the same package)
    Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};
    return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
}

開始分析上面代碼執行過程。

2.1. 註解是已組合類型的直接返回

if (annotation instanceof SynthesizedAnnotation) {
    return annotation;
}

2.2. 判斷註解是否可組合

如果註解是不可組合的,則返回該註解對象。

Class<? extends Annotation> annotationType = annotation.annotationType();
if (!isSynthesizable(annotationType)) {
    return annotation;

}

2.3. 判斷是否可組合的代碼如下

private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
    Boolean synthesizable = synthesizableCache.get(annotationType);
    if (synthesizable != null) {
        return synthesizable;
    }

    synthesizable = Boolean.FALSE;
    for (Method attribute : getAttributeMethods(annotationType)) {
        if (!getAttributeAliasNames(attribute).isEmpty()) {
            synthesizable = Boolean.TRUE;
            break;
        }
        Class<?> returnType = attribute.getReturnType();
        if (Annotation[].class.isAssignableFrom(returnType)) {
            Class<? extends Annotation> nestedAnnotationType =
                (Class<? extends Annotation>) returnType.getComponentType();
            if (isSynthesizable(nestedAnnotationType)) {
                synthesizable = Boolean.TRUE;
                break;
            }
        }
        else if (Annotation.class.isAssignableFrom(returnType)) {
            Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType;
            if (isSynthesizable(nestedAnnotationType)) {
                synthesizable = Boolean.TRUE;
                break;
            }
        }
    }

    synthesizableCache.put(annotationType, synthesizable);
    return synthesizable;
}

下面詳細分析是如何判斷可組合的。

2.3.1. 獲取緩存中的synthesizable

首先以註解類型annotationType做爲key從緩存synthesizableCache中獲取值:Boolean synthesizable,如果緩存中存在該值,則返回。

synthesizable如果爲false,則不可組合。

該緩存synthesizableCache的定義如下,初始化長度爲256(2^8):

private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache = new ConcurrentReferenceHashMap<>(256);

ConcurrentReferenceHashMap是Spring自己實現的Map結構,實現原理這裏不分析,其定義如下:

public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
    …

}
2.3.2. 緩存無synthesizable時,獲取synthesizable

如果緩存synthesizableCache 中沒有annotationType類型的值,則獲取該註解annotationType類裏所有方法(本類,接口中的所有方法,不包括繼承類的方法)的Method對象;

然後判斷如果mehtod爲屬性方法,則將方法的method設置爲method.setAccessible(true),即在使用該mehod對象時取消Java語言訪問檢查。

最後將該annotationType類中所有是屬性方法的method對象保存至緩存Map attributeMethodsCache中,key爲註解的class類型annotationType。

代碼實現如下:

List<Method> methodList = getAttributeMethods(annotationType); 

// 獲取屬性方法的方法
static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
    List<Method> methods = attributeMethodsCache.get(annotationType);
    if (methods != null) {
        return methods;
    }

    methods = new ArrayList<>();
    for (Method method : annotationType.getDeclaredMethods()) {
        if (isAttributeMethod(method)) {
            ReflectionUtils.makeAccessible(method);
            methods.add(method);
        }
    }

    attributeMethodsCache.put(annotationType, methods);
    return methods;

}

屬性方法的條件是參數爲空,返回值不爲空。代碼如下:

static boolean isAttributeMethod(@Nullable Method method) {
    return (method != null && method.getParameterCount() == 0 && method.getReturnType() != void.class);
}

Spring對設置取消Java語言訪問檢查方法做了包裝,本類實現方法及接口聲明的方法都不爲public,並且方法method對象需要訪問檢查的才設置爲true。

public static void makeAccessible(Method method) {
    // getModifiers() 獲取修飾符
    if ((!Modifier.isPublic(method.getModifiers()) ||
        !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
        method.setAccessible(true);
    }
}

緩存Map attributeMethodsCache定義:

private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache = new ConcurrentReferenceHashMap<>(256);
2.3.3. 判斷是否可組合

上面獲取到所有屬性method對象後,循環判斷只要存在滿足下面三個條件之一的method,就認爲此註解類型annotationType是可組合的。最後將annotationType作爲key,是否可以組合synthesizable(true/false)作爲value,存入緩存synthesizableCache。

循環判斷代碼如下:

for (Method attribute : getAttributeMethods(annotationType)) {
    if (!getAttributeAliasNames(attribute).isEmpty()) {
        synthesizable = Boolean.TRUE;
        break;
    }
    Class<?> returnType = attribute.getReturnType();
    if (Annotation[].class.isAssignableFrom(returnType)) {
        Class<? extends Annotation> nestedAnnotationType =
            (Class<? extends Annotation>) returnType.getComponentType();
        if (isSynthesizable(nestedAnnotationType)) {
            synthesizable = Boolean.TRUE;
            break;
        }
    }
    else if (Annotation.class.isAssignableFrom(returnType)) {
        Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType;
        if (isSynthesizable(nestedAnnotationType)) {
            synthesizable = Boolean.TRUE;
            break;
        }
    }
}
3.3.3.1. 第一個判斷條件分析

第一個條件調用下面的方法,該方法中先獲取屬性方法method的別名描述器,如果能獲取到並且屬性的別名names不爲空,則認爲該註解可組合。

static List<String> getAttributeAliasNames(Method attribute) {
    AliasDescriptor descriptor = AliasDescriptor.from(attribute);
    return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.<String> emptyList());
}

from方法在AnnotationUtils的內部類AliasDescriptor中定義:

@Nullable
public static AliasDescriptor from(Method attribute) {
    AliasDescriptor descriptor = aliasDescriptorCache.get(attribute);
    if (descriptor != null) {
        return descriptor;
    }

    // method上如果沒有@AliasFor註解,返回null
    AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
    if (aliasFor == null) {
        return null;
    }

    descriptor = new AliasDescriptor(attribute, aliasFor);
    descriptor.validate();
    aliasDescriptorCache.put(attribute, descriptor);
    return descriptor;
}

創建別名描述器對象,記錄sourceMethod自身一些屬性:name,所在類class,別名name,別名註解類型,別名對應的method對象,是否成對。(每個method都有自己的別名描述器)

private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {

    // 返回此Method所屬的類或接口的Class對象
    Class<?> declaringClass = sourceAttribute.getDeclaringClass();

    this.sourceAttribute = sourceAttribute;
    this.sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
    this.sourceAttributeName = sourceAttribute.getName();

    /**
    * sourceMethod上的@AliasFor設置的註解類型是Annotation.class(默認)或annotationType(sourceMethod所在類class),
    * 則取sourceMethod所在類的annotationType,否則就是@AliasFor設置的註解類型
    **/ 
    this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ? this.sourceAnnotationType : aliasFor.annotation());
    this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);

    /**
    * 別名名稱不能指向它自己
    * @AliasFor(value = "attribute")
    * String attribute() default "";
    **/
    if (this.aliasedAnnotationType == this.sourceAnnotationType && this.aliasedAttributeName.equals(this.sourceAttributeName)) {
        String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
            "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
            sourceAttribute.getName(), declaringClass.getName());
        throw new AnnotationConfigurationException(msg);
    }

    try {
        // 獲取別名的method對象
        this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
    } catch (NoSuchMethodException ex) {
        String msg = String.format(
            "Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].",
            this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
            this.aliasedAnnotationType.getName());
        throw new AnnotationConfigurationException(msg, ex);
    }

    // @AliasFor設置的註解類型是sourceMethod所在類的類型或者Annotation.class類型時爲true,即成對。
    this.isAliasPair = (this.sourceAnnotationType == this.aliasedAnnotationType);
}

獲取sourceMethod的別名名稱,取@AliasFor設置的attibute或者value的屬性值,如果沒有設置則返回該sourceMethod的name。優先級:attibute > value > sourceMethod.getName()。代碼如下:

private String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
    String attributeName = aliasFor.attribute();
    String value = aliasFor.value();
    boolean attributeDeclared = StringUtils.hasText(attributeName);
    boolean valueDeclared = StringUtils.hasText(value);

    /**
    * Ensure user did not declare both 'value' and 'attribute' in @AliasFor,
    * @AliasFor(value = "attribute", attribute = "attribute")
    * String attribute() default "";
    * 
    * method上的@AliasFor不能同時設置value和attribute
    **/
    if (attributeDeclared && valueDeclared) {
        String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " +
            "and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
            attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value);
        throw new AnnotationConfigurationException(msg);
    }

    attributeName = (attributeDeclared ? attributeName : value);
    return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName());
}

然後別名描述器調用validate()校驗該別名描述器是否合法:

// 參考例子,例子是爲了更容易更好的理解源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Configuration
public @interface Af {

    @AliasFor(value = "attribute")
    String value() default "";

    @AliasFor(value = "value", annotation = Configuration.class)
    String attribute() default "";
}

// new AnnotationUtils.from(method).validate()
private void validate() {
    // 如果在sourceMethod上設置的別名註解不是該Annotation.class及Af.class,則必須在該註解類上進行聲明。
    if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
        String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " +
            "an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.",
            this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
            this.aliasedAnnotationType.getName());
        throw new AnnotationConfigurationException(msg);
    }

    if (this.isAliasPair) {
        // 獲取別名屬性上的@AliasFor註解對象,別名屬性上必須也有@AliasFor註解
        AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
        if (mirrorAliasFor == null) {
            String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].",
                this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
            throw new AnnotationConfigurationException(msg);
        }

    // 別名屬性上設置的別名名稱要和sourceMethod上設置的別名名稱一樣,即互爲別名。
    String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute);
    if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
        String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
            this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
            mirrorAliasedAttributeName);
        throw new AnnotationConfigurationException(msg);
    }
    }

    /**
    * 校驗互爲別名的屬性的返回值類型必須一樣。返回值不能爲空,也不可能爲空,因爲編譯的時候會校驗,void 和 default "" 互斥。
    * 如果返回值類型是數組類型,判斷數組的類型也要一樣,如下:
    *  String[].class.getComponentType() = class;java.lang.String; 
    *  int[].class.getComponentType() = int。
    * 也就是說sourceReturnType = int, aliasedReturnType = int[]時,整個條件爲false,
    * 但是sourceReturnType = int[], aliasedReturnType = int,則爲true,還是會判斷出返回值不一樣的情況
    * */
    Class<?> returnType = this.sourceAttribute.getReturnType();
    Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
    if (returnType != aliasedReturnType &&
        (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
        String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
            "and attribute '%s' in annotation [%s] must declare the same return type.",
            this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
            this.aliasedAnnotationType.getName());
        throw new AnnotationConfigurationException(msg);
    }

    // 校驗默認值
    if (this.isAliasPair) {
        validateDefaultValueConfiguration(this.aliasedAttribute);
    }

}

如果在soruceMethod上設置了別名及別名註解不是Annotation.class類型,那麼必須在該註解類上進行聲明,表明該註解類的屬性具有別名註解的功能。同時將標註的註解及該類組合作爲緩存key進行緩存。

public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
    @Nullable Class<? extends Annotation> metaAnnotationType) {

    Assert.notNull(annotationType, "Annotation type must not be null");
    if (metaAnnotationType == null) {
        return false;
    }

    // 創建註解緩存的key,用於緩存註解,即上例中的Af註解
    AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
    Boolean metaPresent = metaPresentCache.get(cacheKey);
    if (metaPresent != null) {
        return metaPresent;
    }

    metaPresent = Boolean.FALSE;

    /**
    * 這裏的findAnnotation是獲取annotationType註解類上metaAnnotationType類型的註解,
    * 比如:獲取Af類上的 @Configuration 註解,能獲取到,就是meta-present
    * */ 
    if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
        metaPresent = Boolean.TRUE;
    }
    metaPresentCache.put(cacheKey, metaPresent);
    return metaPresent;
}

校驗屬性默認值,不能爲null,且必須一致。

private void validateDefaultValueConfiguration(Method aliasedAttribute) {
    Object defaultValue = this.sourceAttribute.getDefaultValue();
    Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();

    // 默認值不能爲空
    if (defaultValue == null || aliasedDefaultValue == null) {
        String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
            "and attribute '%s' in annotation [%s] must declare default values.",
            this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
            aliasedAttribute.getDeclaringClass().getName());
        throw new AnnotationConfigurationException(msg);
    }

    // 默認值必須一樣
    if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
        String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
            "and attribute '%s' in annotation [%s] must declare the same default value.",
            this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
            aliasedAttribute.getDeclaringClass().getName());
        throw new AnnotationConfigurationException(msg);
    }
}

至此,獲取別名描述器的結束。不容易!!!!

總結一下:根據sourceMethod,獲取其所在類,以及獲取標註在sourceMethod上的@AliasFor註解,並獲得設置的值,然後通過設置的屬性值獲得該類中的另一個屬性aliasedMethod。然後校驗@AliasFor中設置的別名註解類型如果不是Annotation.class或者Af.class,就必須在該類上標註。最後校驗返回值類型必須一致,並且默認值必須一致且不能爲null。

最後返回一個List集合:
如果sourceMethod的別名註解類型爲該註解類class(Af.class)或者Annotation.class,則返回包含該別名名稱的List。
否則判斷sourceMethod與當前註解類中其他otherMethod是否有相同的別名屬性,有則返回有相同別名註解屬性的method名稱的List。

判斷返回的List不爲空,則滿足可合成的條件。

看下面例子及源代碼:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface ContextConfiguration {

    @AliasFor(value = "locations")
    String value() default "";

    @AliasFor(value = "value")
    String locations() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@ContextConfiguration
public @interface AfAlias {

    /**
     * groovyScripts 與 xmlFiles 互爲別名,它們的別名都是ContextConfiguration中的locations。隱試別名方式
     */
    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String groovyScripts() default "";

    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String xmlFiles() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@AfAlias
public @interface Af {

    /**
     *  attribute 與 value 互爲別名,因爲 groovyScripts 也是locations的別名,這是別名傳遞
     */
    @AliasFor(value = "groovyScripts", annotation = AfAlias.class)
    String attribute() default "";

    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String value() default "";
}
public List<String> getAttributeAliasNames() {
    // Explicit alias pair? 顯示別名對,直接返回sourceMethod的別名名稱
    if (this.isAliasPair) {
        // 返回一個序列化的List,只包含指定的值,該List不可更改
        return Collections.singletonList(this.aliasedAttributeName);
    }

    // Else: search for implicit aliases 搜則搜索隱式別名
    List<String> aliases = new ArrayList<>();
    for (AliasDescriptor otherDescriptor : getOtherDescriptors()) {
        // 
        // 他們的別名名稱一樣,還要判斷這兩個locations對應的method是否是同一個Class的
        if (this.isAliasFor(otherDescriptor)) {
            // 校驗默認值不能爲null及必須一致,調用了方法:validateDefaultValueConfiguration(otherDescriptor.sourceAttribute);
            this.validateAgainst(otherDescriptor);
            aliases.add(otherDescriptor.sourceAttributeName);
        }
    }
    return aliases;
}

// 獲取otherMethod的別名描述器
private List<AliasDescriptor> getOtherDescriptors() {
    List<AliasDescriptor> otherDescriptors = new ArrayList<>();
    for (Method currentAttribute : getAttributeMethods(this.sourceAnnotationType)) {
        if (!this.sourceAttribute.equals(currentAttribute)) {
            AliasDescriptor otherDescriptor = AliasDescriptor.from(currentAttribute);
            if (otherDescriptor != null) {
                otherDescriptors.add(otherDescriptor);
            }
        }
    }
    return otherDescriptors;
}

/**
* 開始 sourceMethod = attribute,別名屬性是groovyScripts;otherMethod = value,別名屬性locations。
* 第一遍循環,他們的別名屬性不一樣,一個是groovyScripts,一個是locations,外層屬性爲:
*   @AliasFor(value = "groovyScripts", annotation = AfAlias.class)
*   String attribute() default "";
* 第二遍循環,由於Af.class只有兩個屬性,所以循環外層for,通過getAttributeOverrideDescriptor()獲取groovyScripts屬性的別名描述器:
*   @AliasFor(value = "locations", annotation = ContextConfiguration.class)
*   String groovyScripts() default "";
* groovyScripts屬性的別名爲locations。然後判斷此locations與value的別名locations是同一個class裏的屬性,則爲true返回。
**/
private boolean isAliasFor(AliasDescriptor otherDescriptor) {
    for (AliasDescriptor lhs = this; lhs != null; lhs = lhs.getAttributeOverrideDescriptor()) {
        for (AliasDescriptor rhs = otherDescriptor; rhs != null; rhs = rhs.getAttributeOverrideDescriptor()) {
            if (lhs.aliasedAttribute.equals(rhs.aliasedAttribute)) {
                return true;
            }
        }
    }
    return false;
}

/**
* 顯示別名方式,返回null;
* 隱試別名方式,需獲取別名屬性aliasedMethod的別名描述器,例如:
* @AliasFor(value = "groovyScripts", annotation = AfAlias.class)
* String attribute() default "";
* aliasedAttribute 表示 groovyScripts 屬性,然後獲取 groovyScripts的別名描述器。
**/
@Nullable
private AliasDescriptor getAttributeOverrideDescriptor() {
    if (this.isAliasPair) {
        return null;
    }
    return AliasDescriptor.from(this.aliasedAttribute);
}
3.3.3.2. 第二個條件

如果第一個條件爲false,則獲取該method的返回值類型,判斷如果返回值類型是Annotation[].class或者是Annotation[].class子接口類型(Annotation[].class是返回值類型的超類或者接口,或者其本身表示的類),則獲取到該數組的Class對象,繼續步驟 3 返回true的話,則設置synthesizable=true,並不再判斷其他屬性。

Class<?> returnType = attribute.getReturnType();
if (Annotation[].class.isAssignableFrom(returnType)) {
    Class<? extends Annotation> nestedAnnotationType =
            (Class<? extends Annotation>) returnType.getComponentType();
    if (isSynthesizable(nestedAnnotationType)) {
        synthesizable = Boolean.TRUE;
        break;
    }
}
3.3.3.3. 第三個條件

第二個條件不滿足,則判斷method返回值類型如果是Annotation.class或者其子接口類型,則繼續步驟 3 判斷該返回值類型可以合成,可以合成則設置synthesizable=true,並不再判斷其他屬性。

Class<?> returnType = attribute.getReturnType();
if (Annotation.class.isAssignableFrom(returnType)) {
    Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType;
    if (isSynthesizable(nestedAnnotationType)) {
        synthesizable = Boolean.TRUE;
        break;
    }
}
3.3.3.4. 緩存

如果可合成,synthesizable=true;否則false。
最後將該註解Class存入緩存:synthesizableCache.put(annotationType, synthesizable)。

上述執行完後,不滿足合成條件,則返回當前註解,不合成處理。
否則,需要合成處理,繼續下面邏輯。

4. 合成處理

DefaultAnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};

return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);

可以看到,本質原理就是使用了AOP來對A註解對象做了次動態代理,而用於處理代理的對象爲SynthesizedAnnotationInvocationHandler;
我們來看看SynthesizedAnnotationInvocationHandler中的重要處理代碼:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (ReflectionUtils.isEqualsMethod(method)) {
        return annotationEquals(args[0]);
    }
    if (ReflectionUtils.isHashCodeMethod(method)) {
        return annotationHashCode();
    }
    if (ReflectionUtils.isToStringMethod(method)) {
        return annotationToString();
    }
    if (AnnotationUtils.isAnnotationTypeMethod(method)) {
        return annotationType();
    }
    if (!AnnotationUtils.isAttributeMethod(method)) {
        throw new AnnotationConfigurationException(String.format(
            "Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType()));
    }
    return getAttributeValue(method);
}

在invoke(即攔截方法中,這個攔截方法就是在註解中獲取屬性值的方法,不要忘了,註解的屬性實際上定義爲接口的方法),其次判斷,如果當前執行的方法不是equals、hashCode、toString、或者屬性是另外的註解,或者不是屬性方法,之外的方法(這些方法就是要處理的目標屬性),都調用了getAttributeValue方法。

所以我們又跟蹤到getAttributeValue方法的重要代碼:

private Object getAttributeValue(Method attributeMethod) {
    String attributeName = attributeMethod.getName();
    Object value = this.valueCache.get(attributeName);
    if (value == null) {
        value = this.attributeExtractor.getAttributeValue(attributeMethod);
        if (value == null) {
            String msg = String.format("%s returned null for attribute name [%s] from attribute source [%s]",
                this.attributeExtractor.getClass().getName(), attributeName, this.attributeExtractor.getSource());
            throw new IllegalStateException(msg);
        }
            // Synthesize nested annotations before returning them.
        if (value instanceof Annotation) {
            value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
        }
        else if (value instanceof Annotation[]) {
            value = AnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
        }

              this.valueCache.put(attributeName, value);
    }

    // Clone arrays so that users cannot alter the contents of values in our cache.
    if (value.getClass().isArray()) {
        value = cloneArray(value);
    }

    return value;
}

valueCache獲取不到屬性值,通過:value = this.attributeExtractor.getAttributeValue(attributeMethod); 獲取到屬性method的值,並設置到緩存valueCache中:

// 例子
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@ContextConfiguration
public @interface AfAlias {

    /**
     * groovyScripts 與 xmlFiles 互爲別名,隱試別名方式
     */
    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String groovyScripts() default "";

    @AliasFor(value = "locations", annotation = ContextConfiguration.class)
    String xmlFiles() default "";
}

@Nullable
public final Object getAttributeValue(Method attributeMethod) {
    String attributeName = attributeMethod.getName();
    Object attributeValue = getRawAttributeValue(attributeMethod);

    // attributeAliasMap在初始化時設置的,包含了該method所屬註解類的所有的Method及其別名,這裏獲取該method的別名
    // method = groovyScripts時,aliasNames = [xmlFiles]
    List<String> aliasNames = this.attributeAliasMap.get(attributeName);
    if (aliasNames != null) {
        // 獲取到groovyScripts的默認值
        Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
        for (String aliasName : aliasNames) {
            // 獲取別名屬性的值,即獲取locations屬性的值,比較locations與groovyScripts的值是否一樣
            Object aliasValue = getRawAttributeValue(aliasName);
            // @AliasFor標籤的傳遞性也是在這裏體現;如果不相同,直接拋出異常;否則正常返回屬性值
            if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
                !ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
                !ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
                String elementName = (this.annotatedElement != null ? this.annotatedElement.toString() : "unknown element");
                throw new AnnotationConfigurationException(String.format(
                    "In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
                    "alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
                    this.annotationType.getName(), elementName, this.source, attributeName, aliasName,
                    ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
            }

            // If the user didn't declare the annotation with an explicit value,
            // use the value of the alias instead.
            if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
                attributeValue = aliasValue;
            }
        }
    }

    return attributeValue;
}

三、總結

  1. @AliasFor註解用於設置別名,自身的attribute和value互爲別名。用@AliasFor設置別名時,互爲別名的屬性需滿足條件:返回值類型一致、默認值不能爲null且一致、別名名稱不能和自身名稱一樣、在method上設置了別名註解類型後也必須在method所在的註解類上標註(如果註解類型是method所在類或者Annotation.class時可以不用標註)。

  2. 獲取註解時,通過Class的getAnnotation(Class< ? extends Annotation> clazz) 獲取到註解實例,如果該實例不可組合,就直接返回該註解實例;否則組合註解實例後再返回。

  3. 判斷是否可組合,滿足三個條件中的任意一個即可:
    a. method所在類中存在符合條件的別名屬性;
    b. method的返回值類型是Annotation[].class或者子接口或實現類類型時,判斷數組類型的Class中是否存在符合條件的別名屬性;
    c. method的返回值類型是Annotation.class或者子接口或實現類類型時,判斷該返回值類型Class中是否存在符合條件的別名屬性。

  4. 組合的註解實例,通過動態代理的方式,判斷屬性值及別名屬性的值的相同性。並且組合嵌套註解(屬性值類型是Annotation.class或者Annotation[].class)。


在學習Spring Boot時,發現Spring Boot中使用了大量的註解,這些註解定義時很多都用到了@AliasFor註解,然後就想知道這個註解的作用及其解析原理,花了大概一週時間分析原理,邊上班邊總結,看的比較慢,理解有問題的地方歡迎指出,謝謝!

部分內容參考文章:Spring中的@AliasFor標籤

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