spring:解决findMergedRepeatableAnnotations获取可重复的元注解(meta-annotation)结果不正确问题

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注解

	@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方法实际的核心执行代码如下:

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实例。

看到这个方法注释,我就知道我应该找到了答案。

于是照着上面的代码将测试代码修改如下:

	@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)

总结

org.springframework.core.annotation.AnnotationUtilsorg.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 {
	@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() {};
	}
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值