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 new file mode 100644 index 000000000..7959164bc --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java @@ -0,0 +1,388 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +/** + *

表示一组处于在层级结构中具有关联关系的{@link AnnotatedElement},创建实例时, + * 将扫描指定{@link AnnotatedElement}的层级结构中的所有{@link AnnotatedElement}, + * 并将其包装为{@link MetaAnnotatedElement}。
+ * eg:
+ * 若存在元素A有对应父类与父接口BC, + * 则根据A生成的{@link HierarchicalAnnotatedElements}实例将同时包含ABC, + * 该实例同时支持对这三个实例上直接声明的注解,以及这些注解的元注解进行访问。 + * + *

注解搜索范围 + *

在当前实例中,针对带有和不带declared关键字的方法定义如下: + *

+ * + *

扫描顺序 + *

当{@link AnnotatedElement}具有层级结构式,会按照广度优先扫描其本身(元素是{@link Class})、 + * 或其声明类(元素是{@link Method})的层级结构。
+ * 在该过程中,总是先扫描父类,再扫描父接口, + * 若存在多个父接口,则其扫描顺序遵循从{@link Class#getInterfaces()}获得该接口的顺序。 + * + * @author huangchengxing + * @since 6.0.0 + */ +public class HierarchicalAnnotatedElements implements AnnotatedElement, Iterable { + + /** + * 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + */ + protected final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory; + + /** + * 层级中的全部{@link AnnotatedElement}对象,默认为懒加载,需要通过{@link #getElementMappings()}触发初始化
+ * 该集合中的元素按照其与被包装的{@link AnnotatedElement}的距离和被按广度优先扫描的顺序排序 + */ + private volatile Set elementMappings; + + /** + * 被包装的{@link AnnotatedElement}对象 + */ + protected final AnnotatedElement source; + + /** + * 创建一个分层注解元素 + * + * @param element 被包装的元素,若元素已是{@link HierarchicalAnnotatedElements},则返回其本身 + * @return {@link HierarchicalAnnotatedElements}实例, + * 当{@code element}也是一个{@link HierarchicalAnnotatedElements}时,返回{@code element}本身 + */ + public static HierarchicalAnnotatedElements create(final AnnotatedElement element) { + return create(element, (es, e) -> e); + } + + /** + * 创建一个分层注解元素 + * + * @param element 被包装的元素,若元素已是{@link HierarchicalAnnotatedElements},则返回其本身 + * @param elementFactory 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + * @return {@link HierarchicalAnnotatedElements}实例, + * 当{@code element}也是一个{@link HierarchicalAnnotatedElements}时,返回{@code element}本身 + */ + public static HierarchicalAnnotatedElements create( + final AnnotatedElement element, + final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory) { + return element instanceof HierarchicalAnnotatedElements ? + (HierarchicalAnnotatedElements)element : new HierarchicalAnnotatedElements(element, elementFactory); + } + + /** + * 构造 + * + * @param element 被包装的元素 + * @param elementFactory 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + */ + HierarchicalAnnotatedElements( + final AnnotatedElement element, + final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory) { + this.source = Objects.requireNonNull(element); + // 懒加载 + this.elementMappings = null; + this.elementFactory = Objects.requireNonNull(elementFactory); + } + + /** + * 注解是否在层级结构中所有{@link AnnotatedElement}上的注解和元注解中存在 + * + * @param annotationType 注解类型 + * @return 是否 + */ + @Override + public boolean isAnnotationPresent(final Class annotationType) { + return getElementMappings().stream() + .anyMatch(element -> element.isAnnotationPresent(annotationType)); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @return 注解对象 + */ + @Override + public Annotation[] getAnnotations() { + return getElementMappings().stream() + .map(AnnotatedElement::getAnnotations) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public A getAnnotation(final Class annotationType) { + return getElementMappings().stream() + .map(e -> e.getAnnotation(annotationType)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getAnnotationsByType(final Class annotationType) { + return getElementMappings().stream() + .map(e -> e.getAnnotationsByType(annotationType)) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的注解 + * + * @return 注解对象 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return getElementMappings().stream() + .map(AnnotatedElement::getDeclaredAnnotations) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public A getDeclaredAnnotation(final Class annotationType) { + return getElementMappings().stream() + .map(element -> element.getDeclaredAnnotation(annotationType)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getDeclaredAnnotationsByType(final Class annotationType) { + return (A[]) getElementMappings().stream() + .map(element -> element.getDeclaredAnnotationsByType(annotationType)) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 获取注解元素映射集合的迭代器 + * + * @return 迭代器 + */ + @Override + public Iterator iterator() { + return getElementMappings().iterator(); + } + + /** + * 获取被包装的原始{@link AnnotatedElement}对象 + * + * @return 注解对象 + */ + public AnnotatedElement getElement() { + return source; + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HierarchicalAnnotatedElements that = (HierarchicalAnnotatedElements)o; + return elementFactory.equals(that.elementFactory) && source.equals(that.source); + } + + /** + * 获取实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(elementFactory, source); + } + + // ========================= protected ========================= + + /** + * 获取当前元素及层级结构中的关联元素的映射对象 + * + * @return 元素映射对象 + */ + protected final Set getElementMappings() { + initElementMappingsIfNecessary(); + return elementMappings; + } + + /** + * 检验方法的签名是否与原始方法匹配 + * + * @param source 原始的方法 + * @param target 比较的方法 + * @return 是否 + */ + protected boolean isMatchMethod(final Method source, final Method target) { + return CharSequenceUtil.equals(source.getName(), target.getName()) + // 不可为桥接方法或者合成方法 + && !target.isBridge() && !target.isSynthetic() + // 返回值需可通过原始方法的返回值转换得到 + && ClassUtil.isAssignable(target.getReturnType(), source.getReturnType()) + // 参数数量必须一致,且类型也必须严格一致,但不检验泛型 + && Arrays.equals(source.getParameterTypes(), target.getParameterTypes()); + } + + // ========================= private ========================= + + /** + * 将元素转为{@link MetaAnnotatedElement}后添加至{@code mappings} + */ + private void collectElement(Set elements, final AnnotatedElement element) { + AnnotatedElement target = elementFactory.apply(elements, element); + if (Objects.nonNull(target)) { + elements.add(target); + } + } + + /** + * 遍历层级结构,获取层级结构中所有关联的{@link AnnotatedElement},并添加到{@link #elementMappings} + */ + private void initElementMappingsIfNecessary() { + // 双重检查保证初始化过程线程安全 + if (Objects.isNull(elementMappings)) { + synchronized (this) { + if (Objects.isNull(elementMappings)) { + Set mappings = initElementMappings(); + elementMappings = Collections.unmodifiableSet(mappings); + } + } + } + } + + /** + * 遍历层级结构,获取层级结构中所有关联的{@link AnnotatedElement},并添加到{@link #elementMappings} + */ + private Set initElementMappings() { + Set mappings = new LinkedHashSet<>(); + // 原始元素是类 + if (source instanceof Class) { + scanHierarchy(mappings, (Class)source, false, source); + } + // 原始元素是方法 + else if (source instanceof Method) { + final Method methodSource = (Method)source; + // 静态、私有与被final关键字修饰方法无法被子类重写,因此不可能具有层级结构 + if (Modifier.isPrivate(methodSource.getModifiers()) + || Modifier.isFinal(methodSource.getModifiers()) + || Modifier.isStatic(methodSource.getModifiers())) { + collectElement(mappings, methodSource); + } else { + scanHierarchy(mappings, methodSource.getDeclaringClass(), true, methodSource); + } + } + return mappings; + } + + /** + * 按广度优先,遍历{@code type}的父类以及父接口,并从类上/类中指定方法上获得所需的注解 + */ + private void scanHierarchy( + Set mappings, Class type, final boolean isMethod, final AnnotatedElement source) { + Method methodSource = isMethod ? (Method)source : null; + final Deque> deque = new LinkedList<>(); + deque.addLast(type); + final Set> accessed = new HashSet<>(); + while (!deque.isEmpty()) { + type = deque.removeFirst(); + // 已访问过的类不再处理 + if (!isNeedMapping(type, accessed)) { + continue; + } + // 收集元素 + if (!isMethod) { + collectElement(mappings, type); + } else { + // TODO 改为通过带缓存的反射工具类完成 + Stream.of(type.getDeclaredMethods()) + .filter(method -> isMatchMethod(methodSource, method)) + .forEach(method -> collectElement(mappings, method)); + } + // 获取父类与父接口 + accessed.add(type); + deque.addLast(type.getSuperclass()); + CollUtil.addAll(deque, type.getInterfaces()); + } + } + + /** + * 是否需要处理该类,不符合任意一点则不处理: + *

+ */ + private boolean isNeedMapping(Class type, Set> accessedTypes) { + return Objects.nonNull(type) + && !accessedTypes.contains(type) + && !Objects.equals(type, Object.class); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java new file mode 100644 index 000000000..f4e8a0034 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java @@ -0,0 +1,208 @@ +package cn.hutool.core.annotation; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.BiFunction; + +/** + * test for {@link HierarchicalAnnotatedElements} + * + * @author huangchengxing + */ +public class HierarchicalAnnotatedElementTest { + + private static final BiFunction, AnnotatedElement, AnnotatedElement> ELEMENT_MAPPING_FACTORY = (es, e) -> e; + + @SneakyThrows + @Test + public void testCreateFromMethod() { + Method method1 = Foo.class.getDeclaredMethod("method"); + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(method1); + Assert.assertEquals(3, elements.getElementMappings().size()); + + Method method2 = Foo.class.getDeclaredMethod("method2"); + elements = HierarchicalAnnotatedElements.create(method2); + Assert.assertEquals(1, elements.getElementMappings().size()); + } + + @Test + public void testCreate() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Assert.assertNotNull(elements); + Assert.assertEquals(3, elements.getElementMappings().size()); + + elements = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY); + Assert.assertNotNull(elements); + Assert.assertEquals(3, elements.getElementMappings().size()); + + Assert.assertEquals(elements, HierarchicalAnnotatedElements.create(elements, ELEMENT_MAPPING_FACTORY)); + } + + @Test + public void testEquals() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY); + Assert.assertEquals(elements, elements); + Assert.assertEquals(elements, HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY)); + Assert.assertNotEquals(elements, HierarchicalAnnotatedElements.create(Super.class, ELEMENT_MAPPING_FACTORY)); + Assert.assertNotEquals(elements, HierarchicalAnnotatedElements.create(Foo.class, (es, e) -> e)); + Assert.assertNotEquals(elements, null); + } + + @Test + public void testHashCode() { + int hashCode = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY).hashCode(); + Assert.assertEquals(hashCode, HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, HierarchicalAnnotatedElements.create(Super.class, ELEMENT_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, HierarchicalAnnotatedElements.create(Foo.class, (es, e) -> e).hashCode()); + } + + @Test + public void testGetElement() { + AnnotatedElement element = Foo.class; + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(element, ELEMENT_MAPPING_FACTORY); + Assert.assertSame(element, elements.getElement()); + } + + @Test + public void testIsAnnotationPresent() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Assert.assertTrue(elements.isAnnotationPresent(Annotation1.class)); + Assert.assertTrue(elements.isAnnotationPresent(Annotation2.class)); + Assert.assertTrue(elements.isAnnotationPresent(Annotation3.class)); + } + + @Test + public void testGetAnnotations() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Annotation[] annotations = new Annotation[]{ annotation1, annotation2, annotation3 }; + + Assert.assertArrayEquals(annotations, elements.getAnnotations()); + } + + @Test + public void testGetAnnotation() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, elements.getAnnotation(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertEquals(annotation2, elements.getAnnotation(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertEquals(annotation3, elements.getAnnotation(Annotation3.class)); + } + + @Test + public void testGetAnnotationsByType() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertArrayEquals(new Annotation[]{ annotation1 }, elements.getAnnotationsByType(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertArrayEquals(new Annotation[]{ annotation2 }, elements.getAnnotationsByType(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertArrayEquals(new Annotation[]{ annotation3 }, elements.getAnnotationsByType(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotationsByType() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertArrayEquals(new Annotation[]{ annotation1 }, elements.getDeclaredAnnotationsByType(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertArrayEquals(new Annotation[]{ annotation2 }, elements.getDeclaredAnnotationsByType(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertArrayEquals(new Annotation[]{ annotation3 }, elements.getDeclaredAnnotationsByType(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotation() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, elements.getDeclaredAnnotation(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertEquals(annotation2, elements.getDeclaredAnnotation(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertEquals(annotation3, elements.getDeclaredAnnotation(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotations() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Annotation[] annotations = new Annotation[]{ annotation1, annotation2, annotation3 }; + + Assert.assertArrayEquals(annotations, elements.getDeclaredAnnotations()); + } + + @Test + public void testIterator() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Iterator iterator = elements.iterator(); + Assert.assertNotNull(iterator); + + List elementList = new ArrayList<>(); + iterator.forEachRemaining(elementList::add); + Assert.assertEquals(Arrays.asList(Foo.class, Super.class, Interface.class), elementList); + } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { } + + @Annotation3 + private interface Interface { + @Annotation3 + String method(); + @Annotation3 + static String method2() { return null; } + } + + @Annotation2 + private static class Super { + @Annotation2 + public String method() { return null; } + @Annotation2 + public static String method2() { return null; } + } + + @Annotation1 + private static class Foo extends Super implements Interface { + @Annotation1 + @Override + public String method() { return null; } + @Annotation1 + public static String method2() { return null; } + }; + +}