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 1/3] =?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 2/3] =?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 3/3] =?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; } }