spring-core的注解工具提供的方法 AnnotatedElementUtils.findMergedRepeatableAnnotations
用于从AnnotatedElement 对象获取可重复的注解。但如果注解本身也是可以定义在其他注解之上的元注解(meta-annotation),且该注解也是可重复注解。这个方法就可能会失效。这就是我最近在使用spring注解工具时遇到的问题。示例如下。
注解定义
先定义一组注解:
@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })@Repeatable(LevelAs.class)public @interface LevelA {String value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public @interface LevelAs {LevelA[] value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.ANNOTATION_TYPE,ElementType.METHOD })@LevelA("shadows")@Repeatable(LevelBs.class)public @interface LevelB {@AliasFor(annotation = LevelA.class,attribute = "value")String value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public @interface LevelBs {LevelB[] value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })@LevelB("shadows")public @interface LevelC {@AliasFor(annotation = LevelB.class,attribute = "value")String value();}
上面定义了@LevalA,@LevalB,@LevalC
三个注解,其中@LeveA,@LevelB
是可以重复注解(参见 @LevelAs,@LevelBs
),而且还是元注解。@LeveA
是@LevelB
的元注解,@LevelB
是@LevelC
的元注解。它们三个层级关系是这样的。
@LevelA 可重复 @LevelAs└─@LevelB 可重复 @LevelBs└─@LevelC
如下定义了测试这些注解的类
public static class TestClass{@LevelB("hello")@LevelB("world")@LevelA("top1")@LevelA("top2")@LevelC("jerry")public void foo() {};@LevelB("hello")@LevelA("top1")@LevelA("top2")@LevelC("jerry")public void tar() {};}
findMergedRepeatableAnnotations
如下调用AnnotatedElementUtils.findMergedRepeatableAnnotations
方法读取TestClass.tar
上定义的所有@LevelA
注解
@Testpublic void test1() {try {Method method = TestClass.class.getMethod("tar");Set<LevelA> set = AnnotatedElementUtils.findMergedRepeatableAnnotations(method,LevelA.class);set.stream().forEach(a->{System.out.printf("%s\n", a);});} catch (Throwable e) {e.printStackTrace();fail();}}
确实可以获取正确的结果:
@RepeatableAnnotTest$LevelA(value=top1)
@RepeatableAnnotTest$LevelA(value=top2)
@RepeatableAnnotTest$LevelA(value=hello)
@RepeatableAnnotTest$LevelA(value=jerry)
那是因为TestClass.tar
上只定义了一个@LevelB
注解,如果上面的测试代码只是把方法名换为定义了多个@LevelB的方法foo
,结果就是这样的:
@RepeatableAnnotTest$LevelA(value=top1)
@RepeatableAnnotTest$LevelA(value=top2)
@RepeatableAnnotTest$LevelA(value=jerry)
因为这里foo
方法定义了超过一个@LevelB
注解,实际定义在foo
上的注解就成了@LeveBs({@LevelB("hello"),@LevelB("world")})
,就导致AnnotatedElementUtils.findMergedRepeatableAnnotations
方法不能正确获取。
分析AnnotatedElementUtils
的源码,找到findMergedRepeatableAnnotations
方法实际的核心执行代码如下:
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
RepeatableContainers
进一查看RepeatableContainers的代码发现它还有另一个方法standardRepeatables()
/*** Create a {@link RepeatableContainers} instance that searches using Java's* {@link Repeatable @Repeatable} annotation.* @return a {@link RepeatableContainers} instance*/public static RepeatableContainers standardRepeatables() {return StandardRepeatableContainers.INSTANCE;}
创建一个使用Java的@Repeatable注释进行搜索的RepeatableContainers实例。
看到这个方法注释,我就知道我应该找到了答案。
于是照着上面的代码将测试代码修改如下:
@Testpublic void test3() {try {Method method = TestClass.class.getMethod("foo");RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables();MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, repeatableContainers).stream(LevelA.class).forEach(a->{System.out.printf("%s\n", a.synthesize());});} catch (Throwable e) {e.printStackTrace();fail();}}
则结果正确:
@LevelA(value=top1)
@LevelA(value=top2)
@LevelA(value=hello)
@LevelA(value=world)
@LevelA(value=jerry)
总结
org.springframework.core.annotation.AnnotationUtils
和org.springframework.core.annotation.AnnotatedElementUtils
中的静态方法只是注解工具常用场景的便利化封装,比如本例的AnnotatedElementUtils.findMergedRepeatableAnnotations
方法,
对于获取有复层级关系的可重复的元注解(meta-annotation),它就并不一定适用,而应该直接使用更底层的org.springframework.core.annotation.MergedAnnotations
中的工具方法来根据实际场景实现注解分析提取。
完整测试代码
import static org.junit.Assert.*;import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Set;
import org.junit.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;public class RepeatableAnnotTest {@Testpublic void test1() {try {Method method = TestClass.class.getMethod("foo");Set<LevelA> set = AnnotatedElementUtils.findMergedRepeatableAnnotations(method,LevelA.class);set.stream().forEach(a->{System.out.printf("%s\n", a);});} catch (Throwable e) {e.printStackTrace();fail();}}@Testpublic void test2() {try {Method method = TestClass.class.getMethod("foo");RepeatableContainers repeatableContainers = RepeatableContainers.of(LevelA.class,null);MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY,repeatableContainers) .stream(LevelA.class) /* .stream() .sorted((o1,o2)->Integer.compare(o1.getDistance(), o2.getDistance()))*/.forEach(a->{System.out.printf("%s\n", a.synthesize());});} catch (Throwable e) {e.printStackTrace();fail();}}@Testpublic void test3() {try {Method method = TestClass.class.getMethod("foo");RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables();MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, repeatableContainers).stream(LevelA.class).forEach(a->{System.out.printf("%s\n", a.synthesize());});} catch (Throwable e) {e.printStackTrace();fail();}}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })@Repeatable(LevelAs.class)public @interface LevelA {String value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public @interface LevelAs {LevelA[] value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.ANNOTATION_TYPE,ElementType.METHOD })@LevelA("shadows")@Repeatable(LevelBs.class)public @interface LevelB {@AliasFor(annotation = LevelA.class,attribute = "value")String value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public @interface LevelBs {LevelB[] value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })@LevelB("shadows")public @interface LevelC {@AliasFor(annotation = LevelB.class,attribute = "value")String value();}public static class TestClass{@LevelB("hello")@LevelB("world")@LevelA("top1")@LevelA("top2")@LevelC("jerry")public void foo() {};@LevelB("hello")@LevelA("top1")@LevelA("top2")@LevelC("jerry")public void tar() {};}
}