Spring 註解(二)註解工具類 AnnotationUtils 和 AnnotatedElementUtils
Spring 註解(二)註解工具類 AnnotationUtils 和 AnnotatedElementUtils
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10198698.html)
Spring 註解系列文章:
- Spring 註解(一)Spring 註解編程模型
- Spring 註解(二)註解工具類 AnnotationUtils 和 AnnotatedElementUtils
首先回顧一下 AnnotationUtils 和 AnnotatedElementUtils 這兩個註解工具類的用法:
@Test @GetMapping(value = "/GetMapping", consumes = MediaType.APPLICATION_JSON_VALUE) public void test() throws NoSuchMethodException { Method method = ReflectUtils.findDeclaredMethod( AliasForTest.class, "test", null); // AnnotationUtils 不支持註解屬性覆蓋 RequestMapping requestMappingAnn1 = AnnotationUtils.getAnnotation(method, RequestMapping.class); Assert.assertEquals(new String[]{}, requestMappingAnn1.value()); Assert.assertEquals(new String[]{}, requestMappingAnn1.consumes()); // AnnotatedElementUtils 支持註解屬性覆蓋 RequestMapping requestMappingAnn2 = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class); Assert.assertEquals(new String[]{"/GetMapping"}, requestMappingAnn2.value()); Assert.assertEquals(new String[]{MediaType.APPLICATION_JSON_VALUE}, requestMappingAnn2.consumes()); }
一、AnnotationUtils 源碼分析
AnnotationUtils 解決註解別名,包括顯式別名、隱式別名、傳遞的隱式別名,還可以查的指定註解的屬性信息。
AnnotationUtils 底層使用動態代理的方式處理註解別名的問題。
1.1 get* 系列註解查找
get 遵循 JDK 的註解查找語義,只是增加了一級元註解的查找。
public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType) { // 1. 直接查找本地註解 if (annotationType.isInstance(annotation)) { return synthesizeAnnotation((A) annotation); } // 2. 元註解上查找,註意相對於 find* 而言,這裏只查找一級元註解 Class<? extends Annotation> annotatedElement = annotation.annotationType(); try { A metaAnn = annotatedElement.getAnnotation(annotationType); return (metaAnn != null ? synthesizeAnnotation(metaAnn, annotatedElement) : null); } catch (Throwable ex) { handleIntrospectionFailure(annotatedElement, ex); return null; } }
1.2 find* 系列註解查找
遵循 JDK 的註解查找語義,只是增加了多級元註解的查找。
// visited 表示已經查找的元素,Spring 的遞歸很多都用到了這個參數 private static <A extends Annotation> A findAnnotation( AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) { try { // 1. 本地註解查找 A annotation = annotatedElement.getDeclaredAnnotation(annotationType); if (annotation != null) { return annotation; } // 2. 元註解上查找 for (Annotation declaredAnn : getDeclaredAnnotations(annotatedElement)) { Class<? extends Annotation> declaredType = declaredAnn.annotationType(); if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) { // 3. 元註解上遞歸查找 annotation = findAnnotation((AnnotatedElement) declaredType, annotationType, visited); if (annotation != null) { return annotation; } } } } catch (Throwable ex) { handleIntrospectionFailure(annotatedElement, ex); } return null; }
1.3 synthesizeAnnotation 動態代理解決別名問題
static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) {
// 1. SynthesizedAnnotation 為一個標記,表示已經動態代理過了
// hasPlainJavaAnnotationsOnly 如果是 java 中的註解不可能有註解別名,直接返回
if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) {
return annotation;
}
// 2. 判斷是否需要進行動態代理,即註解中存在別名,包括顯示別名、隱式別名、傳遞的隱式別名
Class<? extends Annotation> annotationType = annotation.annotationType();
if (!isSynthesizable(annotationType)) {
return annotation;
}
// 3. AnnotationAttributeExtractor 用於從註解 annotation 中提取屬性的值
DefaultAnnotationAttributeExtractor attributeExtractor =
new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
// 4. SynthesizedAnnotationInvocationHandler 動態代理的類
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
// 5. 接口中有 SynthesizedAnnotation,並返回動態代理的對象
Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};
return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
}
1.4 SynthesizedAnnotationInvocationHandler
下面主要看一下動態代理的 invoke 實現是怎麽實現的。
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();
}
// 註解的 annotationType 返回註解的 Class 類型
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);
}
getAttributeValue 的核心其實就一句話 this.attributeExtractor.getAttributeValue(attributeMethod);
委托給了對應的 AnnotationAttributeExtractor 處理。
1.4.1 AnnotationAttributeExtractor
AbstractAliasAwareAnnotationAttributeExtractor(
Class<? extends Annotation> annotationType, @Nullable Object annotatedElement, S source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
this.annotationType = annotationType;
this.annotatedElement = annotatedElement;
this.source = source;
this.attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
}
在構造方法中有一個很重要的方法 AnnotationUtils.getAttributeAliasMap(annotationType) 用於獲取其別名。
public final Object getAttributeValue(Method attributeMethod) {
String attributeName = attributeMethod.getName();
// attributeValue 表示屬性的真實值
Object attributeValue = getRawAttributeValue(attributeMethod);
// 獲取所有的別名
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
// 屬性的默認值,默認值肯定是一樣的,因為在獲取別名的時候已經校驗了默認值
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
for (String aliasName : aliasNames) {
// 別名的真實值
Object aliasValue = getRawAttributeValue(aliasName);
// 如果兩個別名的值不相等,且都不等於默認值,直接拋異常
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
throw new AnnotationConfigurationException();
}
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
}
}
}
return attributeValue;
}
1.5 AliasDescriptor
(1) getAttributeAliasMap
在 AbstractAliasAwareAnnotationAttributeExtractor 的構造器中有一個很重要的方法 getAttributeAliasMap 獲取註解中所有屬性的別名。
static Map<String, List<String>> getAttributeAliasMap(@Nullable Class<? extends Annotation> annotationType) {
map = new LinkedHashMap<>();
for (Method attribute : getAttributeMethods(annotationType)) {
List<String> aliasNames = getAttributeAliasNames(attribute);
if (!aliasNames.isEmpty()) {
map.put(attribute.getName(), aliasNames);
}
}
return map;
}
static List<String> getAttributeAliasNames(Method attribute) {
AliasDescriptor descriptor = AliasDescriptor.from(attribute);
return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.emptyList());
}
可以別名獲取的所有的工作都是委托給了 AliasDescriptor 完成,這一小節我們就主要看一下這個類。
(2) AliasDescriptor 構造及校驗
public static AliasDescriptor from(Method attribute) {
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
if (aliasFor == null) {
return null;
}
descriptor = new AliasDescriptor(attribute, aliasFor);
descriptor.validate();
return descriptor;
}
構建一個 AliasDescriptor 分為兩步:一是獲取註解信息(構造器),二是校驗別名是否成立(validate)。@AliasFor 有以下的規約:
- 規約1:顯示別名可以不用配置 annotation 屬性
- 規約2:隱式別名默認和原註解屬性名稱一致,getAliasedAttributeName 中體現
- 規約3:隱式別名 @AliasFor 配置的註解必須出現在元註解中,可以是多級元註解
- 規約4:顯示別名必須成對配置
- 規約5:別名必須配置默認值,且默認值一致。註意別名可以為數組類型,而原屬性為數組的元素類型
private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
Class<?> declaringClass = sourceAttribute.getDeclaringClass();
// 1. 註解原字段的信息
this.sourceAttribute = sourceAttribute;
this.sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
this.sourceAttributeName = sourceAttribute.getName();
// 2. @AliasFor 註解的信息
// 規約1:顯示的別名可以不用配置 annotation 屬性
// 規約2:隱式別名默認和原註解屬性名稱一致,getAliasedAttributeName 中體現
this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ?
this.sourceAnnotationType : aliasFor.annotation());
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
if (this.aliasedAnnotationType == this.sourceAnnotationType &&
this.aliasedAttributeName.equals(this.sourceAttributeName)) {
throw new AnnotationConfigurationException(...);
}
try {
// @AliasFor 配置的別名不存在直接拋出異常
this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
} catch (NoSuchMethodException ex) {
throw new AnnotationConfigurationException(..., ex);
}
// 3. isAliasPair=true 表示就同一個註解內的顯示別名
this.isAliasPair = (this.sourceAnnotationType == this.aliasedAnnotationType);
}
(3) getAttributeAliasNames 獲取別名
public List<String> getAttributeAliasNames() {
// 1. 顯示別名,直接返回
if (this.isAliasPair) {
return Collections.singletonList(this.aliasedAttributeName);
}
// 2. 隱式別名,包括可傳遞的隱式別名
List<String> aliases = new ArrayList<>();
// 2.1 遍歷註解中的其它屬性,一一判斷是否互為別名
// getOtherDescriptors 獲取其它的所有屬性
// isAliasFor 判斷兩個屬性是否互為別名,會遞歸向上查找
for (AliasDescriptor otherDescriptor : getOtherDescriptors()) {
if (this.isAliasFor(otherDescriptor)) {
this.validateAgainst(otherDescriptor);
aliases.add(otherDescriptor.sourceAttributeName);
}
}
return aliases;
}
(4) getAttributeOverrideName 獲取當前屬性在元註解中對應的別名
public String getAttributeOverrideName(Class<? extends Annotation> metaAnnotationType) {
// 遞歸向上查找別名,如果 sourceAnnotationType==metaAnnotationType 則查找到了
for (AliasDescriptor desc = this; desc != null; desc = desc.getAttributeOverrideDescriptor()) {
if (desc.isOverrideFor(metaAnnotationType)) {
return desc.aliasedAttributeName;
}
}
return null;
}
二、AnnotatedElementUtils 源碼分析
2.1 Processor 對匹配的註解進行後置處理
Processor 對匹配的註解進行後置處理,可以通過 process 方法的返回值來控制查找的流程:返回 null 時繼續查找,非 null 時直接返回。有一種情況例外就是 aggregates=true,這種情況要查找所有的註解,所以會繼續查找。
(1) 接口
private interface Processor<T> {
// 兩個作用:一是根據返回值是否為 null 控制查詢的流程;二是對查詢的註解進行處理,主要是用於獲取該註解的屬性值
T process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth);
// 只有 MergedAnnotationAttributesProcessor 有效,用於處理元註解屬性覆蓋
// annotation 為當前註解,result 為元註解屬性信息,annotation 會覆蓋元註解中的屬性信息
void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result);
// 查詢所有元註解時有效,不管是否匹配都要執行 process 方法
boolean alwaysProcesses();
// MergedAnnotationAttributesProcessor 查找所有的註解有效
boolean aggregates();
List<T> getAggregatedResults();
}
有兩個方法要特別關註:
process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth)
有兩個作用:一是根據返回值來控制查找的流程;二是 MergedAnnotationAttributesProcessor 的 process 方法返回查找到的註解信息 AnnotationAttributespostProcess(@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes)
只有 MergedAnnotationAttributesProcessor 有效,用來處理元註解屬性覆蓋。其中 annotation 表示當前的註解,attributes 表示元註解的屬性信息,執行時會用 annotation 覆蓋 attributes。
(2) 類圖
Processor 的有幾個實現:SimpleAnnotationProcessor 相當於一個簡單的適配器;AlwaysTrueBooleanAnnotationProcessor 的 process 方法永遠返回 TRUE;MergedAnnotationAttributesProcessor 用於處理元註解屬性覆蓋。
常用的方法對應的 Processor 返回值如下:
getMetaAnnotationTypes
獲取指定註解上的所有元註解,所以 process 方法返回 null 且 alwaysProcesses=truehasMetaAnnotationTypes
判斷指定的註解上是否有元註解,所以 process 方法返回 metaDepth > 0 ? Boolean.TRUE : CONTINUE,即當 metaDepth>0 表示有元註解就停止查詢isAnnotated
是否存在指定的註解,所以只配匹配到 process 方法就返回 TRUE,使用 AlwaysTrueBooleanAnnotationProcessorgetMergedAnnotationAttributes
元註解會進行屬性覆蓋,使用 MergedAnnotationAttributesProcessorgetAllMergedAnnotations
查找所有的註解,使用 MergedAnnotationAttributesProcessor 且 aggregates=true
(3) MergedAnnotationAttributesProcessor
// AnnotationUtils#retrieveAnnotationAttributes 方法獲取當前註解的屬性
public AnnotationAttributes process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
this.classValuesAsString, this.nestedAnnotationsAsMap);
}
// annotation 為當前註解,result 為元註解屬性信息,這個元註解的屬性信息是 process 方法提取的
// annotation 會覆蓋元註解中的屬性信息
public void postProcess(@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) {
annotation = AnnotationUtils.synthesizeAnnotation(annotation, element);
Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
// 1. 已經解析過的屬性,避免循環查找
Set<String> valuesAlreadyReplaced = new HashSet<>();
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
String attributeName = attributeMethod.getName();
// 2. 查找 attributeMethod 屬性到底覆蓋了 targetAnnotationType 元註解的那個屬性
String attributeOverrideName = AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);
// 3 顯示進行屬性覆蓋,通過 @AliasFor 註解
if (attributeOverrideName != null) {
if (valuesAlreadyReplaced.contains(attributeOverrideName)) {
continue;
}
List<String> targetAttributeNames = new ArrayList<>();
targetAttributeNames.add(attributeOverrideName);
valuesAlreadyReplaced.add(attributeOverrideName);
// 確保所有的別名都要進行屬性覆蓋 (SPR-14069)
List<String> aliases = AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName);
if (aliases != null) {
for (String alias : aliases) {
if (!valuesAlreadyReplaced.contains(alias)) {
targetAttributeNames.add(alias);
valuesAlreadyReplaced.add(alias);
}
}
}
// 將 targetAttributeNames 的屬性值設置為 attributeName 的值
overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames);
}
// 3.2 隱式的進行屬性覆蓋,只要字段與元註解的屬性字段一下致(規約)
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
overrideAttribute(element, annotation, attributes, attributeName, attributeName);
}
}
}
2.2 searchWithGetSemantics
searchWithGetSemantics 有 7 個參數:
- element 註解標註的 AnnotatedElement
- annotationTypes、annotationName、containerType 分別表示要查找的註解類型、註解名稱、以及可重復註解的容器對象
- processor 後置的處理器,process 返回 null 繼續查找,否則停止查找。aggregates=true 時例外,因為此時查找全部的註解。
- visited 已經查找的元素,避免重復查找。
- metaDepth 註解深度,普通註解為 0
// 用於查找 element 上的 annotationTypes、annotationName、containerType 類型註解
// 返回後置處理器對查找後的註解 process 後的值
private static <T> T searchWithGetSemantics(AnnotatedElement element,
Set<Class<? extends Annotation>> annotationTypes, @Nullable String annotationName,
@Nullable Class<? extends Annotation> containerType, Processor<T> processor,
Set<AnnotatedElement> visited, int metaDepth) {
if (visited.add(element)) {
try {
// 1. 本地註解查找 Start searching within locally declared annotations
List<Annotation> declaredAnnotations = Arrays.asList(AnnotationUtils.getDeclaredAnnotations(element));
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
annotationTypes, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
// 2. @Inherited 類型查找
if (element instanceof Class) { // otherwise getAnnotations doesn‘t return anything new
Class<?> superclass = ((Class<?>) element).getSuperclass();
if (superclass != null && superclass != Object.class) {
List<Annotation> inheritedAnnotations = new LinkedList<>();
for (Annotation annotation : element.getAnnotations()) {
if (!declaredAnnotations.contains(annotation)) {
inheritedAnnotations.add(annotation);
}
}
// Continue searching within inherited annotations
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
annotationTypes, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
}
}
}
catch (Throwable ex) {
AnnotationUtils.handleIntrospectionFailure(element, ex);
}
}
return null;
}
searchWithGetSemanticsInAnnotations 真正用於在指定的註解集合 annotations 中查找指定的註解。
private static <T> T searchWithGetSemanticsInAnnotations(@Nullable AnnotatedElement element,
List<Annotation> annotations, Set<Class<? extends Annotation>> annotationTypes,
@Nullable String annotationName, @Nullable Class<? extends Annotation> containerType,
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
// 1. 直接匹配 Search in annotations
for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
// 1.1 註解類型或註解名相同
if (annotationTypes.contains(currentAnnotationType) ||
currentAnnotationType.getName().equals(annotationName) ||
processor.alwaysProcesses()) {
T result = processor.process(element, annotation, metaDepth);
if (result != null) {
if (processor.aggregates() && metaDepth == 0) {
processor.getAggregatedResults().add(result);
}
else {
return result;
}
}
}
// 1.2 可重復註解,註意可重復註解不可能是組合註解 Repeatable annotations in container?
else if (currentAnnotationType == containerType) {
for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
T result = processor.process(element, contained, metaDepth);
if (result != null) {
processor.getAggregatedResults().add(result);
}
}
}
}
}
// 2. 遞歸查找元註解 Recursively search in meta-annotations
for (Annotation annotation : annotations) {
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
if (!AnnotationUtils.hasPlainJavaAnnotationsOnly(currentAnnotationType)) {
T result = searchWithGetSemantics(currentAnnotationType, annotationTypes,
annotationName, containerType, processor, visited, metaDepth + 1);
if (result != null) {
// MergedAnnotationAttributesProcessor 用於元註解屬性覆蓋
// annotation 表示當前的註解,attributes 表示元註解的屬性信息,annotation 會覆蓋 attributes。
processor.postProcess(element, annotation, result);
if (processor.aggregates() && metaDepth == 0) {
processor.getAggregatedResults().add(result);
} else {
return result;
}
}
}
}
return null;
}
參考:
- 《spring註解工具類AnnotatedElementUtils和AnnotationUtils》:https://blog.csdn.net/qq_22845447/article/details/83210559
每天用心記錄一點點。內容也許不重要,但習慣很重要!
Spring 註解(二)註解工具類 AnnotationUtils 和 AnnotatedElementUtils