Spring - 注解解析器

2024-06-13 05:18
文章标签 java spring 注解 解析器

本文主要是介绍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 {@Testpublic 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注解,返回nullAliasFor 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标签

这篇关于Spring - 注解解析器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1056381

相关文章

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll