diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java new file mode 100644 index 000000000..135165634 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AnnotationScanner.java @@ -0,0 +1,90 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 注解扫描器,用于从支持的可注解元素上获取所需注解 + * + * @author huangchengxing + * @see TypeAnnotationScanner + * @see MethodAnnotationScanner + * @see FieldAnnotationScanner + * @see MateAnnotationScanner + */ +public interface AnnotationScanner { + + /** + * 是否支持扫描该可注解元素 + * + * @param annotatedElement 可注解元素 + * @return 是否支持扫描该可注解元素 + */ + default boolean support(AnnotatedElement annotatedElement) { + return false; + } + + /** + * 获取可注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true + * + * @param annotatedElement 可注解元素 + * @return 元素上的注解 + */ + List getAnnotations(AnnotatedElement annotatedElement); + + /** + * 若{@link #support(AnnotatedElement)}返回{@code true}, + * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, + * 否则返回{@link Collections#emptyList()} + * + * @param annotatedElement 元素 + * @return 元素上的注解 + */ + default List getIfSupport(AnnotatedElement annotatedElement) { + return support(annotatedElement) ? getAnnotations(annotatedElement) : Collections.emptyList(); + } + + /** + * 给定一组扫描器,使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解 + * + * @param annotatedElement 可注解元素 + * @param scanners 注解扫描器 + * @return 注解 + */ + static List scanByAnySupported(AnnotatedElement annotatedElement, AnnotationScanner... scanners) { + if (ObjectUtil.isNull(annotatedElement) && ArrayUtil.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .filter(scanner -> scanner.support(annotatedElement)) + .findFirst() + .map(scanner -> scanner.getAnnotations(annotatedElement)) + .orElseGet(Collections::emptyList); + } + + /** + * 根据指定的扫描器,扫描元素上可能存在的注解 + * + * @param annotatedElement 可注解元素 + * @param scanners 注解扫描器 + * @return 注解 + */ + static List scanByAllScanner(AnnotatedElement annotatedElement, AnnotationScanner... scanners) { + if (ObjectUtil.isNull(annotatedElement) && ArrayUtil.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .map(scanner -> scanner.getIfSupport(annotatedElement)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java new file mode 100644 index 000000000..119c51ecb --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/FieldAnnotationScanner.java @@ -0,0 +1,27 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.List; + +/** + * 扫描{@link Field}上的注解 + * + * @author huangchengxing + */ +public class FieldAnnotationScanner implements AnnotationScanner { + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Field; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return CollUtil.newArrayList(annotatedElement.getAnnotations()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java new file mode 100644 index 000000000..1a6f8d1ac --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MateAnnotationScanner.java @@ -0,0 +1,95 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 扫描注解类上存在的注解,支持处理枚举实例或枚举类型
+ * 需要注意,当待解析是枚举类时,有可能与{@link TypeAnnotationScanner}冲突 + * + * @author huangchengxing + * @see TypeAnnotationScanner + */ +public class MateAnnotationScanner implements AnnotationScanner { + + /** + * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + private final boolean includeSupperMetaAnnotation; + + /** + * 构造 + * + * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + public MateAnnotationScanner(boolean includeSupperMetaAnnotation) { + this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; + } + + /** + * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 + */ + public MateAnnotationScanner() { + this(true); + } + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return (annotatedElement instanceof Class && ClassUtil.isAssignable(Annotation.class, (Class)annotatedElement)); + } + + /** + * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 + * + * @param consumer 当前层级索引与操作 + * @param source 源注解 + * @param filter 过滤器 + * @author huangchengxing + * @date 2022/6/14 13:28 + */ + public void scan(BiConsumer consumer, Class source, Predicate filter) { + filter = ObjectUtil.defaultIfNull(filter, t -> true); + Deque>> deque = CollUtil.newLinkedList(CollUtil.newArrayList(source)); + int distance = 0; + do { + List> annotationTypes = deque.removeFirst(); + for (Class type : annotationTypes) { + List metaAnnotations = Stream.of(type.getAnnotations()) + .filter(a -> !AnnotationUtil.isJdkMateAnnotation(a.annotationType())) + .filter(filter) + .collect(Collectors.toList()); + for (Annotation metaAnnotation : metaAnnotations) { + consumer.accept(distance, metaAnnotation); + } + deque.addLast(CollStreamUtil.toList(metaAnnotations, Annotation::annotationType)); + } + distance++; + } while (includeSupperMetaAnnotation && !deque.isEmpty()); + } + + @SuppressWarnings("unchecked") + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + List annotations = new ArrayList<>(); + scan( + (index, annotation) -> annotations.add(annotation), + (Class)annotatedElement, + annotation -> ObjectUtil.notEqual(annotation, annotatedElement) + ); + return annotations; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java new file mode 100644 index 000000000..f8de322b8 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java @@ -0,0 +1,27 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 扫描{@link Method}上的注解 + * + * @author huangchengxing + */ +public class MethodAnnotationScanner implements AnnotationScanner { + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Method; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return CollUtil.newArrayList(annotatedElement.getAnnotations()); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java new file mode 100644 index 000000000..f833a3492 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/TypeAnnotationScanner.java @@ -0,0 +1,224 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 扫描{@link Class}上的注解 + * + * @author huangchengxing + */ +public class TypeAnnotationScanner implements AnnotationScanner { + + /** + * 是否允许扫描父类 + */ + private boolean includeSupperClass; + + /** + * 是否允许扫描父接口 + */ + private boolean includeInterfaces; + + /** + * 过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + */ + private Predicate> filter; + + /** + * 排除的类型,以上类型及其树结构将直接不被查找 + */ + private final Set> excludeTypes; + + /** + * 转换器 + */ + private final List>> converters; + + /** + * 是否有转换器 + */ + private boolean hasConverters; + + /** + * 构造一个类注解扫描器 + * + * @param includeSupperClass 是否允许扫描父类 + * @param includeInterfaces 是否允许扫描父接口 + */ + public TypeAnnotationScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { + Assert.notNull(filter, "filter must not null"); + Assert.notNull(excludeTypes, "excludeTypes must not null"); + this.includeSupperClass = includeSupperClass; + this.includeInterfaces = includeInterfaces; + this.filter = filter; + this.excludeTypes = excludeTypes; + this.converters = new ArrayList<>(); + } + + /** + * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 + */ + public TypeAnnotationScanner() { + this(true, true, t -> true, CollUtil.newHashSet()); + } + + /** + * 是否允许扫描父类 + * + * @return 是否允许扫描父类 + */ + public boolean isIncludeSupperClass() { + return includeSupperClass; + } + + /** + * 是否允许扫描父接口 + * + * @return 是否允许扫描父接口 + */ + public boolean isIncludeInterfaces() { + return includeInterfaces; + } + + /** + * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + * + * @param filter 过滤器 + * @return 当前实例 + */ + public TypeAnnotationScanner setFilter(Predicate> filter) { + Assert.notNull(filter, "filter must not null"); + this.filter = filter; + return this; + } + + /** + * 添加不扫描的类型,该类型及其树结构将直接不被查找 + * + * @param excludeTypes 不扫描的类型 + * @return 当前实例 + */ + public TypeAnnotationScanner addExcludeTypes(Class... excludeTypes) { + CollUtil.addAll(this.excludeTypes, excludeTypes); + return this; + } + + /** + * 添加转换器 + * + * @param converter 转换器 + * @return 当前实例 + * @see JdkProxyClassConverter + */ + public TypeAnnotationScanner addConverters(UnaryOperator> converter) { + Assert.notNull(converter, "converter must not null"); + this.converters.add(converter); + if (!this.hasConverters) { + this.hasConverters = true; + } + return this; + } + + /** + * 是否允许扫描父类 + * + * @param includeSupperClass 是否 + * @return 当前实例 + */ + public TypeAnnotationScanner setIncludeSupperClass(boolean includeSupperClass) { + this.includeSupperClass = includeSupperClass; + return this; + } + + /** + * 是否允许扫描父接口 + * + * @param includeInterfaces 是否 + * @return 当前实例 + */ + public TypeAnnotationScanner setIncludeInterfaces(boolean includeInterfaces) { + this.includeInterfaces = includeInterfaces; + return this; + } + + @Override + public boolean support(AnnotatedElement annotatedElement) { + return annotatedElement instanceof Class; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedElement) { + return scan((Class)annotatedElement).stream() + .map(Class::getAnnotations) + .flatMap(Stream::of) + .filter(a -> !AnnotationUtil.isJdkMateAnnotation(a.annotationType())) + .collect(Collectors.toList()); + } + + private Class convert(Class target) { + if (hasConverters) { + converters.forEach(c -> c.apply(target)); + } + return target; + } + + /** + * 递归遍历当前类、父类及其实现的父接口 + * + * @param targetClass 类 + */ + private Set> scan(Class targetClass) { + Deque> classDeque = CollUtil.newLinkedList(targetClass); + Set> accessedTypes = new HashSet<>(); + while (!classDeque.isEmpty()) { + Class target = convert(classDeque.removeFirst()); + // 若当前类已经访问过,则无需再次处理 + if (ObjectUtil.isNull(target) || accessedTypes.contains(target) || excludeTypes.contains(target) || filter.negate().test(target)) { + continue; + } + accessedTypes.add(target); + + // 扫描父类 + if (includeSupperClass) { + Class superClass = target.getSuperclass(); + if (!ObjectUtil.equals(superClass, Object.class) && ObjectUtil.isNotNull(superClass)) { + classDeque.addLast(superClass); + } + } + + // 扫描接口 + if (includeInterfaces) { + Class[] interfaces = target.getInterfaces(); + if (ArrayUtil.isNotEmpty(interfaces)) { + CollUtil.addAll(classDeque, interfaces); + } + } + } + return accessedTypes; + } + + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? sourceClass.getSuperclass() : sourceClass; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java new file mode 100644 index 000000000..d032a516b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/AnnotationForScannerTest.java @@ -0,0 +1,15 @@ +package cn.hutool.core.annotation.scanner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author huangchengxing + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) +@interface AnnotationForScannerTest { + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java new file mode 100644 index 000000000..59af613bd --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/FieldAnnotationScannerTest.java @@ -0,0 +1,33 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.List; + +/** + * @author huangchengxing + */ +public class FieldAnnotationScannerTest { + + @Test + public void testFieldAnnotationScanner() { + FieldAnnotationScanner scanner = new FieldAnnotationScanner(); + Field field = ReflectUtil.getField(Example.class, "id"); + Assert.assertNotNull(field); + Assert.assertTrue(scanner.support(field)); + List annotations = scanner.getAnnotations(field); + Assert.assertEquals(1, annotations.size()); + Assert.assertEquals(AnnotationForScannerTest.class, CollUtil.getFirst(annotations).annotationType()); + } + + public static class Example { + @AnnotationForScannerTest + private Integer id; + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java new file mode 100644 index 000000000..a787670de --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MateAnnotationScannerTest.java @@ -0,0 +1,55 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author huangchengxing + * @date 2022/06/10 16:51 + */ +public class MateAnnotationScannerTest { + + @Test + public void testMateAnnotationScanner() { + AnnotationScanner scanner = new MateAnnotationScanner(); + Assert.assertTrue(scanner.support(AnnotationForScannerTest3.class)); + Map, Annotation> annotations = CollUtil.toMap(scanner.getAnnotations(AnnotationForScannerTest3.class), new HashMap<>(), Annotation::annotationType); + Assert.assertEquals(3, annotations.size()); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest.class)); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest1.class)); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest2.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest3.class)); + + scanner = new MateAnnotationScanner(false); + Assert.assertTrue(scanner.support(AnnotationForScannerTest3.class)); + annotations = CollUtil.toMap(scanner.getAnnotations(AnnotationForScannerTest3.class), new HashMap<>(), Annotation::annotationType); + Assert.assertEquals(1, annotations.size()); + Assert.assertTrue(annotations.containsKey(AnnotationForScannerTest2.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest1.class)); + Assert.assertFalse(annotations.containsKey(AnnotationForScannerTest3.class)); + } + + @AnnotationForScannerTest3 + static class Example {} + + @AnnotationForScannerTest + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest1 {} + + @AnnotationForScannerTest1 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest2 {} + + @AnnotationForScannerTest2 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @interface AnnotationForScannerTest3 {} +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java new file mode 100644 index 000000000..25798c84d --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/MethodAnnotationScannerTest.java @@ -0,0 +1,35 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author huangchengxing + */ +public class MethodAnnotationScannerTest { + + @Test + public void testMethodAnnotationScanner() { + AnnotationScanner scanner = new MethodAnnotationScanner(); + Method method = ReflectUtil.getMethod(Example.class, "test"); + Assert.assertNotNull(method); + Assert.assertTrue(scanner.support(method)); + List annotations = scanner.getAnnotations(method); + Assert.assertEquals(1, annotations.size()); + Assert.assertEquals(CollUtil.getFirst(annotations).annotationType(), AnnotationForScannerTest.class); + } + + static class Example { + @AnnotationForScannerTest + public void test() { + + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java new file mode 100644 index 000000000..1df0727d1 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java @@ -0,0 +1,62 @@ +package cn.hutool.core.annotation.scanner; + +import cn.hutool.core.util.ClassUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * @author huangchengxing + * @date 2022/06/10 16:51 + */ +public class TypeAnnotationScannerTest { + + @Test + public void testTypeAnnotationScanner() { + AnnotationScanner scanner = new TypeAnnotationScanner(); + Assert.assertTrue(scanner.support(Example.class)); + List annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(3, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找父接口 + scanner = new TypeAnnotationScanner().setIncludeInterfaces(false); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(2, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找父类 + scanner = new TypeAnnotationScanner().setIncludeSupperClass(false); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(1, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 不查找ExampleSupplerClass.class + scanner = new TypeAnnotationScanner().addExcludeTypes(ExampleSupplerClass.class); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(1, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + + // 只查找ExampleSupplerClass.class + scanner = new TypeAnnotationScanner().setFilter(t -> ClassUtil.isAssignable(ExampleSupplerClass.class, t)); + Assert.assertTrue(scanner.support(Example.class)); + annotations = scanner.getAnnotations(Example.class); + Assert.assertEquals(2, annotations.size()); + annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); + } + + @AnnotationForScannerTest + static class ExampleSupplerClass implements ExampleInterface {} + + @AnnotationForScannerTest + interface ExampleInterface {} + + @AnnotationForScannerTest + static class Example extends ExampleSupplerClass {} + +}