From ef289dc6764099ef60735af091f04836039106d2 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:17:27 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E4=B8=BAjdk=E5=85=83=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 54 +++++++++++++++++++ .../CombinationAnnotationElement.java | 16 +----- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index a75affb8d..5d99bdf92 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.annotation; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReflectUtil; @@ -16,6 +17,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; /** @@ -27,6 +29,58 @@ import java.util.function.Predicate; */ public class AnnotationUtil { + /** + * 元注解 + */ + static final Set> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, // + Retention.class, // + Inherited.class, // + Documented.class, // + SuppressWarnings.class, // + Override.class, // + Deprecated.class// + ); + + /** + * 是否为Jdk自带的元注解。
+ * 包括: + * + * + * @param annotationType 注解类型 + * @return 是否为Jdk自带的元注解 + */ + public static boolean isJdkMateAnnotation(Class annotationType) { + return META_ANNOTATIONS.contains(annotationType); + } + + /** + * 是否不为Jdk自带的元注解。
+ * 包括: + * + * + * @param annotationType 注解类型 + * @return 是否为Jdk自带的元注解 + */ + public static boolean isNotJdkMateAnnotation(Class annotationType) { + return !isJdkMateAnnotation(annotationType); + } + /** * 将指定的被注解的元素转换为组合注解元素 * diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java index 542793f79..e2e901d66 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java @@ -39,18 +39,6 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa return new CombinationAnnotationElement(element, predicate); } - /** - * 元注解 - */ - private static final Set> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, // - Retention.class, // - Inherited.class, // - Documented.class, // - SuppressWarnings.class, // - Override.class, // - Deprecated.class// - ); - /** * 注解类型与注解对象对应表 */ @@ -138,7 +126,7 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa // 直接注解 for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); - if (false == META_ANNOTATIONS.contains(annotationType)) { + if (AnnotationUtil.isNotJdkMateAnnotation(annotationType)) { if(test(annotation)){ declaredAnnotationMap.put(annotationType, annotation); } @@ -157,7 +145,7 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa Class annotationType; for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); - if (false == META_ANNOTATIONS.contains(annotationType)) { + if (AnnotationUtil.isNotJdkMateAnnotation(annotationType)) { if(test(annotation)){ annotationMap.put(annotationType, annotation); } From 0cb498bf6e1e4c1a3d700ae638dd7673013bae0e Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:26:51 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=99=A8;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annotation/scanner/AnnotationScanner.java | 90 +++++++ .../scanner/FieldAnnotationScanner.java | 27 +++ .../scanner/MateAnnotationScanner.java | 95 ++++++++ .../scanner/MethodAnnotationScanner.java | 27 +++ .../scanner/TypeAnnotationScanner.java | 224 ++++++++++++++++++ .../scanner/AnnotationForScannerTest.java | 15 ++ .../scanner/FieldAnnotationScannerTest.java | 33 +++ .../scanner/MateAnnotationScannerTest.java | 55 +++++ .../scanner/MethodAnnotationScannerTest.java | 35 +++ .../scanner/TypeAnnotationScannerTest.java | 62 +++++ 10 files changed, 663 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java new file mode 100644 index 000000000..135165634 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java @@ -0,0 +1,90 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 注解扫描器,用于从支持的可注解元素上获取所需注解 + * + * @author huangchengxing + * @see TypeAnnotationScanner + * @see MethodAnnotationScanner + * @see FieldAnnotationScanner + * @see MateAnnotationScanner + */ +public interface AnnotationScanner { + + /** + * 是否支持扫描该可注解元素 + * + * @param annotatedElement 可注解元素 + * @return 是否支持扫描该可注解元素 + */ + default boolean support(AnnotatedElement annotatedElement) { + return false; + } + + /** + * 获取可注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true + * + * @param annotatedElement 可注解元素 + * @return 元素上的注解 + */ + List getAnnotations(AnnotatedElement annotatedElement); + + /** + * 若{@link #support(AnnotatedElement)}返回{@code true}, + * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, + * 否则返回{@link Collections#emptyList()} + * + * @param annotatedElement 元素 + * @return 元素上的注解 + */ + default List getIfSupport(AnnotatedElement annotatedElement) { + return support(annotatedElement) ? getAnnotations(annotatedElement) : Collections.emptyList(); + } + + /** + * 给定一组扫描器,使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解 + * + * @param annotatedElement 可注解元素 + * @param scanners 注解扫描器 + * @return 注解 + */ + static List scanByAnySupported(AnnotatedElement annotatedElement, AnnotationScanner... scanners) { + if (ObjectUtil.isNull(annotatedElement) && ArrayUtil.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .filter(scanner -> scanner.support(annotatedElement)) + .findFirst() + .map(scanner -> scanner.getAnnotations(annotatedElement)) + .orElseGet(Collections::emptyList); + } + + /** + * 根据指定的扫描器,扫描元素上可能存在的注解 + * + * @param annotatedElement 可注解元素 + * @param scanners 注解扫描器 + * @return 注解 + */ + static List scanByAllScanner(AnnotatedElement annotatedElement, AnnotationScanner... scanners) { + if (ObjectUtil.isNull(annotatedElement) && ArrayUtil.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .map(scanner -> scanner.getIfSupport(annotatedElement)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java new file mode 100644 index 000000000..119c51ecb --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java @@ -0,0 +1,27 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.List; + +/** + * 扫描{@link Field}上的注解 + * + * @author huangchengxing + */ +public class FieldAnnotationScanner implements AnnotationScanner { + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Field; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return CollUtil.newArrayList(annotatedElement.getAnnotations()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java new file mode 100644 index 000000000..1a6f8d1ac --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java @@ -0,0 +1,95 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 扫描注解类上存在的注解,支持处理枚举实例或枚举类型
+ * 需要注意,当待解析是枚举类时,有可能与{@link TypeAnnotationScanner}冲突 + * + * @author huangchengxing + * @see TypeAnnotationScanner + */ +public class MateAnnotationScanner implements AnnotationScanner { + + /** + * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + private final boolean includeSupperMetaAnnotation; + + /** + * 构造 + * + * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + public MateAnnotationScanner(boolean includeSupperMetaAnnotation) { + this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; + } + + /** + * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 + */ + public MateAnnotationScanner() { + this(true); + } + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return (annotatedElement instanceof Class && ClassUtil.isAssignable(Annotation.class, (Class)annotatedElement)); + } + + /** + * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 + * + * @param consumer 当前层级索引与操作 + * @param source 源注解 + * @param filter 过滤器 + * @author huangchengxing + * @date 2022/6/14 13:28 + */ + public void scan(BiConsumer consumer, Class source, Predicate filter) { + filter = ObjectUtil.defaultIfNull(filter, t -> true); + Deque>> deque = CollUtil.newLinkedList(CollUtil.newArrayList(source)); + int distance = 0; + do { + List> annotationTypes = deque.removeFirst(); + for (Class type : annotationTypes) { + List metaAnnotations = Stream.of(type.getAnnotations()) + .filter(a -> !AnnotationUtil.isJdkMateAnnotation(a.annotationType())) + .filter(filter) + .collect(Collectors.toList()); + for (Annotation metaAnnotation : metaAnnotations) { + consumer.accept(distance, metaAnnotation); + } + deque.addLast(CollStreamUtil.toList(metaAnnotations, Annotation::annotationType)); + } + distance++; + } while (includeSupperMetaAnnotation && !deque.isEmpty()); + } + + @SuppressWarnings("unchecked") + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + List annotations = new ArrayList<>(); + scan( + (index, annotation) -> annotations.add(annotation), + (Class)annotatedElement, + annotation -> ObjectUtil.notEqual(annotation, annotatedElement) + ); + return annotations; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java new file mode 100644 index 000000000..f8de322b8 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java @@ -0,0 +1,27 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 扫描{@link Method}上的注解 + * + * @author huangchengxing + */ +public class MethodAnnotationScanner implements AnnotationScanner { + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Method; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return CollUtil.newArrayList(annotatedElement.getAnnotations()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java new file mode 100644 index 000000000..f833a3492 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java @@ -0,0 +1,224 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 扫描{@link Class}上的注解 + * + * @author huangchengxing + */ +public class TypeAnnotationScanner implements AnnotationScanner { + + /** + * 是否允许扫描父类 + */ + private boolean includeSupperClass; + + /** + * 是否允许扫描父接口 + */ + private boolean includeInterfaces; + + /** + * 过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + */ + private Predicate> filter; + + /** + * 排除的类型,以上类型及其树结构将直接不被查找 + */ + private final Set> excludeTypes; + + /** + * 转换器 + */ + private final List>> converters; + + /** + * 是否有转换器 + */ + private boolean hasConverters; + + /** + * 构造一个类注解扫描器 + * + * @param includeSupperClass 是否允许扫描父类 + * @param includeInterfaces 是否允许扫描父接口 + */ + public TypeAnnotationScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { + Assert.notNull(filter, "filter must not null"); + Assert.notNull(excludeTypes, "excludeTypes must not null"); + this.includeSupperClass = includeSupperClass; + this.includeInterfaces = includeInterfaces; + this.filter = filter; + this.excludeTypes = excludeTypes; + this.converters = new ArrayList<>(); + } + + /** + * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 + */ + public TypeAnnotationScanner() { + this(true, true, t -> true, CollUtil.newHashSet()); + } + + /** + * 是否允许扫描父类 + * + * @return 是否允许扫描父类 + */ + public boolean isIncludeSupperClass() { + return includeSupperClass; + } + + /** + * 是否允许扫描父接口 + * + * @return 是否允许扫描父接口 + */ + public boolean isIncludeInterfaces() { + return includeInterfaces; + } + + /** + * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + * + * @param filter 过滤器 + * @return 当前实例 + */ + public TypeAnnotationScanner setFilter(Predicate> filter) { + Assert.notNull(filter, "filter must not null"); + this.filter = filter; + return this; + } + + /** + * 添加不扫描的类型,该类型及其树结构将直接不被查找 + * + * @param excludeTypes 不扫描的类型 + * @return 当前实例 + */ + public TypeAnnotationScanner addExcludeTypes(Class... excludeTypes) { + CollUtil.addAll(this.excludeTypes, excludeTypes); + return this; + } + + /** + * 添加转换器 + * + * @param converter 转换器 + * @return 当前实例 + * @see JdkProxyClassConverter + */ + public TypeAnnotationScanner addConverters(UnaryOperator> converter) { + Assert.notNull(converter, "converter must not null"); + this.converters.add(converter); + if (!this.hasConverters) { + this.hasConverters = true; + } + return this; + } + + /** + * 是否允许扫描父类 + * + * @param includeSupperClass 是否 + * @return 当前实例 + */ + public TypeAnnotationScanner setIncludeSupperClass(boolean includeSupperClass) { + this.includeSupperClass = includeSupperClass; + return this; + } + + /** + * 是否允许扫描父接口 + * + * @param includeInterfaces 是否 + * @return 当前实例 + */ + public TypeAnnotationScanner setIncludeInterfaces(boolean includeInterfaces) { + this.includeInterfaces = includeInterfaces; + return this; + } + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Class; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return scan((Class)annotatedElement).stream() + .map(Class::getAnnotations) + .flatMap(Stream::of) + .filter(a -> !AnnotationUtil.isJdkMateAnnotation(a.annotationType())) + .collect(Collectors.toList()); + } + + private Class convert(Class target) { + if (hasConverters) { + converters.forEach(c -> c.apply(target)); + } + return target; + } + + /** + * 递归遍历当前类、父类及其实现的父接口 + * + * @param targetClass 类 + */ + private Set> scan(Class targetClass) { + Deque> classDeque = CollUtil.newLinkedList(targetClass); + Set> accessedTypes = new HashSet<>(); + while (!classDeque.isEmpty()) { + Class target = convert(classDeque.removeFirst()); + // 若当前类已经访问过,则无需再次处理 + if (ObjectUtil.isNull(target) || accessedTypes.contains(target) || excludeTypes.contains(target) || filter.negate().test(target)) { + continue; + } + accessedTypes.add(target); + + // 扫描父类 + if (includeSupperClass) { + Class superClass = target.getSuperclass(); + if (!ObjectUtil.equals(superClass, Object.class) && ObjectUtil.isNotNull(superClass)) { + classDeque.addLast(superClass); + } + } + + // 扫描接口 + if (includeInterfaces) { + Class[] interfaces = target.getInterfaces(); + if (ArrayUtil.isNotEmpty(interfaces)) { + CollUtil.addAll(classDeque, interfaces); + } + } + } + return accessedTypes; + } + + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? sourceClass.getSuperclass() : sourceClass; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java new file mode 100644 index 000000000..d032a516b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java @@ -0,0 +1,15 @@ +package cn.hutool.core.annotation.scanner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author huangchengxing + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) +@interface AnnotationForScannerTest { + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java new file mode 100644 index 000000000..59af613bd --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java @@ -0,0 +1,33 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.List; + +/** + * @author huangchengxing + */ +public class FieldAnnotationScannerTest { + + @Test + public void testFieldAnnotationScanner() { + FieldAnnotationScanner scanner = new FieldAnnotationScanner(); + Field field = ReflectUtil.getField(Example.class, "id"); + Assert.assertNotNull(field); + Assert.assertTrue(scanner.support(field)); + List annotations = scanner.getAnnotations(field); + Assert.assertEquals(1, annotations.size()); + Assert.assertEquals(AnnotationForScannerTest.class, CollUtil.getFirst(annotations).annotationType()); + } + + public static class Example { + @AnnotationForScannerTest + private Integer id; + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java new file mode 100644 index 000000000..a787670de --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java @@ -0,0 +1,55 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author huangchengxing + * @date 2022/06/10 16:51 + */ +public class MateAnnotationScannerTest { + + @Test + public void testMateAnnotationScanner() { + AnnotationScanner scanner = new MateAnnotationScanner(); + Assert.assertTrue(scanner.support(AnnotationForScannerTest3.class)); + Map, Annotation> annotations = CollUtil.toMap(scanner.getAnnotations(AnnotationForScannerTest3.class), new HashMap<>(), Annotation::annotationType); + Assert.assertEquals(3, annotations.size()); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest.class)); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest1.class)); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest2.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest3.class)); + + scanner = new MateAnnotationScanner(false); + Assert.assertTrue(scanner.support(AnnotationForScannerTest3.class)); + annotations = CollUtil.toMap(scanner.getAnnotations(AnnotationForScannerTest3.class), new HashMap<>(), Annotation::annotationType); + Assert.assertEquals(1, annotations.size()); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest2.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest1.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest3.class)); + } + + @AnnotationForScannerTest3 + static class Example {} + + @AnnotationForScannerTest + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest1 {} + + @AnnotationForScannerTest1 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest2 {} + + @AnnotationForScannerTest2 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest3 {} +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java new file mode 100644 index 000000000..25798c84d --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java @@ -0,0 +1,35 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author huangchengxing + */ +public class MethodAnnotationScannerTest { + + @Test + public void testMethodAnnotationScanner() { + AnnotationScanner scanner = new MethodAnnotationScanner(); + Method method = ReflectUtil.getMethod(Example.class, "test"); + Assert.assertNotNull(method); + Assert.assertTrue(scanner.support(method)); + List annotations = scanner.getAnnotations(method); + Assert.assertEquals(1, annotations.size()); + Assert.assertEquals(CollUtil.getFirst(annotations).annotationType(), AnnotationForScannerTest.class); + } + + static class Example { + @AnnotationForScannerTest + public void test() { + + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java new file mode 100644 index 000000000..1df0727d1 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java @@ -0,0 +1,62 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.util.ClassUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * @author huangchengxing + * @date 2022/06/10 16:51 + */ +public class TypeAnnotationScannerTest { + + @Test + public void testTypeAnnotationScanner() { + AnnotationScanner scanner = new TypeAnnotationScanner(); + Assert.assertTrue(scanner.support(Example.class)); + List annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(3, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找父接口 + scanner = new TypeAnnotationScanner().setIncludeInterfaces(false); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(2, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找父类 + scanner = new TypeAnnotationScanner().setIncludeSupperClass(false); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(1, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找ExampleSupplerClass.class + scanner = new TypeAnnotationScanner().addExcludeTypes(ExampleSupplerClass.class); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(1, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 只查找ExampleSupplerClass.class + scanner = new TypeAnnotationScanner().setFilter(t -> ClassUtil.isAssignable(ExampleSupplerClass.class, t)); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(2, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + } + + @AnnotationForScannerTest + static class ExampleSupplerClass implements ExampleInterface {} + + @AnnotationForScannerTest + interface ExampleInterface {} + + @AnnotationForScannerTest + static class Example extends ExampleSupplerClass {} + +} From 7ca4a4ffc10869048dc4d24beef5410adf1db836 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:39:05 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E5=90=88=E6=88=90=E6=B3=A8=E8=A7=A3;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 50 +++ .../core/annotation/SyntheticAnnotation.java | 335 ++++++++++++++++++ .../annotation/SyntheticAnnotationTest.java | 81 +++++ 3 files changed, 466 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 5d99bdf92..1a5f24450 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -2,7 +2,11 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import java.lang.annotation.Annotation; @@ -18,7 +22,10 @@ import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 注解工具类
@@ -348,4 +355,47 @@ public class AnnotationUtil { final T annotation = getAnnotation(annotationEle, annotationType); return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + + /** + * 方法是否为注解属性方法。
+ * 方法无参数,且有返回值的方法认为是注解属性的方法。 + * + * @param method 方法 + */ + static boolean isAttributeMethod(Method method) { + return method.getParameterCount() == 0 && method.getReturnType() != void.class; + } + + /** + * 获取注解的全部属性值获取方法 + * + * @param annotationType 注解 + * @return 注解的全部属性值 + * @throws IllegalArgumentException 当别名属性在注解中不存在,或别名属性的值与原属性的值类型不一致时抛出 + */ + static Map getAttributeMethods(Class annotationType) { + // 获取全部注解属性值 + Map attributeMethods = Stream.of(annotationType.getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .collect(Collectors.toMap(Method::getName, Function.identity())); + // 处理别名 + attributeMethods.forEach((methodName, method) -> { + String alias = Opt.ofNullable(method.getAnnotation(Alias.class)) + .map(Alias::value) + .orElse(null); + if (ObjectUtil.isNull(alias)) { + return; + } + // 存在别名,则将原本的值替换为别名对应的值 + Assert.isTrue(attributeMethods.containsKey(alias), "No method for alias: [{}]", alias); + Method aliasAttributeMethod = attributeMethods.get(alias); + Assert.isTrue( + ObjectUtil.isNull(aliasAttributeMethod) || ClassUtil.isAssignable(method.getReturnType(), aliasAttributeMethod.getReturnType()), + "Return type of the alias method [{}] is inconsistent with the original [{}]", + aliasAttributeMethod.getClass(), method.getParameterTypes() + ); + attributeMethods.put(methodName, aliasAttributeMethod); + }); + return attributeMethods; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java new file mode 100644 index 000000000..2df329754 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java @@ -0,0 +1,335 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.annotation.scanner.MateAnnotationScanner; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 表示一个根注解与根注解上的多层元注解合成的注解 + * + *

假设现有根注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,X作为新的根注解,则CBA都是X的元注解。
+ * 通过{@link #isAnnotationPresent(Class)}可确定指定类型是注解是否是该合成注解的元注解,即是否为当前实例的“父类”。 + * 若指定注解是当前实例的元注解,则通过{@link #getAnnotation(Class)}可获得动态代理生成的对应的注解实例。
+ * 需要注意的是,由于认为合并注解X以最初的根注解A作为元注解,因此{@link #getAnnotations()}或{@link #getDeclaredAnnotations()} + * 都将只能获得A。 + * + *

不同层级的元注解可能存在同名的属性,此时,若认为该合成注解X在第0层,则根注解A在第1层,B在第2层......以此类推。 + * 层级越低的注解中离根注解距离近,则属性优先级越高,即遵循“就近原则”。
+ * 举个例子:若CBA同时存在属性y,则将X视为C,B或者A时,获得的y属性的值都与最底层元注解A的值保持一致。 + * 同理,不同层级可能会出现相同的元注解,比如A注解存在元注解B,C,但是C又存在元注解B,因此根据就近原则,A上的元注解B将优先于C上的元注解B生效。 + * 若两相同注解处于同一层级,则按照从其上一级“子注解”的{@link AnnotatedElement#getAnnotations()}的调用顺序排序。
+ * {@link #getAnnotation(Class)}获得的代理类实例的属性值遵循该规则。 + * + *

别名在合成注解中仍然有效,若注解X中任意属性上存在{@link Alias}注解,则{@link Alias#value()}指定的属性值将会覆盖注解属性的本身的值。
+ * {@link Alias}注解仅能指定注解X中存在的属性作为别名,不允许指定元注解或子类注解的属性。 + * + * @author huangchengxing + * @see AnnotationUtil + */ +public class SyntheticAnnotation implements Annotation, AnnotatedElement { + + /** + * 根注解,即当前查找的注解 + */ + private final A source; + + /** + * 包含根注解以及其元注解在内的全部注解实例 + */ + private final Map, MetaAnnotation> metaAnnotationMap; + + /** + * 属性值缓存 + */ + private final Map attributeCaches; + + /** + * 构造 + * + * @param annotation 当前查找的注解类 + */ + SyntheticAnnotation(A annotation) { + this.source = annotation; + this.metaAnnotationMap = new LinkedHashMap<>(); + this.attributeCaches = new HashMap<>(); + loadMetaAnnotations(); // TODO 是否可以添加注解类对应的元注解信息缓存,避免每次都要解析? + } + + /** + * 基于指定根注解,构建包括其元注解在内的合成注解 + * + * @param rootAnnotation 根注解 + * @return 合成注解 + */ + public static SyntheticAnnotation of(T rootAnnotation) { + return new SyntheticAnnotation<>(rootAnnotation); + } + + /** + * 获取根注解 + * + * @return 根注解 + */ + public A getSource() { + return source; + } + + /** + * 获取已解析的元注解信息 + * + * @return 已解析的元注解信息 + */ + Map, MetaAnnotation> getMetaAnnotationMap() { + return metaAnnotationMap; + } + + /** + * 获取根注解类型 + * + * @return java.lang.Class + */ + @Override + public Class annotationType() { + return getSource().annotationType(); + } + + /** + * 获取属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 + *

当不同层级的注解之间存在同名属性时,将优先获取更接近根注解的属性 + * + * @param attributeName 属性名 + */ + public Object getAttribute(String attributeName) { + return attributeCaches.computeIfAbsent(attributeName, a -> metaAnnotationMap.values() + .stream() + .filter(ma -> ma.hasAttribute(attributeName)) // 集合默认是根据distance有序的,故此处无需再排序 + .findFirst() + .map(ma -> ma.getAttribute(attributeName)) + .orElse(null) + ); + } + + /** + * 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @SuppressWarnings("unchecked") + @Override + public T getAnnotation(Class annotationType) { + if (metaAnnotationMap.containsKey(annotationType)) { + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, Synthesized.class}, + new SyntheticAnnotationProxy<>(this, annotationType) + ); + } + return null; + } + + /** + * 获取全部注解 + * + * @return java.lang.annotation.Annotation[] + */ + @Override + public Annotation[] getAnnotations() { + return getMetaAnnotationMap().values().toArray(new MetaAnnotation[0]); + } + + /** + * 获取根注解直接声明注解 + * + * @return 直接声明注解 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[]{ getSource() }; + } + + /** + * 广度优先遍历并缓存该根注解上的全部元注解 + */ + private void loadMetaAnnotations() { + // 若该注解已经是合成注解,则直接使用已解析好的元注解信息 + if (source instanceof SyntheticAnnotation.Synthesized) { + this.metaAnnotationMap.putAll(((Synthesized)source).getMetaAnnotationMap()); + return; + } + // 扫描元注解 + metaAnnotationMap.put(source.annotationType(), new MetaAnnotation(source, 0)); + new MateAnnotationScanner().scan( + (index, annotation) -> metaAnnotationMap.computeIfAbsent( + // 当出现重复的注解时,由于后添加的注解必然层级更高,优先级更低,因此当直接忽略 + annotation.annotationType(), t -> new MetaAnnotation(annotation, index) + ), + source.annotationType(), null + ); + } + + /** + * 元注解包装类 + * + * @author huangchengxing + */ + static class MetaAnnotation implements Annotation { + + private final Annotation annotation; + private final Map attributeMethodCaches; + private final int distance; + + public MetaAnnotation(Annotation annotation, int distance) { + this.annotation = annotation; + this.distance = distance; + this.attributeMethodCaches = AnnotationUtil.getAttributeMethods(annotation.annotationType()); + } + + /** + * 获取注解类型 + * + * @return 注解类型 + */ + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + /** + * 获取元注解 + * + * @return 元注解 + */ + public Annotation get() { + return annotation; + } + + /** + * 获取根注解到元注解的距离 + * + * @return 根注解到元注解的距离 + */ + public int getDistance() { + return distance; + } + + /** + * 元注解是否存在该属性 + * + * @param attributeName 属性名 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName) { + return attributeMethodCaches.containsKey(attributeName); + } + + /** + * 获取元注解的属性值 + * + * @param attributeName 属性名 + * @return 元注解的属性值 + */ + public Object getAttribute(String attributeName) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .map(method -> ReflectUtil.invoke(annotation, method)) + .orElse(null); + } + + } + + /** + * 表示一个已经被合成的注解 + * + * @author huangchengxing + */ + interface Synthesized { + + /** + * 获取合成注解中已解析的元注解信息 + * + * @return 合成注解中已解析的元注解信息 + */ + Map, MetaAnnotation> getMetaAnnotationMap(); + + static boolean isMetaAnnotationMapMethod(Method method) { + return StrUtil.equals("getMetaAnnotationMap", method.getName()); + } + + } + + /** + * 合成注解代理类 + * + * @author huangchengxing + */ + static class SyntheticAnnotationProxy implements Annotation, InvocationHandler { + + private final Class annotationType; + private final SyntheticAnnotation syntheticAnnotation; + + public SyntheticAnnotationProxy(SyntheticAnnotation syntheticAnnotation, Class annotationType) { + this.syntheticAnnotation = syntheticAnnotation; + this.annotationType = annotationType; + } + + @Override + public Class annotationType() { + return annotationType; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Synthesized.isMetaAnnotationMapMethod(method)) { + return syntheticAnnotation.getMetaAnnotationMap(); + } + if (ReflectUtil.isHashCodeMethod(method)) { + return getHashCode(); + } + if (ReflectUtil.isToStringMethod(method)) { + return getToString(); + } + return ObjectUtil.defaultIfNull( + syntheticAnnotation.getAttribute(method.getName()), + () -> ReflectUtil.invoke(this, method, args) + ); + } + + /** + * 获取toString值 + * + * @return toString值 + */ + private String getToString() { + String attributes = Stream.of(annotationType().getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName()))) + .collect(Collectors.joining(", ")); + return StrUtil.format("@{}({})", annotationType().getName(), attributes); + } + + /** + * 获取hashcode值 + * + * @return hashcode值 + */ + private int getHashCode() { + return Objects.hash((Object)syntheticAnnotation.getAnnotations()); + } + + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java new file mode 100644 index 000000000..a81147e2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java @@ -0,0 +1,81 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.util.Map; + +/** + * 合成注解{@link SyntheticAnnotation}的测试用例 + * + * @author huangchengxing + */ +public class SyntheticAnnotationTest { + + @Test + public void testSynthesisAnnotation() { + ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + SyntheticAnnotation syntheticAnnotation = SyntheticAnnotation.of(rootAnnotation); + Assert.assertEquals(syntheticAnnotation.getSource(), rootAnnotation); + Assert.assertEquals(syntheticAnnotation.annotationType(), rootAnnotation.annotationType()); + Assert.assertEquals(1, syntheticAnnotation.getDeclaredAnnotations().length); + Assert.assertEquals(syntheticAnnotation.getDeclaredAnnotations()[0], rootAnnotation); + Assert.assertEquals(3, syntheticAnnotation.getAnnotations().length); + + Assert.assertEquals(syntheticAnnotation.getAttribute("childValue"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue"), "Child's Parent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue"), "Child's GrandParent!"); + + Map, SyntheticAnnotation.MetaAnnotation> annotationMap = syntheticAnnotation.getMetaAnnotationMap(); + ChildAnnotation childAnnotation = syntheticAnnotation.getAnnotation(ChildAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ChildAnnotation.class)); + Assert.assertNotNull(childAnnotation); + Assert.assertEquals(childAnnotation.childValue(), "Child!"); + Assert.assertEquals(childAnnotation.childValueAlias(), "Child!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(childAnnotation).getMetaAnnotationMap()); + + ParentAnnotation parentAnnotation = syntheticAnnotation.getAnnotation(ParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ParentAnnotation.class)); + Assert.assertNotNull(parentAnnotation); + Assert.assertEquals(parentAnnotation.parentValue(), "Child's Parent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(parentAnnotation).getMetaAnnotationMap()); + + GrandParentAnnotation grandParentAnnotation = syntheticAnnotation.getAnnotation(GrandParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); + Assert.assertNotNull(grandParentAnnotation); + Assert.assertEquals(grandParentAnnotation.grandParentValue(), "Child's GrandParent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(grandParentAnnotation).getMetaAnnotationMap()); + } + + // 注解结构如下: + // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation + // -> @GrandParentAnnotation + @ChildAnnotation(childValueAlias = "Child!") + class AnnotatedClass {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE }) + @interface GrandParentAnnotation { + String grandParentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + @interface ParentAnnotation { + String parentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 + @ParentAnnotation(parentValue = "Child's Parent!") // 覆盖元注解@ParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface ChildAnnotation { + String childValueAlias() default ""; + @Alias("childValueAlias") + String childValue() default ""; + } + +} From ff30bdf1ea8986b7a38b96fa65054376ee28fa50 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:56:26 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E7=9A=84=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 1a5f24450..5c3f217aa 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.annotation; +import cn.hutool.core.annotation.scanner.*; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; @@ -19,9 +20,7 @@ import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -356,6 +355,43 @@ public class AnnotationUtil { return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + /** + * 将指定注解实例与其元注解转为合成注解 + * + * @param annotation 注解 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 合成注解 + * @see SyntheticAnnotation + */ + public static T getSynthesisAnnotation(Annotation annotation, Class annotationType) { + return SyntheticAnnotation.of(annotation).getAnnotation(annotationType); + } + + /** + * 获取元素上所有指定注解 + *

+ * + * @param annotatedElement 可注解元素 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + * @see SyntheticAnnotation + */ + public static List getAllSynthesisAnnotations(AnnotatedElement annotatedElement, Class annotationType) { + AnnotationScanner[] scanners = new AnnotationScanner[] { + new MateAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner() + }; + return AnnotationScanner.scanByAnySupported(annotatedElement, scanners).stream() + .map(SyntheticAnnotation::of) + .map(annotation -> annotation.getAnnotation(annotationType)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + /** * 方法是否为注解属性方法。
* 方法无参数,且有返回值的方法认为是注解属性的方法。 From 0e2e894a3ec6c3ffa04bc910a329d2fd65f0898d Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 17:25:57 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=90=88=E6=88=90=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E8=B0=83=E6=95=B4=E4=B8=BA=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E5=90=8D=E7=A7=B0=E5=92=8C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=A6=86=E7=9B=96;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/SyntheticAnnotation.java | 38 ++++++++++++++----- .../annotation/SyntheticAnnotationTest.java | 18 ++++++--- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java index 2df329754..3db0bacfc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java @@ -2,6 +2,8 @@ package cn.hutool.core.annotation; import cn.hutool.core.annotation.scanner.MateAnnotationScanner; import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +23,7 @@ import java.util.stream.Stream; /** * 表示一个根注解与根注解上的多层元注解合成的注解 * - *

假设现有根注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,X作为新的根注解,则CBA都是X的元注解。
+ *

假设现有注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,则CBA都是X的元注解,X为根注解。
* 通过{@link #isAnnotationPresent(Class)}可确定指定类型是注解是否是该合成注解的元注解,即是否为当前实例的“父类”。 * 若指定注解是当前实例的元注解,则通过{@link #getAnnotation(Class)}可获得动态代理生成的对应的注解实例。
* 需要注意的是,由于认为合并注解X以最初的根注解A作为元注解,因此{@link #getAnnotations()}或{@link #getDeclaredAnnotations()} @@ -34,6 +36,8 @@ import java.util.stream.Stream; * 若两相同注解处于同一层级,则按照从其上一级“子注解”的{@link AnnotatedElement#getAnnotations()}的调用顺序排序。
* {@link #getAnnotation(Class)}获得的代理类实例的属性值遵循该规则。 * + *

同名属性将根据类型彼此隔离,即当不同层级的元注解存在同名的属性,但是属性类型不同时,此时低层级的属性并不会覆盖高层级注解的属性。 + * *

别名在合成注解中仍然有效,若注解X中任意属性上存在{@link Alias}注解,则{@link Alias#value()}指定的属性值将会覆盖注解属性的本身的值。
* {@link Alias}注解仅能指定注解X中存在的属性作为别名,不允许指定元注解或子类注解的属性。 * @@ -55,7 +59,7 @@ public class SyntheticAnnotation
implements Annotation, An /** * 属性值缓存 */ - private final Map attributeCaches; + private final Map, Object>> attributeCaches; /** * 构造 @@ -108,15 +112,16 @@ public class SyntheticAnnotation implements Annotation, An } /** - * 获取属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 - *

当不同层级的注解之间存在同名属性时,将优先获取更接近根注解的属性 + * 根据指定的属性名与属性类型获取对应的属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 + *

当不同层级的注解之间存在同名同类型属性时,将优先获取更接近根注解的属性 * * @param attributeName 属性名 */ - public Object getAttribute(String attributeName) { - return attributeCaches.computeIfAbsent(attributeName, a -> metaAnnotationMap.values() + public Object getAttribute(String attributeName, Class attributeType) { + Map, Object> values = attributeCaches.computeIfAbsent(attributeName, t -> MapUtil.newHashMap()); + return values.computeIfAbsent(attributeType, a -> metaAnnotationMap.values() .stream() - .filter(ma -> ma.hasAttribute(attributeName)) // 集合默认是根据distance有序的,故此处无需再排序 + .filter(ma -> ma.hasAttribute(attributeName, attributeType)) // 集合默认是根据distance有序的,故此处无需再排序 .findFirst() .map(ma -> ma.getAttribute(attributeName)) .orElse(null) @@ -154,7 +159,7 @@ public class SyntheticAnnotation implements Annotation, An } /** - * 获取根注解直接声明注解 + * 获取根注解直接声明的注解,该方法正常情况下当只返回原注解 * * @return 直接声明注解 */ @@ -238,6 +243,19 @@ public class SyntheticAnnotation implements Annotation, An return attributeMethodCaches.containsKey(attributeName); } + /** + * 元注解是否存在该属性,且该属性的值类型是指定类型或其子类 + * + * @param attributeName 属性名 + * @param returnType 返回值类型 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName, Class returnType) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .filter(method -> ClassUtil.isAssignable(returnType, method.getReturnType())) + .isPresent(); + } + /** * 获取元注解的属性值 * @@ -304,7 +322,7 @@ public class SyntheticAnnotation implements Annotation, An return getToString(); } return ObjectUtil.defaultIfNull( - syntheticAnnotation.getAttribute(method.getName()), + syntheticAnnotation.getAttribute(method.getName(), method.getReturnType()), () -> ReflectUtil.invoke(this, method, args) ); } @@ -317,7 +335,7 @@ public class SyntheticAnnotation implements Annotation, An private String getToString() { String attributes = Stream.of(annotationType().getDeclaredMethods()) .filter(AnnotationUtil::isAttributeMethod) - .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName()))) + .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName(), method.getReturnType()))) .collect(Collectors.joining(", ")); return StrUtil.format("@{}({})", annotationType().getName(), attributes); } diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java index a81147e2e..8e1db5d3f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java @@ -23,10 +23,10 @@ public class SyntheticAnnotationTest { Assert.assertEquals(syntheticAnnotation.getDeclaredAnnotations()[0], rootAnnotation); Assert.assertEquals(3, syntheticAnnotation.getAnnotations().length); - Assert.assertEquals(syntheticAnnotation.getAttribute("childValue"), "Child!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias"), "Child!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue"), "Child's Parent!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue"), "Child's GrandParent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValue", String.class), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias", String.class), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue", String.class), "Child's Parent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue", String.class), "Child's GrandParent!"); Map, SyntheticAnnotation.MetaAnnotation> annotationMap = syntheticAnnotation.getMetaAnnotationMap(); ChildAnnotation childAnnotation = syntheticAnnotation.getAnnotation(ChildAnnotation.class); @@ -34,31 +34,35 @@ public class SyntheticAnnotationTest { Assert.assertNotNull(childAnnotation); Assert.assertEquals(childAnnotation.childValue(), "Child!"); Assert.assertEquals(childAnnotation.childValueAlias(), "Child!"); + Assert.assertEquals(childAnnotation.grandParentType(), Integer.class); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(childAnnotation).getMetaAnnotationMap()); ParentAnnotation parentAnnotation = syntheticAnnotation.getAnnotation(ParentAnnotation.class); Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ParentAnnotation.class)); Assert.assertNotNull(parentAnnotation); Assert.assertEquals(parentAnnotation.parentValue(), "Child's Parent!"); + Assert.assertEquals(parentAnnotation.grandParentType(), "java.lang.Void"); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(parentAnnotation).getMetaAnnotationMap()); GrandParentAnnotation grandParentAnnotation = syntheticAnnotation.getAnnotation(GrandParentAnnotation.class); Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); Assert.assertNotNull(grandParentAnnotation); Assert.assertEquals(grandParentAnnotation.grandParentValue(), "Child's GrandParent!"); + Assert.assertEquals(grandParentAnnotation.grandParentType(), Integer.class); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(grandParentAnnotation).getMetaAnnotationMap()); } // 注解结构如下: // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation // -> @GrandParentAnnotation - @ChildAnnotation(childValueAlias = "Child!") - class AnnotatedClass {} + @ChildAnnotation(childValueAlias = "Child!", grandParentType = Integer.class) + static class AnnotatedClass {} @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.ANNOTATION_TYPE }) @interface GrandParentAnnotation { String grandParentValue() default ""; + Class grandParentType() default Void.class; } @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 @@ -66,6 +70,7 @@ public class SyntheticAnnotationTest { @Target({ ElementType.TYPE }) @interface ParentAnnotation { String parentValue() default ""; + String grandParentType() default "java.lang.Void"; } @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 @@ -76,6 +81,7 @@ public class SyntheticAnnotationTest { String childValueAlias() default ""; @Alias("childValueAlias") String childValue() default ""; + Class grandParentType() default Void.class; } } From 686de3ac0ccee8f6c16ff73c2de693a9b1c7feb9 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:39:05 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=88=E6=88=90?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 50 +++ .../core/annotation/SyntheticAnnotation.java | 335 ++++++++++++++++++ .../annotation/SyntheticAnnotationTest.java | 81 +++++ 3 files changed, 466 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 5d99bdf92..1a5f24450 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -2,7 +2,11 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import java.lang.annotation.Annotation; @@ -18,7 +22,10 @@ import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 注解工具类
@@ -348,4 +355,47 @@ public class AnnotationUtil { final T annotation = getAnnotation(annotationEle, annotationType); return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + + /** + * 方法是否为注解属性方法。
+ * 方法无参数,且有返回值的方法认为是注解属性的方法。 + * + * @param method 方法 + */ + static boolean isAttributeMethod(Method method) { + return method.getParameterCount() == 0 && method.getReturnType() != void.class; + } + + /** + * 获取注解的全部属性值获取方法 + * + * @param annotationType 注解 + * @return 注解的全部属性值 + * @throws IllegalArgumentException 当别名属性在注解中不存在,或别名属性的值与原属性的值类型不一致时抛出 + */ + static Map getAttributeMethods(Class annotationType) { + // 获取全部注解属性值 + Map attributeMethods = Stream.of(annotationType.getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .collect(Collectors.toMap(Method::getName, Function.identity())); + // 处理别名 + attributeMethods.forEach((methodName, method) -> { + String alias = Opt.ofNullable(method.getAnnotation(Alias.class)) + .map(Alias::value) + .orElse(null); + if (ObjectUtil.isNull(alias)) { + return; + } + // 存在别名,则将原本的值替换为别名对应的值 + Assert.isTrue(attributeMethods.containsKey(alias), "No method for alias: [{}]", alias); + Method aliasAttributeMethod = attributeMethods.get(alias); + Assert.isTrue( + ObjectUtil.isNull(aliasAttributeMethod) || ClassUtil.isAssignable(method.getReturnType(), aliasAttributeMethod.getReturnType()), + "Return type of the alias method [{}] is inconsistent with the original [{}]", + aliasAttributeMethod.getClass(), method.getParameterTypes() + ); + attributeMethods.put(methodName, aliasAttributeMethod); + }); + return attributeMethods; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java new file mode 100644 index 000000000..2df329754 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java @@ -0,0 +1,335 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.annotation.scanner.MateAnnotationScanner; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 表示一个根注解与根注解上的多层元注解合成的注解 + * + *

假设现有根注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,X作为新的根注解,则CBA都是X的元注解。
+ * 通过{@link #isAnnotationPresent(Class)}可确定指定类型是注解是否是该合成注解的元注解,即是否为当前实例的“父类”。 + * 若指定注解是当前实例的元注解,则通过{@link #getAnnotation(Class)}可获得动态代理生成的对应的注解实例。
+ * 需要注意的是,由于认为合并注解X以最初的根注解A作为元注解,因此{@link #getAnnotations()}或{@link #getDeclaredAnnotations()} + * 都将只能获得A。 + * + *

不同层级的元注解可能存在同名的属性,此时,若认为该合成注解X在第0层,则根注解A在第1层,B在第2层......以此类推。 + * 层级越低的注解中离根注解距离近,则属性优先级越高,即遵循“就近原则”。
+ * 举个例子:若CBA同时存在属性y,则将X视为C,B或者A时,获得的y属性的值都与最底层元注解A的值保持一致。 + * 同理,不同层级可能会出现相同的元注解,比如A注解存在元注解B,C,但是C又存在元注解B,因此根据就近原则,A上的元注解B将优先于C上的元注解B生效。 + * 若两相同注解处于同一层级,则按照从其上一级“子注解”的{@link AnnotatedElement#getAnnotations()}的调用顺序排序。
+ * {@link #getAnnotation(Class)}获得的代理类实例的属性值遵循该规则。 + * + *

别名在合成注解中仍然有效,若注解X中任意属性上存在{@link Alias}注解,则{@link Alias#value()}指定的属性值将会覆盖注解属性的本身的值。
+ * {@link Alias}注解仅能指定注解X中存在的属性作为别名,不允许指定元注解或子类注解的属性。 + * + * @author huangchengxing + * @see AnnotationUtil + */ +public class SyntheticAnnotation
implements Annotation, AnnotatedElement { + + /** + * 根注解,即当前查找的注解 + */ + private final A source; + + /** + * 包含根注解以及其元注解在内的全部注解实例 + */ + private final Map, MetaAnnotation> metaAnnotationMap; + + /** + * 属性值缓存 + */ + private final Map attributeCaches; + + /** + * 构造 + * + * @param annotation 当前查找的注解类 + */ + SyntheticAnnotation(A annotation) { + this.source = annotation; + this.metaAnnotationMap = new LinkedHashMap<>(); + this.attributeCaches = new HashMap<>(); + loadMetaAnnotations(); // TODO 是否可以添加注解类对应的元注解信息缓存,避免每次都要解析? + } + + /** + * 基于指定根注解,构建包括其元注解在内的合成注解 + * + * @param rootAnnotation 根注解 + * @return 合成注解 + */ + public static SyntheticAnnotation of(T rootAnnotation) { + return new SyntheticAnnotation<>(rootAnnotation); + } + + /** + * 获取根注解 + * + * @return 根注解 + */ + public A getSource() { + return source; + } + + /** + * 获取已解析的元注解信息 + * + * @return 已解析的元注解信息 + */ + Map, MetaAnnotation> getMetaAnnotationMap() { + return metaAnnotationMap; + } + + /** + * 获取根注解类型 + * + * @return java.lang.Class + */ + @Override + public Class annotationType() { + return getSource().annotationType(); + } + + /** + * 获取属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 + *

当不同层级的注解之间存在同名属性时,将优先获取更接近根注解的属性 + * + * @param attributeName 属性名 + */ + public Object getAttribute(String attributeName) { + return attributeCaches.computeIfAbsent(attributeName, a -> metaAnnotationMap.values() + .stream() + .filter(ma -> ma.hasAttribute(attributeName)) // 集合默认是根据distance有序的,故此处无需再排序 + .findFirst() + .map(ma -> ma.getAttribute(attributeName)) + .orElse(null) + ); + } + + /** + * 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @SuppressWarnings("unchecked") + @Override + public T getAnnotation(Class annotationType) { + if (metaAnnotationMap.containsKey(annotationType)) { + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, Synthesized.class}, + new SyntheticAnnotationProxy<>(this, annotationType) + ); + } + return null; + } + + /** + * 获取全部注解 + * + * @return java.lang.annotation.Annotation[] + */ + @Override + public Annotation[] getAnnotations() { + return getMetaAnnotationMap().values().toArray(new MetaAnnotation[0]); + } + + /** + * 获取根注解直接声明注解 + * + * @return 直接声明注解 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[]{ getSource() }; + } + + /** + * 广度优先遍历并缓存该根注解上的全部元注解 + */ + private void loadMetaAnnotations() { + // 若该注解已经是合成注解,则直接使用已解析好的元注解信息 + if (source instanceof SyntheticAnnotation.Synthesized) { + this.metaAnnotationMap.putAll(((Synthesized)source).getMetaAnnotationMap()); + return; + } + // 扫描元注解 + metaAnnotationMap.put(source.annotationType(), new MetaAnnotation(source, 0)); + new MateAnnotationScanner().scan( + (index, annotation) -> metaAnnotationMap.computeIfAbsent( + // 当出现重复的注解时,由于后添加的注解必然层级更高,优先级更低,因此当直接忽略 + annotation.annotationType(), t -> new MetaAnnotation(annotation, index) + ), + source.annotationType(), null + ); + } + + /** + * 元注解包装类 + * + * @author huangchengxing + */ + static class MetaAnnotation implements Annotation { + + private final Annotation annotation; + private final Map attributeMethodCaches; + private final int distance; + + public MetaAnnotation(Annotation annotation, int distance) { + this.annotation = annotation; + this.distance = distance; + this.attributeMethodCaches = AnnotationUtil.getAttributeMethods(annotation.annotationType()); + } + + /** + * 获取注解类型 + * + * @return 注解类型 + */ + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + /** + * 获取元注解 + * + * @return 元注解 + */ + public Annotation get() { + return annotation; + } + + /** + * 获取根注解到元注解的距离 + * + * @return 根注解到元注解的距离 + */ + public int getDistance() { + return distance; + } + + /** + * 元注解是否存在该属性 + * + * @param attributeName 属性名 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName) { + return attributeMethodCaches.containsKey(attributeName); + } + + /** + * 获取元注解的属性值 + * + * @param attributeName 属性名 + * @return 元注解的属性值 + */ + public Object getAttribute(String attributeName) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .map(method -> ReflectUtil.invoke(annotation, method)) + .orElse(null); + } + + } + + /** + * 表示一个已经被合成的注解 + * + * @author huangchengxing + */ + interface Synthesized { + + /** + * 获取合成注解中已解析的元注解信息 + * + * @return 合成注解中已解析的元注解信息 + */ + Map, MetaAnnotation> getMetaAnnotationMap(); + + static boolean isMetaAnnotationMapMethod(Method method) { + return StrUtil.equals("getMetaAnnotationMap", method.getName()); + } + + } + + /** + * 合成注解代理类 + * + * @author huangchengxing + */ + static class SyntheticAnnotationProxy implements Annotation, InvocationHandler { + + private final Class annotationType; + private final SyntheticAnnotation syntheticAnnotation; + + public SyntheticAnnotationProxy(SyntheticAnnotation syntheticAnnotation, Class annotationType) { + this.syntheticAnnotation = syntheticAnnotation; + this.annotationType = annotationType; + } + + @Override + public Class annotationType() { + return annotationType; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Synthesized.isMetaAnnotationMapMethod(method)) { + return syntheticAnnotation.getMetaAnnotationMap(); + } + if (ReflectUtil.isHashCodeMethod(method)) { + return getHashCode(); + } + if (ReflectUtil.isToStringMethod(method)) { + return getToString(); + } + return ObjectUtil.defaultIfNull( + syntheticAnnotation.getAttribute(method.getName()), + () -> ReflectUtil.invoke(this, method, args) + ); + } + + /** + * 获取toString值 + * + * @return toString值 + */ + private String getToString() { + String attributes = Stream.of(annotationType().getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName()))) + .collect(Collectors.joining(", ")); + return StrUtil.format("@{}({})", annotationType().getName(), attributes); + } + + /** + * 获取hashcode值 + * + * @return hashcode值 + */ + private int getHashCode() { + return Objects.hash((Object)syntheticAnnotation.getAnnotations()); + } + + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java new file mode 100644 index 000000000..a81147e2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java @@ -0,0 +1,81 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.util.Map; + +/** + * 合成注解{@link SyntheticAnnotation}的测试用例 + * + * @author huangchengxing + */ +public class SyntheticAnnotationTest { + + @Test + public void testSynthesisAnnotation() { + ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + SyntheticAnnotation syntheticAnnotation = SyntheticAnnotation.of(rootAnnotation); + Assert.assertEquals(syntheticAnnotation.getSource(), rootAnnotation); + Assert.assertEquals(syntheticAnnotation.annotationType(), rootAnnotation.annotationType()); + Assert.assertEquals(1, syntheticAnnotation.getDeclaredAnnotations().length); + Assert.assertEquals(syntheticAnnotation.getDeclaredAnnotations()[0], rootAnnotation); + Assert.assertEquals(3, syntheticAnnotation.getAnnotations().length); + + Assert.assertEquals(syntheticAnnotation.getAttribute("childValue"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue"), "Child's Parent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue"), "Child's GrandParent!"); + + Map, SyntheticAnnotation.MetaAnnotation> annotationMap = syntheticAnnotation.getMetaAnnotationMap(); + ChildAnnotation childAnnotation = syntheticAnnotation.getAnnotation(ChildAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ChildAnnotation.class)); + Assert.assertNotNull(childAnnotation); + Assert.assertEquals(childAnnotation.childValue(), "Child!"); + Assert.assertEquals(childAnnotation.childValueAlias(), "Child!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(childAnnotation).getMetaAnnotationMap()); + + ParentAnnotation parentAnnotation = syntheticAnnotation.getAnnotation(ParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ParentAnnotation.class)); + Assert.assertNotNull(parentAnnotation); + Assert.assertEquals(parentAnnotation.parentValue(), "Child's Parent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(parentAnnotation).getMetaAnnotationMap()); + + GrandParentAnnotation grandParentAnnotation = syntheticAnnotation.getAnnotation(GrandParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); + Assert.assertNotNull(grandParentAnnotation); + Assert.assertEquals(grandParentAnnotation.grandParentValue(), "Child's GrandParent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(grandParentAnnotation).getMetaAnnotationMap()); + } + + // 注解结构如下: + // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation + // -> @GrandParentAnnotation + @ChildAnnotation(childValueAlias = "Child!") + class AnnotatedClass {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE }) + @interface GrandParentAnnotation { + String grandParentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + @interface ParentAnnotation { + String parentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 + @ParentAnnotation(parentValue = "Child's Parent!") // 覆盖元注解@ParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface ChildAnnotation { + String childValueAlias() default ""; + @Alias("childValueAlias") + String childValue() default ""; + } + +} From b8a30fd11b445e0fd78ec94f3eac5ef1710ac0ae Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 15:56:26 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E7=9A=84=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 1a5f24450..5c3f217aa 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.annotation; +import cn.hutool.core.annotation.scanner.*; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; @@ -19,9 +20,7 @@ import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -356,6 +355,43 @@ public class AnnotationUtil { return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + /** + * 将指定注解实例与其元注解转为合成注解 + * + * @param annotation 注解 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 合成注解 + * @see SyntheticAnnotation + */ + public static T getSynthesisAnnotation(Annotation annotation, Class annotationType) { + return SyntheticAnnotation.of(annotation).getAnnotation(annotationType); + } + + /** + * 获取元素上所有指定注解 + *

+ * + * @param annotatedElement 可注解元素 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + * @see SyntheticAnnotation + */ + public static List getAllSynthesisAnnotations(AnnotatedElement annotatedElement, Class annotationType) { + AnnotationScanner[] scanners = new AnnotationScanner[] { + new MateAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner() + }; + return AnnotationScanner.scanByAnySupported(annotatedElement, scanners).stream() + .map(SyntheticAnnotation::of) + .map(annotation -> annotation.getAnnotation(annotationType)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + /** * 方法是否为注解属性方法。
* 方法无参数,且有返回值的方法认为是注解属性的方法。 From 5e402a1dd969df8cef2b54c5fb5a2e4ab15e1494 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 14 Jun 2022 17:25:57 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E5=90=88=E6=88=90=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E8=B0=83=E6=95=B4=E4=B8=BA=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E5=90=8D=E7=A7=B0=E5=92=8C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=A6=86=E7=9B=96;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/SyntheticAnnotation.java | 38 ++++++++++++++----- .../annotation/SyntheticAnnotationTest.java | 18 ++++++--- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java index 2df329754..3db0bacfc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java @@ -2,6 +2,8 @@ package cn.hutool.core.annotation; import cn.hutool.core.annotation.scanner.MateAnnotationScanner; import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +23,7 @@ import java.util.stream.Stream; /** * 表示一个根注解与根注解上的多层元注解合成的注解 * - *

假设现有根注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,X作为新的根注解,则CBA都是X的元注解。
+ *

假设现有注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,则CBA都是X的元注解,X为根注解。
* 通过{@link #isAnnotationPresent(Class)}可确定指定类型是注解是否是该合成注解的元注解,即是否为当前实例的“父类”。 * 若指定注解是当前实例的元注解,则通过{@link #getAnnotation(Class)}可获得动态代理生成的对应的注解实例。
* 需要注意的是,由于认为合并注解X以最初的根注解A作为元注解,因此{@link #getAnnotations()}或{@link #getDeclaredAnnotations()} @@ -34,6 +36,8 @@ import java.util.stream.Stream; * 若两相同注解处于同一层级,则按照从其上一级“子注解”的{@link AnnotatedElement#getAnnotations()}的调用顺序排序。
* {@link #getAnnotation(Class)}获得的代理类实例的属性值遵循该规则。 * + *

同名属性将根据类型彼此隔离,即当不同层级的元注解存在同名的属性,但是属性类型不同时,此时低层级的属性并不会覆盖高层级注解的属性。 + * *

别名在合成注解中仍然有效,若注解X中任意属性上存在{@link Alias}注解,则{@link Alias#value()}指定的属性值将会覆盖注解属性的本身的值。
* {@link Alias}注解仅能指定注解X中存在的属性作为别名,不允许指定元注解或子类注解的属性。 * @@ -55,7 +59,7 @@ public class SyntheticAnnotation
implements Annotation, An /** * 属性值缓存 */ - private final Map attributeCaches; + private final Map, Object>> attributeCaches; /** * 构造 @@ -108,15 +112,16 @@ public class SyntheticAnnotation implements Annotation, An } /** - * 获取属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 - *

当不同层级的注解之间存在同名属性时,将优先获取更接近根注解的属性 + * 根据指定的属性名与属性类型获取对应的属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 + *

当不同层级的注解之间存在同名同类型属性时,将优先获取更接近根注解的属性 * * @param attributeName 属性名 */ - public Object getAttribute(String attributeName) { - return attributeCaches.computeIfAbsent(attributeName, a -> metaAnnotationMap.values() + public Object getAttribute(String attributeName, Class attributeType) { + Map, Object> values = attributeCaches.computeIfAbsent(attributeName, t -> MapUtil.newHashMap()); + return values.computeIfAbsent(attributeType, a -> metaAnnotationMap.values() .stream() - .filter(ma -> ma.hasAttribute(attributeName)) // 集合默认是根据distance有序的,故此处无需再排序 + .filter(ma -> ma.hasAttribute(attributeName, attributeType)) // 集合默认是根据distance有序的,故此处无需再排序 .findFirst() .map(ma -> ma.getAttribute(attributeName)) .orElse(null) @@ -154,7 +159,7 @@ public class SyntheticAnnotation implements Annotation, An } /** - * 获取根注解直接声明注解 + * 获取根注解直接声明的注解,该方法正常情况下当只返回原注解 * * @return 直接声明注解 */ @@ -238,6 +243,19 @@ public class SyntheticAnnotation implements Annotation, An return attributeMethodCaches.containsKey(attributeName); } + /** + * 元注解是否存在该属性,且该属性的值类型是指定类型或其子类 + * + * @param attributeName 属性名 + * @param returnType 返回值类型 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName, Class returnType) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .filter(method -> ClassUtil.isAssignable(returnType, method.getReturnType())) + .isPresent(); + } + /** * 获取元注解的属性值 * @@ -304,7 +322,7 @@ public class SyntheticAnnotation implements Annotation, An return getToString(); } return ObjectUtil.defaultIfNull( - syntheticAnnotation.getAttribute(method.getName()), + syntheticAnnotation.getAttribute(method.getName(), method.getReturnType()), () -> ReflectUtil.invoke(this, method, args) ); } @@ -317,7 +335,7 @@ public class SyntheticAnnotation implements Annotation, An private String getToString() { String attributes = Stream.of(annotationType().getDeclaredMethods()) .filter(AnnotationUtil::isAttributeMethod) - .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName()))) + .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName(), method.getReturnType()))) .collect(Collectors.joining(", ")); return StrUtil.format("@{}({})", annotationType().getName(), attributes); } diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java index a81147e2e..8e1db5d3f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java @@ -23,10 +23,10 @@ public class SyntheticAnnotationTest { Assert.assertEquals(syntheticAnnotation.getDeclaredAnnotations()[0], rootAnnotation); Assert.assertEquals(3, syntheticAnnotation.getAnnotations().length); - Assert.assertEquals(syntheticAnnotation.getAttribute("childValue"), "Child!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias"), "Child!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue"), "Child's Parent!"); - Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue"), "Child's GrandParent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValue", String.class), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias", String.class), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue", String.class), "Child's Parent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue", String.class), "Child's GrandParent!"); Map, SyntheticAnnotation.MetaAnnotation> annotationMap = syntheticAnnotation.getMetaAnnotationMap(); ChildAnnotation childAnnotation = syntheticAnnotation.getAnnotation(ChildAnnotation.class); @@ -34,31 +34,35 @@ public class SyntheticAnnotationTest { Assert.assertNotNull(childAnnotation); Assert.assertEquals(childAnnotation.childValue(), "Child!"); Assert.assertEquals(childAnnotation.childValueAlias(), "Child!"); + Assert.assertEquals(childAnnotation.grandParentType(), Integer.class); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(childAnnotation).getMetaAnnotationMap()); ParentAnnotation parentAnnotation = syntheticAnnotation.getAnnotation(ParentAnnotation.class); Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ParentAnnotation.class)); Assert.assertNotNull(parentAnnotation); Assert.assertEquals(parentAnnotation.parentValue(), "Child's Parent!"); + Assert.assertEquals(parentAnnotation.grandParentType(), "java.lang.Void"); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(parentAnnotation).getMetaAnnotationMap()); GrandParentAnnotation grandParentAnnotation = syntheticAnnotation.getAnnotation(GrandParentAnnotation.class); Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); Assert.assertNotNull(grandParentAnnotation); Assert.assertEquals(grandParentAnnotation.grandParentValue(), "Child's GrandParent!"); + Assert.assertEquals(grandParentAnnotation.grandParentType(), Integer.class); Assert.assertEquals(annotationMap, SyntheticAnnotation.of(grandParentAnnotation).getMetaAnnotationMap()); } // 注解结构如下: // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation // -> @GrandParentAnnotation - @ChildAnnotation(childValueAlias = "Child!") - class AnnotatedClass {} + @ChildAnnotation(childValueAlias = "Child!", grandParentType = Integer.class) + static class AnnotatedClass {} @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.ANNOTATION_TYPE }) @interface GrandParentAnnotation { String grandParentValue() default ""; + Class grandParentType() default Void.class; } @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 @@ -66,6 +70,7 @@ public class SyntheticAnnotationTest { @Target({ ElementType.TYPE }) @interface ParentAnnotation { String parentValue() default ""; + String grandParentType() default "java.lang.Void"; } @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 @@ -76,6 +81,7 @@ public class SyntheticAnnotationTest { String childValueAlias() default ""; @Alias("childValueAlias") String childValue() default ""; + Class grandParentType() default Void.class; } }