spring-core的注解工具提供的方法 AnnotatedElementUtils.findMergedRepeatableAnnotations
用于从AnnotatedElement 对象获取可重复的注解。但如果注解本身也是可以定义在其他注解之上的元注解(meta-annotation),且该注解也是可重复注解。这个方法就可能会失效。这就是我最近在使用spring注解工具时遇到的问题。示例如下。
注解定义
先定义一组注解:
java
@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
的元注解。它们三个层级关系是这样的。
cpp
@LevelA 可重复 @LevelAs
└─@LevelB 可重复 @LevelBs
└─@LevelC
如下定义了测试这些注解的类
java
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
注解
java
@Test
public 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
方法实际的核心执行代码如下:
java
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
RepeatableContainers
进一查看RepeatableContainers的代码发现它还有另一个方法standardRepeatables()
创建一个使用Java的@Repeatable注释进行搜索的RepeatableContainers实例。
java
/**
* 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
@Test
public 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)
完整测试代码
java
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 {
@Test
public 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();
}
}
@Test
public 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();
}
}
@Test
public 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() {};
}
}