diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 5d99bdf92..1a5f24450 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -2,7 +2,11 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import java.lang.annotation.Annotation; @@ -18,7 +22,10 @@ import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 注解工具类
@@ -348,4 +355,47 @@ public class AnnotationUtil { final T annotation = getAnnotation(annotationEle, annotationType); return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + + /** + * 方法是否为注解属性方法。
+ * 方法无参数,且有返回值的方法认为是注解属性的方法。 + * + * @param method 方法 + */ + static boolean isAttributeMethod(Method method) { + return method.getParameterCount() == 0 && method.getReturnType() != void.class; + } + + /** + * 获取注解的全部属性值获取方法 + * + * @param annotationType 注解 + * @return 注解的全部属性值 + * @throws IllegalArgumentException 当别名属性在注解中不存在,或别名属性的值与原属性的值类型不一致时抛出 + */ + static Map getAttributeMethods(Class annotationType) { + // 获取全部注解属性值 + Map attributeMethods = Stream.of(annotationType.getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .collect(Collectors.toMap(Method::getName, Function.identity())); + // 处理别名 + attributeMethods.forEach((methodName, method) -> { + String alias = Opt.ofNullable(method.getAnnotation(Alias.class)) + .map(Alias::value) + .orElse(null); + if (ObjectUtil.isNull(alias)) { + return; + } + // 存在别名,则将原本的值替换为别名对应的值 + Assert.isTrue(attributeMethods.containsKey(alias), "No method for alias: [{}]", alias); + Method aliasAttributeMethod = attributeMethods.get(alias); + Assert.isTrue( + ObjectUtil.isNull(aliasAttributeMethod) || ClassUtil.isAssignable(method.getReturnType(), aliasAttributeMethod.getReturnType()), + "Return type of the alias method [{}] is inconsistent with the original [{}]", + aliasAttributeMethod.getClass(), method.getParameterTypes() + ); + attributeMethods.put(methodName, aliasAttributeMethod); + }); + return attributeMethods; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java new file mode 100644 index 000000000..2df329754 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java @@ -0,0 +1,335 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.annotation.scanner.MateAnnotationScanner; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 表示一个根注解与根注解上的多层元注解合成的注解 + * + *

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

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

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

当不同层级的注解之间存在同名属性时,将优先获取更接近根注解的属性 + * + * @param attributeName 属性名 + */ + public Object getAttribute(String attributeName) { + return attributeCaches.computeIfAbsent(attributeName, a -> metaAnnotationMap.values() + .stream() + .filter(ma -> ma.hasAttribute(attributeName)) // 集合默认是根据distance有序的,故此处无需再排序 + .findFirst() + .map(ma -> ma.getAttribute(attributeName)) + .orElse(null) + ); + } + + /** + * 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解 + */ + @SuppressWarnings("unchecked") + @Override + public T getAnnotation(Class annotationType) { + if (metaAnnotationMap.containsKey(annotationType)) { + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, Synthesized.class}, + new SyntheticAnnotationProxy<>(this, annotationType) + ); + } + return null; + } + + /** + * 获取全部注解 + * + * @return java.lang.annotation.Annotation[] + */ + @Override + public Annotation[] getAnnotations() { + return getMetaAnnotationMap().values().toArray(new MetaAnnotation[0]); + } + + /** + * 获取根注解直接声明注解 + * + * @return 直接声明注解 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[]{ getSource() }; + } + + /** + * 广度优先遍历并缓存该根注解上的全部元注解 + */ + private void loadMetaAnnotations() { + // 若该注解已经是合成注解,则直接使用已解析好的元注解信息 + if (source instanceof SyntheticAnnotation.Synthesized) { + this.metaAnnotationMap.putAll(((Synthesized)source).getMetaAnnotationMap()); + return; + } + // 扫描元注解 + metaAnnotationMap.put(source.annotationType(), new MetaAnnotation(source, 0)); + new MateAnnotationScanner().scan( + (index, annotation) -> metaAnnotationMap.computeIfAbsent( + // 当出现重复的注解时,由于后添加的注解必然层级更高,优先级更低,因此当直接忽略 + annotation.annotationType(), t -> new MetaAnnotation(annotation, index) + ), + source.annotationType(), null + ); + } + + /** + * 元注解包装类 + * + * @author huangchengxing + */ + static class MetaAnnotation implements Annotation { + + private final Annotation annotation; + private final Map attributeMethodCaches; + private final int distance; + + public MetaAnnotation(Annotation annotation, int distance) { + this.annotation = annotation; + this.distance = distance; + this.attributeMethodCaches = AnnotationUtil.getAttributeMethods(annotation.annotationType()); + } + + /** + * 获取注解类型 + * + * @return 注解类型 + */ + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + /** + * 获取元注解 + * + * @return 元注解 + */ + public Annotation get() { + return annotation; + } + + /** + * 获取根注解到元注解的距离 + * + * @return 根注解到元注解的距离 + */ + public int getDistance() { + return distance; + } + + /** + * 元注解是否存在该属性 + * + * @param attributeName 属性名 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName) { + return attributeMethodCaches.containsKey(attributeName); + } + + /** + * 获取元注解的属性值 + * + * @param attributeName 属性名 + * @return 元注解的属性值 + */ + public Object getAttribute(String attributeName) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .map(method -> ReflectUtil.invoke(annotation, method)) + .orElse(null); + } + + } + + /** + * 表示一个已经被合成的注解 + * + * @author huangchengxing + */ + interface Synthesized { + + /** + * 获取合成注解中已解析的元注解信息 + * + * @return 合成注解中已解析的元注解信息 + */ + Map, MetaAnnotation> getMetaAnnotationMap(); + + static boolean isMetaAnnotationMapMethod(Method method) { + return StrUtil.equals("getMetaAnnotationMap", method.getName()); + } + + } + + /** + * 合成注解代理类 + * + * @author huangchengxing + */ + static class SyntheticAnnotationProxy implements Annotation, InvocationHandler { + + private final Class annotationType; + private final SyntheticAnnotation syntheticAnnotation; + + public SyntheticAnnotationProxy(SyntheticAnnotation syntheticAnnotation, Class annotationType) { + this.syntheticAnnotation = syntheticAnnotation; + this.annotationType = annotationType; + } + + @Override + public Class annotationType() { + return annotationType; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Synthesized.isMetaAnnotationMapMethod(method)) { + return syntheticAnnotation.getMetaAnnotationMap(); + } + if (ReflectUtil.isHashCodeMethod(method)) { + return getHashCode(); + } + if (ReflectUtil.isToStringMethod(method)) { + return getToString(); + } + return ObjectUtil.defaultIfNull( + syntheticAnnotation.getAttribute(method.getName()), + () -> ReflectUtil.invoke(this, method, args) + ); + } + + /** + * 获取toString值 + * + * @return toString值 + */ + private String getToString() { + String attributes = Stream.of(annotationType().getDeclaredMethods()) + .filter(AnnotationUtil::isAttributeMethod) + .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName()))) + .collect(Collectors.joining(", ")); + return StrUtil.format("@{}({})", annotationType().getName(), attributes); + } + + /** + * 获取hashcode值 + * + * @return hashcode值 + */ + private int getHashCode() { + return Objects.hash((Object)syntheticAnnotation.getAnnotations()); + } + + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java new file mode 100644 index 000000000..a81147e2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticAnnotationTest.java @@ -0,0 +1,81 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.util.Map; + +/** + * 合成注解{@link SyntheticAnnotation}的测试用例 + * + * @author huangchengxing + */ +public class SyntheticAnnotationTest { + + @Test + public void testSynthesisAnnotation() { + ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + SyntheticAnnotation syntheticAnnotation = SyntheticAnnotation.of(rootAnnotation); + Assert.assertEquals(syntheticAnnotation.getSource(), rootAnnotation); + Assert.assertEquals(syntheticAnnotation.annotationType(), rootAnnotation.annotationType()); + Assert.assertEquals(1, syntheticAnnotation.getDeclaredAnnotations().length); + Assert.assertEquals(syntheticAnnotation.getDeclaredAnnotations()[0], rootAnnotation); + Assert.assertEquals(3, syntheticAnnotation.getAnnotations().length); + + Assert.assertEquals(syntheticAnnotation.getAttribute("childValue"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("childValueAlias"), "Child!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("parentValue"), "Child's Parent!"); + Assert.assertEquals(syntheticAnnotation.getAttribute("grandParentValue"), "Child's GrandParent!"); + + Map, SyntheticAnnotation.MetaAnnotation> annotationMap = syntheticAnnotation.getMetaAnnotationMap(); + ChildAnnotation childAnnotation = syntheticAnnotation.getAnnotation(ChildAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ChildAnnotation.class)); + Assert.assertNotNull(childAnnotation); + Assert.assertEquals(childAnnotation.childValue(), "Child!"); + Assert.assertEquals(childAnnotation.childValueAlias(), "Child!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(childAnnotation).getMetaAnnotationMap()); + + ParentAnnotation parentAnnotation = syntheticAnnotation.getAnnotation(ParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(ParentAnnotation.class)); + Assert.assertNotNull(parentAnnotation); + Assert.assertEquals(parentAnnotation.parentValue(), "Child's Parent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(parentAnnotation).getMetaAnnotationMap()); + + GrandParentAnnotation grandParentAnnotation = syntheticAnnotation.getAnnotation(GrandParentAnnotation.class); + Assert.assertTrue(syntheticAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); + Assert.assertNotNull(grandParentAnnotation); + Assert.assertEquals(grandParentAnnotation.grandParentValue(), "Child's GrandParent!"); + Assert.assertEquals(annotationMap, SyntheticAnnotation.of(grandParentAnnotation).getMetaAnnotationMap()); + } + + // 注解结构如下: + // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation + // -> @GrandParentAnnotation + @ChildAnnotation(childValueAlias = "Child!") + class AnnotatedClass {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE }) + @interface GrandParentAnnotation { + String grandParentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + @interface ParentAnnotation { + String parentValue() default ""; + } + + @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 + @ParentAnnotation(parentValue = "Child's Parent!") // 覆盖元注解@ParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface ChildAnnotation { + String childValueAlias() default ""; + @Alias("childValueAlias") + String childValue() default ""; + } + +}