diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java index 2407de316..34ee2974b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java @@ -93,11 +93,41 @@ import java.util.stream.Stream; * * * + *

可重复注解支持 + *

工具类中格式为findAllXXXgetAllXXX格式的方法, + * 支持获得{@link AnnotatedElement}上的可重复注解。 + * 此处的可重复注解定义包括两方面: + *

+ * + *

缓存 + *

为了避免注解以及{@link AnnotatedElement}层级结构解析过程中的大量反射调用, + * 工具类为{@link AnnotatedElement}及其元注解信息进行了缓存。
+ * 缓存功能默认基于{@link WeakConcurrentMap}实现,会在gc时自动回收部分缓存数据。 + * 但是若有必要,也可以调用{@link #clearCaches()}方法主动清空缓存。 + * * @author huangchengxing * @see ResolvedAnnotationMapping * @see GenericAnnotationMapping * @see HierarchicalAnnotatedElements + * @see RepeatableMetaAnnotatedElement * @see MetaAnnotatedElement + * @see RepeatableAnnotationCollector * @since 6.0.0 */ public class AnnotatedElementUtil { @@ -112,6 +142,16 @@ public class AnnotatedElementUtil { */ private static final Map> ELEMENT_CACHE = new WeakConcurrentMap<>(); + /** + * 不支持属性解析的{@link RepeatableMetaAnnotatedElement}缓存 + */ + private static final Map> RESOLVED_REPEATABLE_ELEMENT_CACHE = new WeakConcurrentMap<>(); + + /** + * 不支持属性解析的{@link RepeatableMetaAnnotatedElement}缓存 + */ + private static final Map> REPEATABLE_ELEMENT_CACHE = new WeakConcurrentMap<>(); + // region ========== find ========== /** @@ -140,7 +180,8 @@ public class AnnotatedElementUtil { } /** - * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有该类型的注解或元注解 + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上直接声明的注解、 + * 这些注解包含的可重复注解,以及上述所有注解的元注解中获取指定类型注解 * * @param element {@link AnnotatedElement} * @param annotationType 注解类型 @@ -148,7 +189,7 @@ public class AnnotatedElementUtil { * @return 注解对象 */ public static T[] findAllAnnotations(final AnnotatedElement element, final Class annotationType) { - return toHierarchyMetaElement(element, false) + return toHierarchyRepeatableMetaElement(element, false) .getAnnotationsByType(annotationType); } @@ -190,7 +231,8 @@ public class AnnotatedElementUtil { } /** - * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有该类型的注解或元注解。
+ * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上直接声明的注解、 + * 这些注解包含的可重复注解,以及上述所有注解的元注解中获取指定类型注解。
* 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 * * @param element {@link AnnotatedElement} @@ -199,7 +241,7 @@ public class AnnotatedElementUtil { * @return 注解对象 */ public static T[] findAllResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { - return toHierarchyMetaElement(element, true) + return toHierarchyRepeatableMetaElement(element, true) .getAnnotationsByType(annotationType); } @@ -221,7 +263,8 @@ public class AnnotatedElementUtil { } /** - * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有该类型的注解 + * 从{@code element}上直接声明的注解、这些注解包含的可重复注解, + * 以及上述所有注解的元注解中获取指定类型注解。 * * @param element {@link AnnotatedElement} * @param annotationType 注解类型 @@ -229,7 +272,7 @@ public class AnnotatedElementUtil { * @return 注解对象 */ public static T[] findAllDirectlyAnnotations(final AnnotatedElement element, final Class annotationType) { - return toHierarchyMetaElement(element, false) + return toHierarchyRepeatableMetaElement(element, false) .getDeclaredAnnotationsByType(annotationType); } @@ -271,7 +314,8 @@ public class AnnotatedElementUtil { } /** - * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有该类型的注解。
+ * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上直接声明的注解、 + * 这些注解包含的可重复注解,以及上述所有注解的元注解中获取指定类型注解。
* 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 * * @param element {@link AnnotatedElement} @@ -280,7 +324,7 @@ public class AnnotatedElementUtil { * @return 注解对象 */ public static T[] findAllDirectlyResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { - return toHierarchyMetaElement(element, true) + return toHierarchyRepeatableMetaElement(element, true) .getDeclaredAnnotationsByType(annotationType); } @@ -324,6 +368,19 @@ public class AnnotatedElementUtil { .getAnnotations(); } + /** + * 从{@code element}上直接声明的注解、这些注解包含的可重复注解,以及上述所有注解的元注解中获取指定类型注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] getAllAnnotations(final AnnotatedElement element, final Class annotationType) { + return toRepeatableMetaElement(element, false) + .getAnnotationsByType(annotationType); + } + /** * 从{@code element}上,获取所有的注解或元注解。
* 得到的注解支持基于{@link Alias}的别名机制。 @@ -350,6 +407,20 @@ public class AnnotatedElementUtil { .getAnnotations(); } + /** + * 从{@code element}上直接声明的注解、这些注解包含的可重复注解,以及上述所有注解的元注解中获取指定类型注解
+ * 得到的注解支持基于{@link Alias}的别名机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] getAllResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { + return toRepeatableMetaElement(element, true) + .getAnnotationsByType(annotationType); + } + // endregion // region ========== get & direct ========== @@ -367,6 +438,19 @@ public class AnnotatedElementUtil { .getDeclaredAnnotation(annotationType); } + /** + * 从{@code element}上直接声明的注解、这些注解包含的可重复注解中获取指定类型注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] getAllDirectlyAnnotations(final AnnotatedElement element, final Class annotationType) { + return toRepeatableMetaElement(element, false) + .getDeclaredAnnotationsByType(annotationType); + } + /** * 从{@code element}上获取所有的注解 * @@ -404,6 +488,19 @@ public class AnnotatedElementUtil { .getDeclaredAnnotations(); } + /** + * 从{@code element}上直接声明的注解、这些注解包含的可重复注解中获取指定类型注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] getAllDirectlyResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { + return toRepeatableMetaElement(element, true) + .getDeclaredAnnotationsByType(annotationType); + } + // endregion // region ========== to element ========== @@ -430,6 +527,29 @@ public class AnnotatedElementUtil { return HierarchicalAnnotatedElements.create(element, (es, e) -> getMetaElementCache(e)); } + /** + *

扫描{@code element}所处层级结构中的{@link AnnotatedElement}, + * 并将其全部转为{@link RepeatableMetaAnnotatedElement}后, + * 再把所有对象合并为{@link HierarchicalAnnotatedElements}。
+ * 得到的对象可访问{@code element}所处层级结构中所有{@link AnnotatedElement}上的直接声明的注解, + * 这些注解包含的可重复注解,以及上述注解的所有元注解。 + * + * @param element 元素 + * @param resolved 是否解析注解属性,若为{@code true}则获得的注解将支持属性别名以及属性覆盖机制 + * @return {@link HierarchicalAnnotatedElements}实例 + * @see #getRepeatableMetaElementCache(AnnotatedElement) + * @see #getResolvedRepeatableMetaElementCache(AnnotatedElement) + */ + public static AnnotatedElement toHierarchyRepeatableMetaElement(final AnnotatedElement element, final boolean resolved) { + if (Objects.isNull(element)) { + return emptyElement(); + } + if (resolved) { + return HierarchicalAnnotatedElements.create(element, (es, e) -> getResolvedRepeatableMetaElementCache(e)); + } + return HierarchicalAnnotatedElements.create(element, (es, e) -> getRepeatableMetaElementCache(e)); + } + /** *

扫描{@code element}所处层级结构中的{@link AnnotatedElement}, * 再把所有对象合并为{@link HierarchicalAnnotatedElements} @@ -460,6 +580,49 @@ public class AnnotatedElementUtil { ); } + /** + * 将{@link AnnotatedElement}转为{@link RepeatableMetaAnnotatedElement}, + * 得到的对象可访问{@link AnnotatedElement}上的直接声明的注解,这些注解包含的可重复注解,以及上述注解的所有元注解。 + * + * @param element 元素 + * @param resolved 是否解析注解属性,若为{@code true}则获得的注解将支持属性别名以及属性覆盖机制 + * @return {@link AnnotatedElement}实例 + * @see #getMetaElementCache(AnnotatedElement) + * @see #getResolvedMetaElementCache(AnnotatedElement) + */ + public static AnnotatedElement toRepeatableMetaElement(final AnnotatedElement element, final boolean resolved) { + return ObjUtil.defaultIfNull( + element, e -> resolved ? getResolvedRepeatableMetaElementCache(e) : getRepeatableMetaElementCache(e), emptyElement() + ); + } + + /** + *

将{@link AnnotatedElement}转为{@link RepeatableMetaAnnotatedElement}, + * 得到的对象可访问{@link AnnotatedElement}上的直接声明的注解, + * 通过{@code collector}从这些注解获得的可重复注解,以及上述注解的所有元注解。
+ * 注意:方法将不会通过缓存结果,因此每次调用都需要重新通过反射并获得相关注解。 + * + * @param collector 可重复注解收集器,为{@code null}时等同于{@link RepeatableAnnotationCollector#none()} + * @param element 元素 + * @param resolved 是否解析注解属性,若为{@code true}则获得的注解将支持属性别名以及属性覆盖机制 + * @return {@link AnnotatedElement}实例 + */ + public static AnnotatedElement toRepeatableMetaElement( + final AnnotatedElement element, RepeatableAnnotationCollector collector, final boolean resolved) { + if (Objects.isNull(element)) { + return emptyElement(); + } + collector = ObjUtil.defaultIfNull(collector, RepeatableAnnotationCollector.none()); + if (resolved) { + return RepeatableMetaAnnotatedElement.create( + collector, element, (source, annotation) -> ResolvedAnnotationMapping.create((ResolvedAnnotationMapping)source, annotation, true) + ); + } + return RepeatableMetaAnnotatedElement.create( + collector, element, (source, annotation) -> GenericAnnotationMapping.create(annotation, Objects.isNull(source)) + ); + } + /** * 将一组注解中的非{@code null}注解对象合并为一个{@link AnnotatedElement} * @@ -493,26 +656,70 @@ public class AnnotatedElementUtil { * @param element {@link AnnotatedElement} * @return {@link MetaAnnotatedElement}实例 */ - private static MetaAnnotatedElement getResolvedMetaElementCache(final AnnotatedElement element) { + static MetaAnnotatedElement getResolvedMetaElementCache(final AnnotatedElement element) { return RESOLVED_ELEMENT_CACHE.computeIfAbsent(element, ele -> MetaAnnotatedElement.create( element, (source, annotation) -> ResolvedAnnotationMapping.create(source, annotation, true) )); } /** - * 创建一个支持注解解析的{@link MetaAnnotatedElement} + * 创建一个不支持注解解析的{@link MetaAnnotatedElement} * * @param element {@link AnnotatedElement} * @return {@link MetaAnnotatedElement}实例 */ - private static MetaAnnotatedElement getMetaElementCache(final AnnotatedElement element) { + static MetaAnnotatedElement getMetaElementCache(final AnnotatedElement element) { return ELEMENT_CACHE.computeIfAbsent(element, ele -> MetaAnnotatedElement.create( element, (source, annotation) -> GenericAnnotationMapping.create(annotation, Objects.isNull(source)) )); } + /** + * 创建一个支持注解解析的{@link RepeatableMetaAnnotatedElement} + * + * @param element {@link AnnotatedElement} + * @return {@link MetaAnnotatedElement}实例 + */ + static RepeatableMetaAnnotatedElement getResolvedRepeatableMetaElementCache(final AnnotatedElement element) { + return RESOLVED_REPEATABLE_ELEMENT_CACHE.computeIfAbsent(element, ele -> RepeatableMetaAnnotatedElement.create( + element, (source, annotation) -> ResolvedAnnotationMapping.create(source, annotation, true) + )); + } + + /** + * 创建一个不支持注解解析的{@link RepeatableMetaAnnotatedElement} + * + * @param element {@link AnnotatedElement} + * @return {@link MetaAnnotatedElement}实例 + */ + static RepeatableMetaAnnotatedElement getRepeatableMetaElementCache(final AnnotatedElement element) { + return REPEATABLE_ELEMENT_CACHE.computeIfAbsent(element, ele -> RepeatableMetaAnnotatedElement.create( + element, (source, annotation) -> GenericAnnotationMapping.create(annotation, Objects.isNull(source)) + )); + } + // endregion + /** + * 清空相关缓存,包括: + *

+ * + * @see AnnotationUtil#clearCaches() + * @see RepeatableAnnotationCollector#clearSingletonCaches() + */ + public static void clearCaches() { + ELEMENT_CACHE.clear(); + RESOLVED_ELEMENT_CACHE.clear(); + REPEATABLE_ELEMENT_CACHE.clear(); + RESOLVED_REPEATABLE_ELEMENT_CACHE.clear(); + RepeatableAnnotationCollector.clearSingletonCaches(); + AnnotationUtil.clearCaches(); + } + /** * 由一组注解聚合来的{@link AnnotatedElement} */ 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 124ac5eac..6691928d6 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,6 +1,8 @@ package cn.hutool.core.annotation; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.reflect.FieldUtil; import cn.hutool.core.reflect.MethodUtil; import cn.hutool.core.util.ArrayUtil; @@ -13,7 +15,6 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; @@ -26,6 +27,22 @@ import java.util.stream.Stream; */ public class AnnotationUtil { + /** + * 直接声明的注解缓存 + */ + private static final Map DECLARED_ANNOTATIONS_CACHE = new WeakConcurrentMap<>(); + + /** + * 获取直接声明的注解,若已有缓存则从缓存中获取 + * + * @param element {@link AnnotatedElement} + * @return 注解 + * @since 6.0.0 + */ + public static Annotation[] getDeclaredAnnotations(AnnotatedElement element) { + return MapUtil.computeIfAbsent(DECLARED_ANNOTATIONS_CACHE, element, AnnotatedElement::getDeclaredAnnotations); + } + /** * 将指定的被注解的元素转换为组合注解元素 * @@ -295,16 +312,14 @@ public class AnnotationUtil { } /** - * 获取注解属性 + * 获取注解属性,若已有缓存则从缓存中获取 * * @param annotationType 注解类型 * @return 注解属性 * @since 6.0.0 */ public static Method[] getAnnotationAttributes(final Class annotationType) { - // TODO 改为通过带缓存的反射工具类完成 - Objects.requireNonNull(annotationType); - return Stream.of(annotationType.getDeclaredMethods()) + return Stream.of(MethodUtil.getDeclaredMethods(annotationType)) .filter(AnnotationUtil::isAnnotationAttribute) .toArray(Method[]::new); } @@ -339,4 +354,11 @@ public class AnnotationUtil { && !attribute.isSynthetic(); } + /** + * 清空相关缓存 + */ + public static void clearCaches() { + DECLARED_ANNOTATIONS_CACHE.clear(); + } + } 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 abbca341b..adbce12c3 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 @@ -4,11 +4,7 @@ import cn.hutool.core.collection.SetUtil; import cn.hutool.core.map.TableMap; import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collection; @@ -115,7 +111,7 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa * @param element 元素 */ private void init(final AnnotatedElement element) { - final Annotation[] declaredAnnotations = element.getDeclaredAnnotations(); + final Annotation[] declaredAnnotations = AnnotationUtil.getDeclaredAnnotations(element); this.declaredAnnotationMap = new TableMap<>(); parseDeclared(declaredAnnotations); @@ -145,7 +141,7 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa declaredAnnotationMap.put(annotationType, annotation); } // 测试不通过的注解,不影响继续递归 - parseDeclared(annotationType.getDeclaredAnnotations()); + parseDeclared(AnnotationUtil.getDeclaredAnnotations(annotationType)); } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java b/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java index a6f06ad8e..c159961b1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java @@ -2,6 +2,7 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.reflect.MethodUtil; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.ArrayUtil; @@ -172,7 +173,7 @@ public class HierarchicalAnnotatedElements implements AnnotatedElement, Iterable @Override public Annotation[] getDeclaredAnnotations() { return getElementMappings().stream() - .map(AnnotatedElement::getDeclaredAnnotations) + .map(AnnotationUtil::getDeclaredAnnotations) .filter(ArrayUtil::isNotEmpty) .flatMap(Stream::of) .toArray(Annotation[]::new); @@ -357,8 +358,7 @@ public class HierarchicalAnnotatedElements implements AnnotatedElement, Iterable if (!isMethod) { collectElement(mappings, type); } else { - // TODO 改为通过带缓存的反射工具类完成 - Stream.of(type.getDeclaredMethods()) + Stream.of(MethodUtil.getDeclaredMethods(type)) .filter(method -> isMatchMethod(methodSource, method)) .forEach(method -> collectElement(mappings, method)); } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java index 26e0567cc..a10ac07d9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java @@ -10,7 +10,6 @@ import java.lang.annotation.Inherited; import java.lang.reflect.AnnotatedElement; import java.util.*; import java.util.function.BiFunction; -import java.util.stream.Stream; /** *

注解元素映射,用于包装一个{@link AnnotatedElement},然后将被包装的元素上, @@ -21,8 +20,11 @@ import java.util.stream.Stream; * 并且在当前实例中,{@link Inherited}注解将不生效, * 即通过directly方法将无法获得父类上带有{@link Inherited}的注解。 * - *

当通过静态工厂方法创建时,该实例与关联的{@link ResolvedAnnotationMapping}都会针对{@link ResolvedAnnotationMapping}进行缓存, - * 从而避免频繁的反射与代理造成不必要的性能损耗。 + *

在一个{@link MetaAnnotatedElement}中, + * {@link AnnotatedElement}上同类型的注解或元注解只会被保留一个, + * 即当出现两个根注解都具有相同元注解时,仅有第一个根注解上的元注解会被保留, + * 因此当通过{@link #getAnnotationsByType(Class)} + * 或{@link #getDeclaredAnnotationsByType(Class)}方法用于只能获得一个注解对象。 * * @author huangchengxing * @see ResolvedAnnotationMapping @@ -214,6 +216,34 @@ public class MetaAnnotatedElement> imple return getAnnotationMappings().values().iterator(); } + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MetaAnnotatedElement that = (MetaAnnotatedElement)o; + return element.equals(that.element) && mappingFactory.equals(that.mappingFactory); + } + + /** + * 获取实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(element, mappingFactory); + } + // ========================= protected ========================= /** @@ -270,7 +300,7 @@ public class MetaAnnotatedElement> imple */ private void initAnnotationMappings(final Map, T> mappings) { final Deque deque = new LinkedList<>(); - Arrays.stream(element.getDeclaredAnnotations()) + Arrays.stream(AnnotationUtil.getDeclaredAnnotations(element)) .filter(m -> isNeedMapping(mappings, m)) .map(annotation -> createMapping(null, annotation)) .filter(Objects::nonNull) @@ -283,40 +313,15 @@ public class MetaAnnotatedElement> imple } // 保存该注解,并将其需要处理的元注解也加入队列 mappings.put(mapping.annotationType(), mapping); - Stream.of(mapping.annotationType().getDeclaredAnnotations()) - .map(annotation -> createMapping(mapping, annotation)) - .filter(Objects::nonNull) - .filter(m -> isNeedMapping(mappings, m)) - .forEach(deque::addLast); + for (final Annotation annotation : AnnotationUtil.getDeclaredAnnotations(mapping.annotationType())) { + if (mappings.containsKey(annotation.annotationType())) { + continue; + } + final T m = createMapping(mapping, annotation); + if (Objects.nonNull(m) && isNeedMapping(mappings, m)) { + deque.addLast(m); + } + } } } - - /** - * 比较两个实例是否相等 - * - * @param o 对象 - * @return 是否 - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MetaAnnotatedElement that = (MetaAnnotatedElement)o; - return element.equals(that.element) && mappingFactory.equals(that.mappingFactory); - } - - /** - * 获取实例的哈希值 - * - * @return 哈希值 - */ - - @Override - public int hashCode() { - return Objects.hash(element, mappingFactory); - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableAnnotationCollector.java b/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableAnnotationCollector.java new file mode 100644 index 000000000..cf6fe2bed --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableAnnotationCollector.java @@ -0,0 +1,494 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.WeakConcurrentMap; +import cn.hutool.core.reflect.MethodUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 可重复注解收集器,用于从一个注解获得被它包含的可重复注解 + * + * @author huangchengxing + */ +public interface RepeatableAnnotationCollector { + + /** + * 空实现 + * + * @return {@link RepeatableAnnotationCollector}实例 + */ + static RepeatableAnnotationCollector none() { + return None.INSTANCE; + } + + /** + *

当注解中有且仅有一个名为{@code value}的属性时, + * 若该属性类型为注解数组,且该数组对应的注解类型被{@link Repeatable}注解, + * 则收集器将返回该属性中包括的可重复注解。
+ * eg: + *


+	 * // 容器注解
+	 * {@literal @}interface Annotation {
+	 * 	Item[] value() default {};
+	 * }
+	 * // 可重复注解
+	 * {@literal @}Repeatable(Annotation.class)
+	 * {@literal @}interface Item {}
+	 * 
+ * 解析任意{@code Annotation}注解对象,则可以获得{@code value}属性中的{@code Item}注解对象 + * + * @return {@link RepeatableAnnotationCollector}实例 + * @see Standard + */ + static RepeatableAnnotationCollector standard() { + return Standard.INSTANCE; + } + + /** + * 当解析注解属性时,将根据给定的判断条件,确定该属性中是否含有可重复注解。
+ * 收集器将返回所有匹配的属性中的可重复注解。 + * + * @param predicate 是否为容纳可重复注解的属性的判断条件 + * @return {@link RepeatableAnnotationCollector}实例 + */ + static RepeatableAnnotationCollector condition(final BiPredicate predicate) { + return new Condition(predicate); + } + + /** + *

当注解中存在有属性为注解数组,且该数组对应的注解类型被{@link Repeatable}注解时, + * 认为该属性包含可重复注解。
+ * 收集器将返回所有符合上述条件的属性中的可重复注解。
+ * eg: + *


+	 * {@literal @}interface Annotation {
+	 * 	Item1[] items1() default {};
+	 * 	Item2[] items2() default {};
+	 * }
+	 * 
+ * 解析任意{@code Annotation}注解对象, + * 则可以获得{@code items1}属性中的{@code Item1}注解对象, + * 以及{@code items2}属性中的{@code Item2}注解对象, + * + * @return {@link RepeatableAnnotationCollector}实例 + */ + static RepeatableAnnotationCollector full() { + return Full.INSTANCE; + } + + /** + * 清空单例缓存 + */ + static void clearSingletonCaches() { + Standard.INSTANCE.repeatableMethodCache.clear(); + Full.INSTANCE.repeatableMethodCache.clear(); + } + + /** + *

若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的注解对象。 + * 若包含的注解对象也是可重复注解的容器注解,则继续解析直到获得所有非容器注解为止。
+ * eg: + * 若存在嵌套关系{@code a -> b -> c}, + * 则解析注解a,则将得到全部c注解。
+ * 如果注解不包含可重复注解,则返回a本身。 + * + * @param annotation 容器注解 + * @return 容器注解中的可重复注解,若{@code annotation}不为容器注解,则数组中仅有其本身一个对象 + */ + List getFinalRepeatableAnnotations(final Annotation annotation); + + /** + *

若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的注解对象。 + * 若包含的注解对象也是可重复注解的容器注解,则继续解析直到获得所有非容器注解为止。
+ * eg: + * 若存在嵌套关系{@code a -> b -> c},则解析注解a, + * 将获得全部abc注解。
+ * 如果注解不包含可重复注解,则返回a本身。 + * + * @param annotation 容器注解 + * @return 容器注解中的可重复注解,若{@code annotation}不为容器注解,则数组中仅有其本身一个对象 + */ + List getAllRepeatableAnnotations(final Annotation annotation); + + /** + *

若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的指定类型注解对象。
+ * eg: + * 若存在嵌套关系{@code a -> b -> c},则: + *

+ * + * @param annotation 容器注解 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 容器注解中的可重复注解 + */ + List getRepeatableAnnotations(final Annotation annotation, final Class annotationType); + + /** + * 空实现 + */ + class None implements RepeatableAnnotationCollector { + + /** + * 默认实例 + */ + private static final None INSTANCE = new None(); + + /** + * 默认返回空集合 + * + * @param annotation 注解 + * @return 空集合 + */ + @Override + public List getFinalRepeatableAnnotations(final Annotation annotation) { + return Objects.isNull(annotation) ? + Collections.emptyList() : Collections.singletonList(annotation); + } + + /** + * 默认返回空集合 + * + * @param annotation 注解 + * @return 空集合 + */ + @Override + public List getAllRepeatableAnnotations(final Annotation annotation) { + return Objects.isNull(annotation) ? + Collections.emptyList() : Collections.singletonList(annotation); + } + + /** + * 默认返回空集合 + * + * @param annotation 注解 + * @return 空集合 + */ + @Override + public List getRepeatableAnnotations(final Annotation annotation, final Class annotationType) { + if (Objects.isNull(annotation)) { + return Collections.emptyList(); + } + return Objects.equals(annotation.annotationType(), annotationType) ? + Collections.singletonList(annotationType.cast(annotation)) : Collections.emptyList(); + } + + } + + /** + * {@link RepeatableAnnotationCollector}的基本实现 + */ + abstract class AbstractCollector implements RepeatableAnnotationCollector { + + /** + *

若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的注解对象。 + * 若包含的注解对象也是可重复注解的容器注解,则继续解析直到获得所有非容器注解为止。 + * + * @param annotation 容器注解 + * @return 容器注解中的可重复注解,若{@code annotation}不为容器注解,则数组中仅有其本身一个对象 + */ + @Override + public final List getFinalRepeatableAnnotations(final Annotation annotation) { + return find(annotation, null, false); + } + + /** + *

若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的注解对象。 + * 若包含的注解对象也是可重复注解的容器注解,则继续解析直到获得所有非容器注解为止。
+ * 当{@code accumulate}为{@code true}时,返回结果为全量的注解。 + * eg: + * 若存在嵌套关系{@code a -> b -> c},则解析注解a, + * 将获得全部abc注解。 + * 如果注解不包含可重复注解,则返回其本身。 + * + * @param annotation 容器注解 + * @return 容器注解中的可重复注解,若{@code annotation}不为容器注解,则数组中仅有其本身一个对象 + */ + @Override + public List getAllRepeatableAnnotations(Annotation annotation) { + return find(annotation, null, true); + } + + /** + * 若一个注解是可重复注解的容器注解,则尝试通过其属性获得获得包含的指定类型注解对象 + * + * @param annotation 容器注解 + * @param annotationType 注解类型 + * @param 注解类型 + * @return 容器注解中的可重复注解 + */ + @Override + public List getRepeatableAnnotations( + final Annotation annotation, final Class annotationType) { + final List annotations = find(annotation, t -> Objects.equals(t.annotationType(), annotationType), false); + return annotations.stream() + .map(annotationType::cast) + .collect(Collectors.toList()); + } + + /** + * 递归遍历注解,将其平铺 + */ + private List find( + final Annotation annotation, final java.util.function.Predicate condition, final boolean accumulate) { + if (Objects.isNull(annotation)) { + return Collections.emptyList(); + } + final boolean hasCondition = Objects.nonNull(condition); + final List results = new ArrayList<>(); + final Deque deque = new LinkedList<>(); + deque.add(annotation); + while (!deque.isEmpty()) { + final Annotation source = deque.removeFirst(); + final List repeatableMethods = resolveRepeatableMethod(source); + // 若是累加的,则记录每一个注解 + if (accumulate) { + results.add(source); + } + final boolean isTarget = hasCondition && condition.test(source); + if (CollUtil.isEmpty(repeatableMethods) || isTarget) { + // 不是累加的,则仅当正在处理的注解不为可重复注解时才记录 + boolean shouldProcess = !accumulate && (!hasCondition || isTarget); + if (shouldProcess) { + results.add(source); + } + continue; + } + final Annotation[] repeatableAnnotation = repeatableMethods.stream() + .map(method -> getRepeatableAnnotationsFormAttribute(source, method)) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + if (ArrayUtil.isNotEmpty(repeatableAnnotation)) { + CollUtil.addAll(deque, repeatableAnnotation); + } + } + return results; + } + + /** + * 调用{@code value}方法,获得嵌套的可重复注解 + * + * @param annotation 注解对象 + * @param method 容纳可重复注解的方法 + * @return 可重复注解 + * @throws ClassCastException 当{@code method}调用结果无法正确转为{@link Annotation[]}类型时抛出 + */ + protected Annotation[] getRepeatableAnnotationsFormAttribute(final Annotation annotation, final Method method) { + return MethodUtil.invoke(annotation, method); + } + + /** + * 解析获得注解中存放可重复注解的属性 + * + * @param annotation 注解 + * @return 属性 + */ + protected abstract List resolveRepeatableMethod(final Annotation annotation); + + } + + /** + * 标准实现,当注解中有且仅有一个名为{@code value}的属性时, + * 若该属性类型为注解数组,且该数组对应的注解类型被{@link Repeatable}注解, + * 则收集器将返回该属性中包括的可重复注解。 + */ + class Standard extends AbstractCollector { + + /** + * 默认的value属性 + */ + private static final String VALUE = "value"; + + /** + * 默认实例 + */ + private static final Standard INSTANCE = new Standard(); + + /** + * 空方法缓存 + */ + private static final Object NONE = new Object(); + + /** + * 可重复注解对应的方法缓存 + */ + private final Map, Object> repeatableMethodCache = new WeakConcurrentMap<>(); + + /** + * 构造 + */ + Standard() { + } + + /** + * 解析获得注解中存放可重复注解的属性 + * + * @param annotation 注解 + * @return 属性 + */ + @Override + protected List resolveRepeatableMethod(final Annotation annotation) { + final Object cache = MapUtil.computeIfAbsent( + repeatableMethodCache, annotation.annotationType(), this::resolveRepeatableMethodFromType + ); + return (cache == NONE) ? null : Collections.singletonList((Method)cache); + } + + /** + * 从缓存中获得存放可重复注解的属性 + */ + private Object resolveRepeatableMethodFromType(final Class annotationType) { + final Method[] attributes = AnnotationUtil.getAnnotationAttributes(annotationType); + if (attributes.length != 1) { + return NONE; + } + return isRepeatableMethod(attributes[0]) ? attributes[0] : NONE; + } + + /** + * 判断方法是否为容器注解的{@code value}方法 + * + * @param attribute 注解的属性 + * @return 该属性是否为注解存放可重复注解的方法 + */ + protected boolean isRepeatableMethod(final Method attribute) { + // 属性名需为“value” + if (!CharSequenceUtil.equals(VALUE, attribute.getName())) { + return false; + } + final Class attributeType = attribute.getReturnType(); + // 返回值类型需为数组 + return attributeType.isArray() + // 且数组元素需为注解 + && attributeType.getComponentType() + .isAnnotation() + // 该注解类必须被@Repeatable注解,但不要求与当前属性的声明方法一致 + && attributeType.getComponentType() + .isAnnotationPresent(Repeatable.class); + } + + } + + /** + * 自定义判断条件的实现,当解析注解属性时,将根据给定的判断条件, + * 确定该属性中是否含有可重复注解,收集器将返回所有匹配的属性中的可重复注解。 + */ + class Condition extends AbstractCollector { + + /** + * 是否为容纳可重复注解的属性的判断条件 + */ + private final BiPredicate predicate; + + /** + * 构造 + * + * @param predicate 是否为容纳可重复注解的属性的判断条件 + */ + Condition(final BiPredicate predicate) { + this.predicate = Objects.requireNonNull(predicate); + } + + /** + * 解析获得注解中存放可重复注解的属性 + * + * @param annotation 注解 + * @return 属性 + */ + @Override + protected List resolveRepeatableMethod(final Annotation annotation) { + return Stream.of(AnnotationUtil.getAnnotationAttributes(annotation.annotationType())) + .filter(method -> predicate.test(annotation, method)) + .collect(Collectors.toList()); + } + + } + + /** + * 全量实现,当注解中存在有属性为注解数组,且该数组对应的注解类型被{@link Repeatable}注解时, + * 认为该属性包含可重复注解。
+ * 收集器将返回所有符合上述条件的属性中的可重复注解。 + */ + class Full extends AbstractCollector { + + /** + * 默认实例 + */ + private static final Full INSTANCE = new Full(); + + /** + * 空方法缓存 + */ + private static final Object NONE = new Object(); + + /** + * 可重复注解对应的方法缓存 + */ + private final Map, Object> repeatableMethodCache = new WeakConcurrentMap<>(); + + /** + * 构造 + */ + Full() { + } + + /** + * 解析获得注解中存放可重复注解的属性 + * + * @param annotation 注解 + * @return 属性 + */ + @SuppressWarnings("unchecked") + @Override + protected List resolveRepeatableMethod(final Annotation annotation) { + final Object cache = MapUtil.computeIfAbsent( + repeatableMethodCache, annotation.annotationType(), this::resolveRepeatableMethodFromType + ); + return (cache == NONE) ? null : (List)cache; + } + + /** + * 从缓存中获得存放可重复注解的属性 + */ + private Object resolveRepeatableMethodFromType(final Class annotationType) { + final List methods = Stream.of(AnnotationUtil.getAnnotationAttributes(annotationType)) + .filter(this::isRepeatableMethod) + .collect(Collectors.toList()); + return methods.isEmpty() ? NONE : methods; + } + + /** + * 判断方法是否为容器注解的{@code value}方法 + * + * @param attribute 注解的属性 + * @return 该属性是否为注解存放可重复注解的方法 + */ + protected boolean isRepeatableMethod(final Method attribute) { + final Class attributeType = attribute.getReturnType(); + // 返回值类型需为数组 + return attributeType.isArray() + // 且数组元素需为注解 + && attributeType.getComponentType() + .isAnnotation() + // 该注解类必须被@Repeatable注解,但不要求与当前属性的声明方法一致 + && attributeType.getComponentType() + .isAnnotationPresent(Repeatable.class); + } + + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElement.java new file mode 100644 index 000000000..811307439 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElement.java @@ -0,0 +1,389 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + *

支持可重复注解的增强{@link AnnotatedElement}, + * 功能与{@link MetaAnnotatedElement}类似,但是存在下述差异: + *

    + *
  • + * 限制以同一根注解延伸出的树结构上——而不是{@link AnnotatedElement}上——每种类型注解只能保留一个, + * 即当{@link AnnotatedElement}存在多个根注解有相同的元注解时,这些元注解会都会被扫描到; + *
  • + *
  • + * 支持扫描{@link AnnotatedElement}可重复注解,即当当前实例指定的{@link RepeatableAnnotationCollector} + * 支持从{@link AnnotatedElement}上直接声明的注解中获得可重复注解时, + * 则将会自动将其展开直到不为容器注解为止。
    + * eg:
    + * A上存在注解X,该注解是一个容器注解,内部可重复注解Y, + * 包含解析后,得到注解X与可重复注解Y,
    + * 同理,若存在XYX的嵌套关系,则解析后获得全部三者; + *
  • + *
+ * 由于上述机制,当通过实例的{@link #getAnnotation(Class)}或{@link #getDeclaredAnnotation(Class)} + * 方法获得指定类型注解时,若该类型注解存在多个,仅能尽可能获得最先被扫描到的那一个。 + * + * @author huangchengxing + * @since 6.0.0 + * @see RepeatableAnnotationCollector + */ +public class RepeatableMetaAnnotatedElement> implements AnnotatedElement, Iterable { + + /** + * 包装的{@link AnnotatedElement}对象 + */ + private final AnnotatedElement element; + + /** + * 创建{@link AnnotationMapping}的工厂方法 + */ + private final BiFunction mappingFactory; + + /** + * 解析得到的根注解与元注解的聚合体 + */ + private final List aggregations; + + /** + * 可重复注解收集器 + */ + private final RepeatableAnnotationCollector repeatableCollector; + + /** + * 获取{@link AnnotatedElement}上的注解结构,该方法会针对相同的{@link AnnotatedElement}缓存映射对象 + * + * @param element 被注解元素 + * @param mappingFactory 创建{@link AnnotationMapping}的工厂方法,返回值为{@code null}时将忽略该注解 + * @param {@link AnnotationMapping}类型 + * @return {@link AnnotatedElement}上的注解结构 + */ + public static > RepeatableMetaAnnotatedElement create( + final AnnotatedElement element, final BiFunction mappingFactory) { + return create(RepeatableAnnotationCollector.standard(), element, mappingFactory); + } + + /** + * 获取{@link AnnotatedElement}上的注解结构,该方法会针对相同的{@link AnnotatedElement}缓存映射对象 + * + * @param collector 可重复注解收集器 + * @param element 被注解元素 + * @param mappingFactory 创建{@link AnnotationMapping}的工厂方法,返回值为{@code null}时将忽略该注解 + * @param {@link AnnotationMapping}类型 + * @return {@link AnnotatedElement}上的注解结构 + */ + public static > RepeatableMetaAnnotatedElement create( + final RepeatableAnnotationCollector collector, + final AnnotatedElement element, + final BiFunction mappingFactory) { + return new RepeatableMetaAnnotatedElement<>(collector, element, mappingFactory); + } + + /** + * 创建一个支持可重复注解的增强{@link AnnotatedElement} + * + * @param element 包装的{@link AnnotatedElement}对象 + * @param mappingFactory 创建{@link AnnotationMapping}的工厂方法 + */ + RepeatableMetaAnnotatedElement( + final RepeatableAnnotationCollector repeatableCollector, final AnnotatedElement element, final BiFunction mappingFactory) { + this.element = Objects.requireNonNull(element); + this.mappingFactory = Objects.requireNonNull(mappingFactory); + this.repeatableCollector = repeatableCollector; + this.aggregations = Collections.unmodifiableList(initAggregations(element)); + } + + /** + * 指定注解是否在{@link #element}上直接声明的注解、直接声明的注解包含的可重复注解, + * 以及他们的元注解中存在 + * + * @param annotationType 注解类型 + * @return 是否 + */ + @Override + public boolean isAnnotationPresent(final Class annotationType) { + return aggregations.stream() + .anyMatch(aggregation -> aggregation.getMappings().containsKey(annotationType)); + } + + /** + * 从{@link #element}上直接声明的注解、直接声明的注解包含的可重复注解,以及它们的元注解中获得指定类型的注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @Override + public A getAnnotation(final Class annotationType) { + return aggregations.stream() + .map(Aggregation::getMappings) + .map(annotations -> annotations.get(annotationType)) + .filter(Objects::nonNull) + .findFirst() + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .orElse(null); + } + + /** + * 获取{@link #element}上直接声明的注解、直接声明的注解包含的可重复注解,以及它们的元注解 + * + * @return 注解 + */ + @Override + public Annotation[] getAnnotations() { + return aggregations.stream() + .map(aggregation -> aggregation.getMappings().values()) + .flatMap(Collection::stream) + .map(T::getResolvedAnnotation) + .toArray(Annotation[]::new); + } + + /** + * 从{@link #element}上直接声明的注解、直接声明的注解包含的可重复注解,以及它们的元注解中获得指定类型的注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @Override + public A[] getAnnotationsByType(final Class annotationType) { + return aggregations.stream() + .map(aggregation -> aggregation.getMappings().get(annotationType)) + .filter(Objects::nonNull) + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 获取由{@link #element}直接声明的注解,不包含被直接声明的容器注解包括的可重复注解 + * + * @return 注解 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return aggregations.stream() + .filter(Aggregation::isDirect) + .map(Aggregation::getRoot) + .map(T::getResolvedAnnotation) + .toArray(Annotation[]::new); + } + + /** + * 获取由{@link #element}直接声明的注解,不包含被直接声明的容器注解包括的可重复注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @Override + public A getDeclaredAnnotation(final Class annotationType) { + return aggregations.stream() + .filter(Aggregation::isDirect) + .map(Aggregation::getRoot) + .filter(annotation -> Objects.equals(annotationType, annotation.annotationType())) + .findFirst() + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .orElse(null); + } + + /** + * 获取由{@link #element}直接声明的注解,不包含被直接声明的容器注解包括的可重复注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @Override + public A[] getDeclaredAnnotationsByType(final Class annotationType) { + return aggregations.stream() + .filter(Aggregation::isDirect) + .map(Aggregation::getRoot) + .filter(annotation -> Objects.equals(annotationType, annotation.annotationType())) + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 注解对象 + * + * @return 被包装的原始元素 + */ + public AnnotatedElement getElement() { + return element; + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final RepeatableMetaAnnotatedElement that = (RepeatableMetaAnnotatedElement)o; + return element.equals(that.element) && mappingFactory.equals(that.mappingFactory) && repeatableCollector.equals(that.repeatableCollector); + } + + /** + * 获取实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(element, mappingFactory, repeatableCollector); + } + + /** + * 获取迭代器 + * + * @return 迭代器 + */ + @Override + public Iterator iterator() { + return aggregations.stream() + .map(Aggregation::getMappings) + .map(Map::values) + .flatMap(Collection::stream) + .iterator(); + } + + /** + * 初始化 + */ + private List initAggregations(final AnnotatedElement element) { + // TODO 若有可能,一并支持处理元注解中的可重复注解 + List result = new ArrayList<>(); + for (final Annotation declaredAnnotation : AnnotationUtil.getDeclaredAnnotations(element)) { + List repeatableAnnotations = collectRepeatable(declaredAnnotation); + if (CollUtil.isNotEmpty(repeatableAnnotations)) { + result.addAll(repeatableAnnotations); + } + } + return result; + } + + /** + * 若当前注解是可重复注解的容器,则将其平铺后把可重复注解加入{@link #aggregations} + */ + private List collectRepeatable(final Annotation annotation) { + return repeatableCollector.getAllRepeatableAnnotations(annotation) + .stream() + .map(a -> new Aggregation(a, Objects.equals(a, annotation))) + .collect(Collectors.toList()); + } + + /** + * 由根注解与其元注解构成的注解聚合 + */ + class Aggregation { + + /** + * 根注解 + */ + private final T root; + + /** + * 注解 + */ + private volatile Map, T> mappings; + + /** + * 是否是由{@link #element}直接声明的注解 + */ + private final boolean isDirect; + + /** + * 创建一个合并聚合 + */ + public Aggregation(final Annotation root, final boolean isDirect) { + this.root = mappingFactory.apply(null, root); + this.isDirect = isDirect; + } + + /** + * 获得收集到的注解 + */ + private Map, T> getMappings() { + if (Objects.isNull(mappings)) { + synchronized (this) { + if (Objects.isNull(mappings)) { + mappings = Collections.unmodifiableMap(initMetaAnnotations()); + } + } + } + return mappings; + } + + /** + * 获得注解及其元注解 + */ + private Map, T> initMetaAnnotations() { + final Map, T> collectedMappings = new LinkedHashMap<>(); + final Deque deque = new LinkedList<>(); + deque.add(root); + while (!deque.isEmpty()) { + final T source = deque.removeFirst(); + if (!isNeedMapping(collectedMappings, source)) { + continue; + } + collectedMappings.put(source.annotationType(), source); + for (final Annotation annotation : AnnotationUtil.getDeclaredAnnotations(source.annotationType())) { + if (collectedMappings.containsKey(annotation.annotationType())) { + continue; + } + final T mapping = mappingFactory.apply(source, annotation); + if (Objects.nonNull(mapping) && isNeedMapping(collectedMappings, mapping)) { + deque.addLast(mapping); + } + } + } + return collectedMappings; + } + + /** + * 该注解是否需要映射
+ * 默认情况下,已经处理过、或在{@link java.lang}包下的注解不会被处理 + */ + private boolean isNeedMapping(final Map, T> mappings, final Annotation annotation) { + return !CharSequenceUtil.startWith(annotation.annotationType().getName(), "java.lang.") + && !mappings.containsKey(annotation.annotationType()); + } + + /** + * 根注解是否由{@link #element}直接声明 + * + * @return 是否 + */ + public boolean isDirect() { + return isDirect; + } + + /** + * 获取根注解 + * + * @return 根注解 + */ + public T getRoot() { + return root; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java index 26062a144..943b783cb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java @@ -170,7 +170,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping Arrays.fill(this.resolvedAttributes, NOT_FOUND_INDEX); // 若有必要,解析属性 - // TODO 可能的改进:flag改为枚举,使得可以自行选择:1.只支持属性别名,2.只支持属性覆盖,3.两个都支持,4.两个都不支持 + // TODO flag改为枚举,使得可以自行选择:1.只支持属性别名,2.只支持属性覆盖,3.两个都支持,4.两个都不支持 this.resolved = resolveAttribute && resolveAttributes(); } @@ -178,6 +178,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping * 解析属性 */ private boolean resolveAttributes() { + // TODO 支持处理@PropIgnore,被标记的属性无法被覆写,也不会被别名关联 // 解析同一注解中的别名 resolveAliasAttributes(); // 使用子注解覆写当前注解中的属性 @@ -525,8 +526,8 @@ public class ResolvedAnnotationMapping implements AnnotationMapping // 根据AliasSet更新关联的属性 Stream.of(aliasSets).filter(Objects::nonNull).forEach(set -> { - final int resolvedIndex = set.resolve(); - set.forEach(index -> resolvedAttributes[index] = resolvedIndex); + final int effectiveAttributeIndex = set.determineEffectiveAttribute(); + set.forEach(index -> resolvedAttributes[index] = effectiveAttributeIndex); }); } @@ -616,7 +617,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping *
  • 若有多个属性具有非默认值,则要求所有的非默认值都必须相等,若符合并返回该首个具有非默认值的属性,否则报错;
  • * */ - private int resolve() { + private int determineEffectiveAttribute() { int resolvedIndex = NOT_FOUND_INDEX; boolean hasNotDef = false; Object lastValue = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java b/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java index 6b212c9d2..f7368527f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java @@ -15,11 +15,7 @@ import cn.hutool.core.util.ArrayUtil; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; /** @@ -33,6 +29,10 @@ public class MethodUtil { * 方法缓存 */ private static final WeakConcurrentMap, Method[]> METHODS_CACHE = new WeakConcurrentMap<>(); + /** + * 直接声明的方法缓存 + */ + private static final WeakConcurrentMap, Method[]> DECLARED_METHODS_CACHE = new WeakConcurrentMap<>(); // --------------------------------------------------------------------------------------------------------- method @@ -323,6 +323,19 @@ public class MethodUtil { (key) -> getMethodsDirectly(beanClass, true, true)); } + /** + * 获得类中所有直接声明方法,不包括其父类中的方法 + * + * @param beanClass 类,非{@code null} + * @return 方法列表 + * @throws SecurityException 安全检查异常 + */ + public static Method[] getDeclaredMethods(final Class beanClass) throws SecurityException { + Assert.notNull(beanClass); + return DECLARED_METHODS_CACHE.computeIfAbsent(beanClass, + key -> getMethodsDirectly(beanClass, false, Objects.equals(Object.class, beanClass))); + } + /** * 获得一个类中所有方法列表,直接反射获取,无缓存
    * 接口获取方法和默认方法,获取的方法包括: diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java index 03ed704a6..7445efde8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java @@ -37,6 +37,31 @@ public class AnnotatedElementUtilTest { ANNOTATION6, ANNOTATION5 // Interface.class's annotations }; + @Test + public void testClearCaches() { + AnnotatedElement type = Foo.class; + + AnnotatedElement element = AnnotatedElementUtil.getResolvedMetaElementCache(type); + Assert.assertSame(element, AnnotatedElementUtil.getResolvedMetaElementCache(type)); + AnnotatedElementUtil.clearCaches(); + Assert.assertNotSame(element, AnnotatedElementUtil.getResolvedMetaElementCache(type)); + + element = AnnotatedElementUtil.getMetaElementCache(type); + Assert.assertSame(element, AnnotatedElementUtil.getMetaElementCache(type)); + AnnotatedElementUtil.clearCaches(); + Assert.assertNotSame(element, AnnotatedElementUtil.getMetaElementCache(type)); + + element = AnnotatedElementUtil.getResolvedRepeatableMetaElementCache(type); + Assert.assertSame(element, AnnotatedElementUtil.getResolvedRepeatableMetaElementCache(type)); + AnnotatedElementUtil.clearCaches(); + Assert.assertNotSame(element, AnnotatedElementUtil.getResolvedRepeatableMetaElementCache(type)); + + element = AnnotatedElementUtil.getRepeatableMetaElementCache(type); + Assert.assertSame(element, AnnotatedElementUtil.getRepeatableMetaElementCache(type)); + AnnotatedElementUtil.clearCaches(); + Assert.assertNotSame(element, AnnotatedElementUtil.getRepeatableMetaElementCache(type)); + } + @Test public void testIsAnnotated() { Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation1.class)); @@ -243,6 +268,25 @@ public class AnnotatedElementUtilTest { ); } + @Test + public void testGetAllAnnotations() { + Annotation3[] resolvedAnnotation3s = AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation3.class); + Assert.assertEquals(1, resolvedAnnotation3s.length); + Assert.assertEquals(ANNOTATION3, resolvedAnnotation3s[0]); // value与alias互为别名 + + Annotation2[] resolvedAnnotation2s = AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation2.class); + Assert.assertEquals(1, resolvedAnnotation2s.length); + Assert.assertEquals(ANNOTATION2, resolvedAnnotation2s[0]); // value与alias互为别名 + + Annotation1[] resolvedAnnotation1s = AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation1.class); + Assert.assertEquals(1, resolvedAnnotation1s.length); + Assert.assertEquals(ANNOTATION1, resolvedAnnotation1s[0]); + + Assert.assertEquals(0, AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation4.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation5.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllAnnotations(Foo.class, Annotation6.class).length); + } + @Test public void testGetResolvedAnnotation() { Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation3.class); @@ -264,6 +308,33 @@ public class AnnotatedElementUtilTest { Assert.assertNull(AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation6.class)); } + @Test + public void testGetAllResolvedAnnotations() { + Annotation3[] resolvedAnnotation3s = AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation3.class); + Assert.assertEquals(1, resolvedAnnotation3s.length); + Annotation3 resolvedAnnotation3 = resolvedAnnotation3s[0]; + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2[] resolvedAnnotation2s = AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation2.class); + Assert.assertEquals(1, resolvedAnnotation2s.length); + Annotation2 resolvedAnnotation2 = resolvedAnnotation2s[0]; + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1[] resolvedAnnotation1s = AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation1.class); + Assert.assertEquals(1, resolvedAnnotation1s.length); + Annotation1 resolvedAnnotation1 = resolvedAnnotation1s[0]; + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(0, AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation4.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation5.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllResolvedAnnotations(Foo.class, Annotation6.class).length); + } + @Test public void testGetResolvedAnnotations() { Map, Annotation> annotationMap = Stream.of(AnnotatedElementUtil.getResolvedAnnotations(Foo.class)) @@ -299,6 +370,20 @@ public class AnnotatedElementUtilTest { Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation6.class)); } + @Test + public void testGetAllDirectlyAnnotations() { + Annotation3[] resolvedAnnotation3s = AnnotatedElementUtil.getAllDirectlyAnnotations(Foo.class, Annotation3.class); + Assert.assertEquals(1, resolvedAnnotation3s.length); + Annotation3 resolvedAnnotation3 = resolvedAnnotation3s[0]; + Assert.assertEquals(ANNOTATION3, resolvedAnnotation3); + + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation2.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation1.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation4.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation5.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation6.class).length); + } + @Test public void testGetDirectlyAnnotations() { Annotation[] annotations = AnnotatedElementUtil.getDirectlyAnnotations(Foo.class); @@ -320,6 +405,22 @@ public class AnnotatedElementUtilTest { Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation6.class)); } + @Test + public void testGetDirectlyAllResolvedAnnotations() { + Annotation3[] resolvedAnnotation3s = AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation3.class); + Assert.assertEquals(1, resolvedAnnotation3s.length); + Annotation3 resolvedAnnotation3 = resolvedAnnotation3s[0]; + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation2.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation1.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation4.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation5.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.getAllDirectlyResolvedAnnotations(Foo.class, Annotation6.class).length); + } + @Test public void testGetDirectlyResolvedAnnotations() { Annotation[] annotations = AnnotatedElementUtil.getDirectlyResolvedAnnotations(Foo.class); @@ -364,6 +465,39 @@ public class AnnotatedElementUtilTest { Assert.assertEquals(ANNOTATION5, resolvedElement.getAnnotation(Annotation5.class)); } + @Test + public void testToHierarchyRepeatableMetaElement() { + Assert.assertNotNull(AnnotatedElementUtil.toHierarchyRepeatableMetaElement(null, false)); + Assert.assertNotNull(AnnotatedElementUtil.toHierarchyRepeatableMetaElement(null, true)); + AnnotatedElement element = AnnotatedElementUtil.toHierarchyRepeatableMetaElement(Foo.class, false); + + // 带有元注解 + Assert.assertArrayEquals(ANNOTATIONS, element.getAnnotations()); + + // 不带元注解 + Assert.assertArrayEquals(DECLARED_ANNOTATIONS, element.getDeclaredAnnotations()); + + // 解析注解属性 + AnnotatedElement resolvedElement = AnnotatedElementUtil.toHierarchyRepeatableMetaElement(Foo.class, true); + Annotation3 resolvedAnnotation3 = resolvedElement.getAnnotation(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = resolvedElement.getAnnotation(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = resolvedElement.getAnnotation(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(ANNOTATION4, resolvedElement.getAnnotation(Annotation4.class)); + Assert.assertEquals(ANNOTATION6, resolvedElement.getAnnotation(Annotation6.class)); + Assert.assertEquals(ANNOTATION5, resolvedElement.getAnnotation(Annotation5.class)); + } + @Test public void testToHierarchyElement() { Assert.assertNotNull(AnnotatedElementUtil.toHierarchyElement(Foo.class)); @@ -401,6 +535,36 @@ public class AnnotatedElementUtilTest { Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 } + @Test + public void testToRepeatableMetaElement() { + Assert.assertNotNull(AnnotatedElementUtil.toRepeatableMetaElement(null, RepeatableAnnotationCollector.none(), false)); + Assert.assertNotNull(AnnotatedElementUtil.toRepeatableMetaElement(null, RepeatableAnnotationCollector.none(), true)); + + // 不解析注解属性 + AnnotatedElement element = AnnotatedElementUtil.toRepeatableMetaElement(Foo.class, RepeatableAnnotationCollector.none(), false); + Assert.assertEquals(element, AnnotatedElementUtil.toRepeatableMetaElement(Foo.class, RepeatableAnnotationCollector.none(), false)); // 第二次获取时从缓存中获取 + Assert.assertArrayEquals(new Annotation[]{ANNOTATION3, ANNOTATION2, ANNOTATION1}, element.getAnnotations()); + + // 解析注解属性 + element = AnnotatedElementUtil.toRepeatableMetaElement(Foo.class, RepeatableAnnotationCollector.none(), true); + Assert.assertEquals(element, AnnotatedElementUtil.toRepeatableMetaElement(Foo.class, RepeatableAnnotationCollector.none(), true)); // 第二次获取时从缓存中获取 + Assert.assertEquals(3, element.getAnnotations().length); + + Annotation3 resolvedAnnotation3 = element.getAnnotation(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = element.getAnnotation(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = element.getAnnotation(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + } + @Test public void testAsElement() { Annotation[] annotations = new Annotation[]{ANNOTATION1, ANNOTATION2}; @@ -433,6 +597,7 @@ public class AnnotatedElementUtilTest { // ================= interface ================= + @Annotation6 @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) private @interface Annotation5 {} @@ -474,6 +639,22 @@ public class AnnotatedElementUtilTest { int num() default Integer.MIN_VALUE; } + /** + *

    Foo.class上注解情况如下: + *

    {@code
    +	 * annotation3 => annotation2 => annotation1
    +	 * }
    + * + *

    Super.class上注解情况如下: + *

    {@code
    +	 * annotation4
    +	 * }
    + * + *

    Interface.class上注解情况如下: + *

    {@code
    +	 * annotation6 => annotation5 => annotation6【循环引用】
    +	 * }
    + */ @Annotation3(value = "foo", num = Integer.MAX_VALUE) private static class Foo extends Super implements Interface {} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java index 5d3612bc8..6053d8851 100755 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java @@ -1,10 +1,12 @@ package cn.hutool.core.annotation; import cn.hutool.core.util.ObjUtil; +import lombok.SneakyThrows; import org.junit.Assert; import org.junit.Test; import java.lang.annotation.*; +import java.lang.reflect.Method; import java.util.Map; /** @@ -12,6 +14,16 @@ import java.util.Map; */ public class AnnotationUtilTest { + @Test + public void testGetDeclaredAnnotations() { + Annotation[] annotations = AnnotationUtil.getDeclaredAnnotations(ClassForTest.class); + Assert.assertArrayEquals(annotations, ClassForTest.class.getDeclaredAnnotations()); + Assert.assertSame(annotations, AnnotationUtil.getDeclaredAnnotations(ClassForTest.class)); + + AnnotationUtil.clearCaches(); + Assert.assertNotSame(annotations, AnnotationUtil.getDeclaredAnnotations(ClassForTest.class)); + } + @Test public void testToCombination() { final CombinationAnnotationElement element = AnnotationUtil.toCombination(ClassForTest.class); @@ -115,6 +127,21 @@ public class AnnotationUtilTest { Assert.assertEquals(MetaAnnotationForTest.class, annotation.annotationType()); } + @Test + public void testGetAnnotationAttributes() { + Method[] methods = AnnotationUtil.getAnnotationAttributes(AnnotationForTest.class); + Assert.assertArrayEquals(methods, AnnotationUtil.getAnnotationAttributes(AnnotationForTest.class)); + Assert.assertEquals(1, methods.length); + Assert.assertArrayEquals(AnnotationForTest.class.getDeclaredMethods(), methods); + } + + @SneakyThrows + @Test + public void testIsAnnotationAttribute() { + Assert.assertFalse(AnnotationUtil.isAnnotationAttribute(AnnotationForTest.class.getMethod("equals", Object.class))); + Assert.assertTrue(AnnotationUtil.isAnnotationAttribute(AnnotationForTest.class.getMethod("value"))); + } + @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) private @interface MetaAnnotationForTest{ diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java index fd8a2f3cc..f4cdfdc1d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java @@ -111,6 +111,7 @@ public class MetaAnnotatedElementTest { new Annotation[]{ annotation4 }, element.getAnnotationsByType(Annotation4.class) ); + Assert.assertEquals(0, element.getAnnotationsByType(None.class).length); } @Test @@ -121,6 +122,7 @@ public class MetaAnnotatedElementTest { new Annotation[]{ annotation4 }, element.getDeclaredAnnotationsByType(Annotation4.class) ); + Assert.assertEquals(0, element.getDeclaredAnnotationsByType(None.class).length); } @Test @@ -168,6 +170,12 @@ public class MetaAnnotatedElementTest { Assert.assertSame(source, element.getElement()); } + @Annotation4 // 循环引用 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface None { } + + @Annotation4 // 循环引用 @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) private @interface Annotation1 { diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableAnnotationCollectorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableAnnotationCollectorTest.java new file mode 100644 index 000000000..ac9386a57 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableAnnotationCollectorTest.java @@ -0,0 +1,220 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.text.CharSequenceUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * test for {@link RepeatableAnnotationCollector} + * + * @author huangchengxing + */ +public class RepeatableAnnotationCollectorTest { + + private static final Annotation1 ANNOTATION1 = Foo.class.getAnnotation(Annotation1.class); + private static final List ANNOTATION2S = Arrays.asList(ANNOTATION1.value()); + private static final List ANNOTATION3S = ANNOTATION2S.stream() + .map(Annotation2::value) + .flatMap(Stream::of) + .collect(Collectors.toList()); + private static final List ANNOTATIONS = new ArrayList<>(); + static { + ANNOTATIONS.add(ANNOTATION1); + ANNOTATIONS.addAll(ANNOTATION2S); + ANNOTATIONS.addAll(ANNOTATION3S); + } + + private static final BiPredicate PREDICATE = (annotation, attribute) -> { + // 属性名需为“value” + if (!CharSequenceUtil.equals("value", attribute.getName())) { + return false; + } + final Class attributeType = attribute.getReturnType(); + // 返回值类型需为数组 + return attributeType.isArray() + // 且数组元素需为注解 + && attributeType.getComponentType() + .isAnnotation() + // 该注解类必须被@Repeatable注解,但不要求与当前属性的声明方法一致 + && attributeType.getComponentType() + .isAnnotationPresent(Repeatable.class); + }; + + @Test + public void testNone() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.none(); + Assert.assertSame(collector, RepeatableAnnotationCollector.none()); + + Assert.assertEquals(0, collector.getFinalRepeatableAnnotations(null).size()); + + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(Collections.singletonList(annotation), collector.getFinalRepeatableAnnotations(annotation)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getFinalRepeatableAnnotations(annotation3)); + + Assert.assertEquals(Collections.singletonList(annotation3), collector.getRepeatableAnnotations(annotation3, Annotation3.class)); + Assert.assertTrue(collector.getRepeatableAnnotations(annotation3, Annotation1.class).isEmpty()); + Assert.assertTrue(collector.getRepeatableAnnotations(null, Annotation1.class).isEmpty()); + } + + @Test + public void testNoneWhenAccumulate() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.none(); + Assert.assertSame(collector, RepeatableAnnotationCollector.none()); + + Assert.assertEquals(0, collector.getAllRepeatableAnnotations(null).size()); + + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(Collections.singletonList(annotation), collector.getAllRepeatableAnnotations(annotation)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getAllRepeatableAnnotations(annotation3)); + } + + @Test + public void testGenericCollector() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.standard(); + Assert.assertSame(collector, RepeatableAnnotationCollector.standard()); + + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + List annotations = Stream.of(annotation.value()) + .map(Annotation2::value) + .flatMap(Stream::of) + .collect(Collectors.toList()); + Assert.assertEquals(annotations, collector.getFinalRepeatableAnnotations(annotation)); + Assert.assertEquals(ANNOTATION3S, collector.getFinalRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getFinalRepeatableAnnotations(annotation3)); + + Assert.assertEquals(Collections.singletonList(ANNOTATION1), collector.getRepeatableAnnotations(ANNOTATION1, Annotation1.class)); + Assert.assertEquals(ANNOTATION2S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation2.class)); + Assert.assertEquals(ANNOTATION3S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation3.class)); + } + + @Test + public void testGenericCollectorWhenAccumulate() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.standard(); + Assert.assertSame(collector, RepeatableAnnotationCollector.standard()); + + List annotations = new ArrayList<>(); + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + annotations.add(annotation); + annotations.addAll(Arrays.asList(annotation.value())); + Stream.of(annotation.value()) + .map(Annotation2::value) + .flatMap(Stream::of) + .forEach(annotations::add); + Assert.assertEquals(annotations, collector.getAllRepeatableAnnotations(annotation)); + + Assert.assertEquals(ANNOTATIONS, collector.getAllRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getAllRepeatableAnnotations(annotation3)); + } + + @Test + public void testConditionCollector() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.condition(PREDICATE); + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + List annotations = Stream.of(annotation.value()) + .map(Annotation2::value) + .flatMap(Stream::of) + .collect(Collectors.toList()); + Assert.assertEquals(annotations, collector.getFinalRepeatableAnnotations(annotation)); + + Assert.assertEquals(ANNOTATION3S, collector.getFinalRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getFinalRepeatableAnnotations(annotation3)); + + Assert.assertEquals(Collections.singletonList(ANNOTATION1), collector.getRepeatableAnnotations(ANNOTATION1, Annotation1.class)); + Assert.assertEquals(ANNOTATION2S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation2.class)); + Assert.assertEquals(ANNOTATION3S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation3.class)); + } + + @Test + public void testConditionCollectorWhenAccumulate() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.condition(PREDICATE); + + List annotations = new ArrayList<>(); + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + annotations.add(annotation); + annotations.addAll(Arrays.asList(annotation.value())); + Stream.of(annotation.value()) + .map(Annotation2::value) + .flatMap(Stream::of) + .forEach(annotations::add); + Assert.assertEquals(annotations, collector.getAllRepeatableAnnotations(annotation)); + Assert.assertEquals(ANNOTATIONS, collector.getAllRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getAllRepeatableAnnotations((annotation3))); + } + + @Test + public void testFullCollector() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.full(); + Assert.assertSame(collector, RepeatableAnnotationCollector.full()); + + Assert.assertEquals(ANNOTATION3S, collector.getFinalRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getFinalRepeatableAnnotations(annotation3)); + + Assert.assertEquals(Collections.singletonList(ANNOTATION1), collector.getRepeatableAnnotations(ANNOTATION1, Annotation1.class)); + Assert.assertEquals(ANNOTATION2S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation2.class)); + Assert.assertEquals(ANNOTATION3S, collector.getRepeatableAnnotations(ANNOTATION1, Annotation3.class)); + } + + @Test + public void testFullCollectorWhenAccumulate() { + RepeatableAnnotationCollector collector = RepeatableAnnotationCollector.full(); + Assert.assertSame(collector, RepeatableAnnotationCollector.full()); + + Assert.assertEquals(ANNOTATIONS, collector.getAllRepeatableAnnotations(ANNOTATION1)); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals(Collections.singletonList(annotation3), collector.getAllRepeatableAnnotations(annotation3)); + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + Annotation2[] value() default {}; + } + + @Repeatable(Annotation1.class) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { + Annotation3[] value() default {}; + } + + @Repeatable(Annotation2.class) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { + int value(); + String name() default ""; + } + + @Annotation3(Integer.MIN_VALUE) + @Annotation1({ + @Annotation2({@Annotation3(1), @Annotation3(2)}), + @Annotation2({@Annotation3(3), @Annotation3(4)}) + }) + private static class Foo {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElementTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElementTest.java new file mode 100644 index 000000000..2fc38fe03 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatableMetaAnnotatedElementTest.java @@ -0,0 +1,213 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.iter.IterUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + * test for {@link RepeatableMetaAnnotatedElement} + * + * @author huangchengxing + */ +public class RepeatableMetaAnnotatedElementTest { + + private static final BiFunction RESOLVED_MAPPING_FACTORY = + (source, annotation) -> new ResolvedAnnotationMapping(source, annotation, true); + + private static final BiFunction MAPPING_FACTORY = + (source, annotation) -> new ResolvedAnnotationMapping(source, annotation, false); + + @Test + public void testEquals() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY); + Assert.assertEquals(element, element); + Assert.assertNotEquals(element, null); + Assert.assertEquals(element, RepeatableMetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY)); + Assert.assertNotEquals(element, RepeatableMetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY)); + Assert.assertNotEquals(element, RepeatableMetaAnnotatedElement.create(Annotation1.class, MAPPING_FACTORY)); + } + + @Test + public void testHashCode() { + int hashCode = RepeatableMetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY).hashCode(); + Assert.assertEquals(hashCode, RepeatableMetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, RepeatableMetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, RepeatableMetaAnnotatedElement.create(Annotation1.class, MAPPING_FACTORY).hashCode()); + } + + @Test + public void testIsAnnotationPresent() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + Assert.assertTrue(element.isAnnotationPresent(Annotation1.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation2.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation3.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation4.class)); + } + + @Test + public void testGetAnnotations() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + List> annotationTypes = Arrays.stream(element.getAnnotations()) + .map(Annotation::annotationType) + .collect(Collectors.toList()); + Map, Integer> countMap = IterUtil.countMap(annotationTypes.iterator()); + + Assert.assertEquals((Integer)1, countMap.get(Annotation1.class)); + Assert.assertEquals((Integer)2, countMap.get(Annotation2.class)); + Assert.assertEquals((Integer)4, countMap.get(Annotation3.class)); + Assert.assertEquals((Integer)7, countMap.get(Annotation4.class)); + } + + @Test + public void testGetAnnotation() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, element.getAnnotation(Annotation1.class)); + + Annotation4 annotation4 = Annotation1.class.getAnnotation(Annotation4.class); + Assert.assertEquals(annotation4, element.getAnnotation(Annotation4.class)); + + Annotation2 annotation2 = annotation1.value()[0]; + Assert.assertEquals(annotation2, element.getAnnotation(Annotation2.class)); + + Annotation3 annotation3 = annotation2.value()[0]; + Assert.assertEquals(annotation3, element.getAnnotation(Annotation3.class)); + } + + @Test + public void testGetAnnotationsByType() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + + Annotation[] annotations = element.getAnnotationsByType(Annotation1.class); + Assert.assertEquals(1, annotations.length); + + annotations = element.getAnnotationsByType(Annotation2.class); + Assert.assertEquals(2, annotations.length); + + annotations = element.getAnnotationsByType(Annotation3.class); + Assert.assertEquals(4, annotations.length); + + annotations = element.getAnnotationsByType(Annotation4.class); + Assert.assertEquals(7, annotations.length); + } + + @Test + public void testGetDeclaredAnnotations() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + List> annotationTypes = Arrays.stream(element.getDeclaredAnnotations()) + .map(Annotation::annotationType) + .collect(Collectors.toList()); + Map, Integer> countMap = IterUtil.countMap(annotationTypes.iterator()); + + Assert.assertEquals((Integer)1, countMap.get(Annotation1.class)); + Assert.assertFalse(countMap.containsKey(Annotation2.class)); + Assert.assertFalse(countMap.containsKey(Annotation3.class)); + Assert.assertFalse(countMap.containsKey(Annotation4.class)); + } + + @Test + public void testGetDeclaredAnnotation() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + + Annotation1 annotation1 = Foo.class.getDeclaredAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, element.getDeclaredAnnotation(Annotation1.class)); + Assert.assertNull(element.getDeclaredAnnotation(Annotation3.class)); + Assert.assertNull(element.getDeclaredAnnotation(Annotation4.class)); + Assert.assertNull(element.getDeclaredAnnotation(Annotation2.class)); + } + + @Test + public void testGetDeclaredAnnotationsByType() { + AnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + + Annotation[] annotations = element.getDeclaredAnnotationsByType(Annotation1.class); + Assert.assertEquals(1, annotations.length); + + annotations = element.getDeclaredAnnotationsByType(Annotation2.class); + Assert.assertEquals(0, annotations.length); + + annotations = element.getDeclaredAnnotationsByType(Annotation3.class); + Assert.assertEquals(0, annotations.length); + + annotations = element.getDeclaredAnnotationsByType(Annotation4.class); + Assert.assertEquals(0, annotations.length); + } + + @Test + public void testGetElement() { + AnnotatedElement element = Foo.class; + RepeatableMetaAnnotatedElement repeatableMetaAnnotatedElement = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), element, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + Assert.assertSame(element, repeatableMetaAnnotatedElement.getElement()); + } + + @Test + public void testIterator() { + RepeatableMetaAnnotatedElement element = RepeatableMetaAnnotatedElement.create( + RepeatableAnnotationCollector.standard(), Foo.class, (s, a) -> new GenericAnnotationMapping(a, Objects.isNull(s)) + ); + int count = 0; + for (GenericAnnotationMapping mapping : element) { + count++; + } + Assert.assertEquals(14, count); + } + + @Annotation4 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + Annotation2[] value() default {}; + } + + @Annotation4 + @Repeatable(Annotation1.class) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { + Annotation3[] value() default {}; + } + + @Annotation4 + @Repeatable(Annotation2.class) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { } + + @Annotation4 // 循环引用 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation4 { } + + @Annotation1({ + @Annotation2({@Annotation3, @Annotation3}), + @Annotation2({@Annotation3, @Annotation3}) + }) + private static class Foo {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/reflect/MethodUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/reflect/MethodUtilTest.java index 7322a9a70..5919e66c3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/reflect/MethodUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/reflect/MethodUtilTest.java @@ -81,6 +81,18 @@ public class MethodUtilTest { Assert.assertEquals(10, testClass.getA()); } + @Test + public void getDeclaredMethodsTest() { + Class type = ReflectUtilTest.TestBenchClass.class; + Method[] methods = type.getDeclaredMethods(); + Assert.assertArrayEquals(methods, MethodUtil.getDeclaredMethods(type)); + Assert.assertSame(MethodUtil.getDeclaredMethods(type), MethodUtil.getDeclaredMethods(type)); + + type = Object.class; + methods = type.getDeclaredMethods(); + Assert.assertArrayEquals(methods, MethodUtil.getDeclaredMethods(type)); + } + @Test @Ignore public void getMethodBenchTest() {