diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationAttributeWrapper.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationAttributeWrapper.java new file mode 100644 index 000000000..9d4c170cd --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationAttributeWrapper.java @@ -0,0 +1,71 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * {@link AnnotationAttributeWrapper}的基本实现 + * + * @author huangchengxing + * @see ForceAliasedAnnotationAttribute + * @see AliasedAnnotationAttribute + * @see MirroredAnnotationAttribute + */ +public abstract class AbstractAnnotationAttributeWrapper implements AnnotationAttributeWrapper { + + protected final AnnotationAttribute original; + protected final AnnotationAttribute linked; + + protected AbstractAnnotationAttributeWrapper(AnnotationAttribute original, AnnotationAttribute linked) { + Assert.notNull(original, "target must not null"); + Assert.notNull(linked, "linked must not null"); + this.original = original; + this.linked = linked; + } + + @Override + public AnnotationAttribute getOriginal() { + return original; + } + + @Override + public AnnotationAttribute getLinked() { + return linked; + } + + @Override + public AnnotationAttribute getNonWrappedOriginal() { + AnnotationAttribute curr = null; + AnnotationAttribute next = original; + while (next != null) { + curr = next; + next = next.isWrapped() ? ((AnnotationAttributeWrapper)curr).getOriginal() : null; + } + return curr; + } + + @Override + public Collection getAllLinkedNonWrappedAttributes() { + List leafAttributes = new ArrayList<>(); + collectLeafAttribute(this, leafAttributes); + return leafAttributes; + } + + private void collectLeafAttribute(AnnotationAttribute curr, List leafAttributes) { + if (ObjectUtil.isNull(curr)) { + return; + } + if (!curr.isWrapped()) { + leafAttributes.add(curr); + return; + } + AnnotationAttributeWrapper wrappedAttribute = (AnnotationAttributeWrapper)curr; + collectLeafAttribute(wrappedAttribute.getOriginal(), leafAttributes); + collectLeafAttribute(wrappedAttribute.getLinked(), leafAttributes); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAttributePostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAttributePostProcessor.java index f9888c505..8eba6bb49 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAttributePostProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAttributePostProcessor.java @@ -26,36 +26,36 @@ public class AliasAttributePostProcessor implements SynthesizedAnnotationPostPro @Override public void process(SynthesizedAnnotation annotation, SyntheticAnnotation syntheticAnnotation) { - Map attributeMap = annotation.getAttributes(); + final Map attributeMap = annotation.getAttributes(); // 记录别名与属性的关系 - ForestMap attributeAliasMappings = new LinkedForestMap<>(false); + final ForestMap attributeAliasMappings = new LinkedForestMap<>(false); attributeMap.forEach((attributeName, attribute) -> { - String alias = Opt.ofNullable(attribute.getAnnotation(Alias.class)) + final String alias = Opt.ofNullable(attribute.getAnnotation(Alias.class)) .map(Alias::value) .orElse(null); if (ObjectUtil.isNull(alias)) { return; } - AnnotationAttribute aliasAttribute = attributeMap.get(alias); + final AnnotationAttribute aliasAttribute = attributeMap.get(alias); Assert.notNull(aliasAttribute, "no method for alias: [{}]", alias); attributeAliasMappings.putLinkedNodes(alias, aliasAttribute, attributeName, attribute); }); // 处理别名 attributeMap.forEach((attributeName, attribute) -> { - AnnotationAttribute resolvedAttributeMethod = Opt.ofNullable(attributeName) + final AnnotationAttribute resolvedAttribute = Opt.ofNullable(attributeName) .map(attributeAliasMappings::getRootNode) .map(TreeEntry::getValue) .map(aliasAttribute -> (AnnotationAttribute)new ForceAliasedAnnotationAttribute(attribute, aliasAttribute)) .orElse(attribute); Assert.isTrue( - ObjectUtil.isNull(resolvedAttributeMethod) - || ClassUtil.isAssignable(attribute.getAttribute().getReturnType(), resolvedAttributeMethod.getAttribute().getReturnType()), + ObjectUtil.isNull(resolvedAttribute) + || ClassUtil.isAssignable(attribute.getAttributeType(), resolvedAttribute.getAttributeType()), "return type of the root alias method [{}] is inconsistent with the original [{}]", - resolvedAttributeMethod.getClass(), attribute.getAttribute().getReturnType() + resolvedAttribute.getClass(), attribute.getAttributeType() ); - attributeMap.put(attributeName, resolvedAttributeMethod); + attributeMap.put(attributeName, resolvedAttribute); }); annotation.setAttributes(attributeMap); } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java new file mode 100644 index 000000000..188b1e509 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java @@ -0,0 +1,35 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

表示“原始属性”将作为“关联属性”的别名。 + *

    + *
  • 当“原始属性”为默认值时,获取“关联属性”将返回“关联属性”本身的值;
  • + *
  • 当“原始属性”不为默认值时,获取“关联属性”将返回“原始属性”的值;
  • + *
+ * 注意,该注解与{@link Link}或{@link MirrorFor}一起使用时,将只有被声明在最上面的注解会生效 + * + * @author huangchengxing + * @see Link + * @see RelationType#ALIAS_FOR + */ +@Link(type = RelationType.ALIAS_FOR) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface AliasFor { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + */ + @Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + */ + @Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) + String attribute() default ""; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasForLinkAttributePostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasForLinkAttributePostProcessor.java index a556e5abf..d775d6ab8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasForLinkAttributePostProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasForLinkAttributePostProcessor.java @@ -1,9 +1,11 @@ package cn.hutool.core.annotation; +import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ObjectUtil; import java.util.HashMap; import java.util.Map; +import java.util.function.BinaryOperator; /** * 处理注解中带有{@link Link}注解,且{@link Link#type()}为{@link RelationType#FORCE_ALIAS_FOR} @@ -22,7 +24,7 @@ public class AliasForLinkAttributePostProcessor implements SynthesizedAnnotation @Override public void process(SynthesizedAnnotation annotation, SyntheticAnnotation syntheticAnnotation) { - Map attributeMap = new HashMap<>(annotation.getAttributes()); + final Map attributeMap = new HashMap<>(annotation.getAttributes()); attributeMap.forEach((originalAttributeName, originalAttribute) -> { // 获取注解 final Link link = SyntheticAnnotationUtil.getLink( @@ -33,7 +35,7 @@ public class AliasForLinkAttributePostProcessor implements SynthesizedAnnotation } // 获取注解属性 - SynthesizedAnnotation aliasAnnotation = SyntheticAnnotationUtil.getLinkedAnnotation(link, syntheticAnnotation, annotation.annotationType()); + final SynthesizedAnnotation aliasAnnotation = SyntheticAnnotationUtil.getLinkedAnnotation(link, syntheticAnnotation, annotation.annotationType()); if (ObjectUtil.isNull(aliasAnnotation)) { return; } @@ -43,12 +45,40 @@ public class AliasForLinkAttributePostProcessor implements SynthesizedAnnotation // aliasFor if (RelationType.ALIAS_FOR.equals(link.type())) { - aliasAnnotation.setAttributes(aliasAttribute.getAttributeName(), new AliasedAnnotationAttribute(aliasAttribute, originalAttribute)); + wrappingLinkedAttribute(syntheticAnnotation, originalAttribute, aliasAttribute, AliasedAnnotationAttribute::new); return; } // forceAliasFor - aliasAnnotation.setAttributes(aliasAttribute.getAttributeName(), new ForceAliasedAnnotationAttribute(aliasAttribute, originalAttribute)); + wrappingLinkedAttribute(syntheticAnnotation, originalAttribute, aliasAttribute, ForceAliasedAnnotationAttribute::new); }); } + /** + * 对指定注解属性进行包装,若该属性已被包装过,则递归以其为根节点的树结构,对树上全部的叶子节点进行包装 + */ + private void wrappingLinkedAttribute( + SyntheticAnnotation syntheticAnnotation, AnnotationAttribute originalAttribute, AnnotationAttribute aliasAttribute, BinaryOperator wrapping) { + // 不是包装属性 + if (!aliasAttribute.isWrapped()) { + processAttribute(syntheticAnnotation, originalAttribute, aliasAttribute, wrapping); + return; + } + // 是包装属性 + final AbstractAnnotationAttributeWrapper wrapper = (AbstractAnnotationAttributeWrapper)aliasAttribute; + wrapper.getAllLinkedNonWrappedAttributes().forEach( + t -> processAttribute(syntheticAnnotation, originalAttribute, t, wrapping) + ); + } + + /** + * 获取指定注解属性,然后将其再进行一层包装 + */ + private void processAttribute( + SyntheticAnnotation syntheticAnnotation, AnnotationAttribute originalAttribute, + AnnotationAttribute target, BinaryOperator wrapping) { + Opt.ofNullable(target.getAnnotationType()) + .map(syntheticAnnotation::getSynthesizedAnnotation) + .ifPresent(t -> t.replaceAttribute(target.getAttributeName(), old -> wrapping.apply(old, originalAttribute))); + } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java index 44638308a..36d8dda81 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java @@ -1,29 +1,37 @@ package cn.hutool.core.annotation; -import cn.hutool.core.lang.Assert; - /** *

表示一个具有别名的属性。 * 当别名属性值为默认值时,优先返回原属性的值,当别名属性不为默认值时,优先返回别名属性的值 * * @author huangchengxing * @see AliasForLinkAttributePostProcessor - * @see RelationType#ALIAS_BY + * @see RelationType#FORCE_ALIAS_FOR * @see RelationType#ALIAS_FOR */ -public class AliasedAnnotationAttribute extends AnnotationAttributeWrapper implements AnnotationAttribute { +public class AliasedAnnotationAttribute extends AbstractAnnotationAttributeWrapper { - private final AnnotationAttribute aliasAttribute; - - protected AliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute aliasAttribute) { - super(origin); - Assert.notNull(aliasAttribute, "aliasAttribute must not null"); - this.aliasAttribute = aliasAttribute; + protected AliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); } + /** + * 若{@link #linked}为默认值,则返回{@link #original}的值,否则返回{@link #linked}的值 + * + * @return 属性值 + */ @Override public Object getValue() { - return aliasAttribute.isValueEquivalentToDefaultValue() ? super.getValue() : aliasAttribute.getValue(); + return linked.isValueEquivalentToDefaultValue() ? super.getValue() : linked.getValue(); } + /** + * 当{@link #original}与{@link #linked}都为默认值时返回{@code true} + * + * @return 是否 + */ + @Override + public boolean isValueEquivalentToDefaultValue() { + return linked.isValueEquivalentToDefaultValue() && original.isValueEquivalentToDefaultValue(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java index a7b9627b5..aface5b92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java @@ -16,8 +16,9 @@ import java.lang.reflect.Method; * * @author huangchengxing * @see SynthesizedAnnotationPostProcessor - * @see CacheableAnnotationAttribute * @see AnnotationAttributeWrapper + * @see CacheableAnnotationAttribute + * @see AbstractAnnotationAttributeWrapper * @see ForceAliasedAnnotationAttribute * @see AliasedAnnotationAttribute * @see MirroredAnnotationAttribute @@ -38,6 +39,15 @@ public interface AnnotationAttribute { */ Method getAttribute(); + /** + * 获取声明属性的注解类 + * + * @return 声明注解的注解类 + */ + default Class getAnnotationType() { + return getAttribute().getDeclaringClass(); + } + /** * 获取属性名称 * @@ -90,7 +100,7 @@ public interface AnnotationAttribute { * @return boolean */ default boolean isWrapped() { - return this instanceof AnnotationAttributeWrapper; + return false; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeWrapper.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeWrapper.java index f1ec42cd8..54caa5d98 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeWrapper.java @@ -1,14 +1,25 @@ package cn.hutool.core.annotation; -import cn.hutool.core.lang.Assert; - import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Collection; /** *

表示一个被包装过的{@link AnnotationAttribute}, - * 该实例中的一些方法可能会被代理到其他的注解属性对象中, - * 从而使得通过原始的注解属性的方法获取到另一注解属性的值。 + * 该实例中的一些方法可能会被代理到另一个注解属性对象中, + * 从而使得通过原始的注解属性的方法获取到另一注解属性的值。
+ * 除了{@link #getValue()}以外,其他方法的返回值应当尽可能与{@link #getOriginal()} + * 返回的{@link AnnotationAttribute}对象的方法返回值一致。 + * + *

当包装类被包装了多层后,则规则生效优先级按包装的先后顺序倒序排序, + * 比如a、b互为镜像,此时a、b两属性应当都被{@link MirroredAnnotationAttribute}包装, + * 若再指定c为a的别名字段,则c、a、b都要在原基础上再次包装一层{@link AliasedAnnotationAttribute}。
+ * 此时a、b同时被包装了两层,则执行时,优先执行{@link AliasedAnnotationAttribute}的逻辑, + * 当该规则不生效时,比如c只有默认值,此时上一次的{@link MirroredAnnotationAttribute}的逻辑才会生效。 + * + *

被包装的{@link AnnotationAttribute}实际结构为一颗二叉树, + * 当包装类再次被包装时,实际上等于又添加了一个新的根节点, + * 此时需要同时更新树的全部关联叶子节点。 * * @author huangchengxing * @see AnnotationAttribute @@ -16,42 +27,101 @@ import java.lang.reflect.Method; * @see AliasedAnnotationAttribute * @see MirroredAnnotationAttribute */ -public abstract class AnnotationAttributeWrapper implements AnnotationAttribute { +public interface AnnotationAttributeWrapper extends AnnotationAttribute { - protected final AnnotationAttribute origin; + // =========================== 新增方法 =========================== - protected AnnotationAttributeWrapper(AnnotationAttribute origin) { - Assert.notNull(origin, "target must not null"); - this.origin = origin; + /** + * 获取被包装的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} + * + * @return 被包装的{@link AnnotationAttribute}对象 + */ + AnnotationAttribute getOriginal(); + + /** + * 获取最初的被包装的{@link AnnotationAttribute} + * + * @return 最初的被包装的{@link AnnotationAttribute} + */ + AnnotationAttribute getNonWrappedOriginal(); + + /** + * 获取包装{@link #getOriginal()}的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} + * + * @return 包装对象 + */ + AnnotationAttribute getLinked(); + + /** + * 遍历以当前实例为根节点的树结构,获取所有未被包装的属性 + * + * @return 叶子节点 + */ + Collection getAllLinkedNonWrappedAttributes(); + + // =========================== 代理实现 =========================== + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + @Override + default Annotation getAnnotation() { + return getOriginal().getAnnotation(); } + /** + * 获取注解属性对应的方法 + * + * @return 注解属性对应的方法 + */ @Override - public Annotation getAnnotation() { - return origin.getAnnotation(); + default Method getAttribute() { + return getOriginal().getAttribute(); } + /** + * 该注解属性的值是否等于默认值
+ * 默认仅当{@link #getOriginal()}与{@link #getLinked()}返回的注解属性 + * 都为默认值时,才返回{@code true} + * + * @return 该注解属性的值是否等于默认值 + */ @Override - public Method getAttribute() { - return origin.getAttribute(); + default boolean isValueEquivalentToDefaultValue() { + return getOriginal().isValueEquivalentToDefaultValue() && getLinked().isValueEquivalentToDefaultValue(); } + /** + * 获取属性类型 + * + * @return 属性类型 + */ @Override - public boolean isValueEquivalentToDefaultValue() { - return origin.isValueEquivalentToDefaultValue(); + default Class getAttributeType() { + return getOriginal().getAttributeType(); } + /** + * 获取属性上的注解 + * + * @param annotationType 注解类型 + * @return 注解对象 + */ @Override - public Class getAttributeType() { - return origin.getAttributeType(); + default T getAnnotation(Class annotationType) { + return getOriginal().getAnnotation(annotationType); } + /** + * 当前注解属性是否已经被{@link AnnotationAttributeWrapper}包装 + * + * @return boolean + */ @Override - public T getAnnotation(Class annotationType) { - return origin.getAnnotation(annotationType); - } - - @Override - public boolean isWrapped() { + default boolean isWrapped() { return true; } + } 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 869412563..08c40fcb0 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 @@ -359,7 +359,8 @@ public class AnnotationUtil { * @see SyntheticAnnotation */ public static T getSynthesisAnnotation(Annotation annotation, Class annotationType) { - return SyntheticAnnotation.of(annotation).getAnnotation(annotationType); + // TODO 缓存合成注解信息,避免重复解析 + return SyntheticAnnotation.of(annotation).syntheticAnnotation(annotationType); } /** @@ -385,6 +386,30 @@ public class AnnotationUtil { .collect(Collectors.toList()); } + /** + * 获取元素上距离指定元素最接近的合成注解 + *

    + *
  • 若元素是类,则递归解析全部父类和全部父接口上的注解;
  • + *
  • 若元素是方法、属性或注解,则只解析其直接声明的注解;
  • + *
+ * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationType 注解类 + * @param 注解类型 + * @return 合成注解 + * @see SyntheticAnnotation + */ + public static T getSyntheticAnnotation(AnnotatedElement annotatedEle, Class annotationType) { + AnnotationScanner[] scanners = new AnnotationScanner[]{ + new MetaAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner() + }; + return AnnotationScanner.scanByAnySupported(annotatedEle, scanners).stream() + .map(annotation -> getSynthesisAnnotation(annotation, annotationType)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + /** * 扫描注解类,以及注解类的{@link Class}层级结构中的注解,将返回除了{@link #META_ANNOTATIONS}中指定的JDK默认注解外, * 按元注解对象与{@code annotationType}的距离和{@link Class#getAnnotations()}顺序排序的注解对象集合 diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java index bfaa3b493..4fd2ffd1d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java @@ -8,7 +8,7 @@ import java.util.Collection; import java.util.Comparator; /** - * 带缓存功能的{@link SynthesizedAnnotationAttributeProcessor}实现, + *

带缓存功能的{@link SynthesizedAnnotationAttributeProcessor}实现, * 构建时需要传入比较器,获取属性值时将根据比较器对合成注解进行排序, * 然后选择具有所需属性的,排序最靠前的注解用于获取属性值 * diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java index 8349a3bb6..72318ffba 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java @@ -1,40 +1,49 @@ package cn.hutool.core.annotation; -import cn.hutool.core.lang.Assert; - /** * 表示一个被指定了强制别名的注解属性。 - * 当调用{@link #getValue()}时,总是返回{@link #aliasAttribute}的值 + * 当调用{@link #getValue()}时,总是返回{@link #linked}的值 * * @author huangchengxing * @see AliasAttributePostProcessor * @see AliasForLinkAttributePostProcessor - * @see RelationType#FORCE_ALIAS_BY + * @see RelationType#ALIAS_FOR * @see RelationType#FORCE_ALIAS_FOR */ -public class ForceAliasedAnnotationAttribute extends AnnotationAttributeWrapper implements AnnotationAttribute { +public class ForceAliasedAnnotationAttribute extends AbstractAnnotationAttributeWrapper { - private final AnnotationAttribute aliasAttribute; - - protected ForceAliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute aliasAttribute) { - super(origin); - Assert.notNull(aliasAttribute, "aliasAttribute must not null"); - this.aliasAttribute = aliasAttribute; + protected ForceAliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); } + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 + */ @Override public Object getValue() { - return aliasAttribute.getValue(); + return linked.getValue(); } + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 + */ @Override public boolean isValueEquivalentToDefaultValue() { - return aliasAttribute.isValueEquivalentToDefaultValue(); + return linked.isValueEquivalentToDefaultValue(); } + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 + */ @Override public Class getAttributeType() { - return aliasAttribute.getAttributeType(); + return linked.getAttributeType(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java b/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java index a2e4a9679..d2f8ecacd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java @@ -5,7 +5,7 @@ import java.lang.annotation.*; /** *

用于在同一注解中,或具有一定关联的不同注解的属性中,表明这些属性之间具有特定的关联关系。 * 在通过{@link SyntheticAnnotation}获取合成注解后,合成注解获取属性值时会根据该注解进行调整。
- * 注意:该注解的优先级低于{@link Alias} + * 注意:该注解的优先级低于{@link Alias};且与{@link MirrorFor}或{@link AliasFor}一起使用时,将只有被声明在最上面的注解会生效 * * @author huangchengxing * @see SyntheticAnnotation diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java new file mode 100644 index 000000000..99ffdab4b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java @@ -0,0 +1,38 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

表示注解的属性与指定的属性互为镜像,通过一个属性将能够获得对方的值。
+ * 它们遵循下述规则: + *

    + *
  • 互为镜像的两个属性,必须同时通过指定模式为{@code MIRROR_FOR}的{@link Link}注解指定对方;
  • + *
  • 互为镜像的两个属性,类型必须一致;
  • + *
  • 互为镜像的两个属性在获取值,且两者的值皆不同时,必须且仅允许有一个非默认值,该值被优先返回;
  • + *
  • 互为镜像的两个属性,在值都为默认值或都不为默认值时,两者的值必须相等;
  • + *
+ * 注意,该注解与{@link Link}或{@link AliasFor}一起使用时,将只有被声明在最上面的注解会生效 + * + * @author huangchengxing + * @see Link + * @see RelationType#MIRROR_FOR + */ +@Link(type = RelationType.MIRROR_FOR) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface MirrorFor { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + */ + @Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + */ + @Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) + String attribute() default ""; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAttributePostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAttributePostProcessor.java index 352203382..7c370ff5a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAttributePostProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAttributePostProcessor.java @@ -46,10 +46,10 @@ public class MirrorLinkAttributePostProcessor implements SynthesizedAnnotationPo // 包装这一对镜像属性,并替换原注解中的对应属性 final AnnotationAttribute mirroredOriginalAttribute = new MirroredAnnotationAttribute(originalAttribute, mirrorAttribute); - syntheticAnnotation.getSynthesizedAnnotation(originalAttribute.getAttribute().getDeclaringClass()) - .setAttributes(originalAttributeName, mirroredOriginalAttribute); + syntheticAnnotation.getSynthesizedAnnotation(originalAttribute.getAnnotationType()) + .setAttribute(originalAttributeName, mirroredOriginalAttribute); final AnnotationAttribute mirroredTargetAttribute = new MirroredAnnotationAttribute(mirrorAttribute, originalAttribute); - mirrorAnnotation.setAttributes(link.attribute(), mirroredTargetAttribute); + mirrorAnnotation.setAttribute(link.attribute(), mirroredTargetAttribute); }); } @@ -59,7 +59,7 @@ public class MirrorLinkAttributePostProcessor implements SynthesizedAnnotationPo // 镜像属性返回值必须一致 SyntheticAnnotationUtil.checkAttributeType(original, mirror); // 镜像属性上必须存在对应的注解 - final Link mirrorAttributeAnnotation = mirror.getAnnotation(Link.class); + final Link mirrorAttributeAnnotation = SyntheticAnnotationUtil.getLink(mirror, RelationType.MIRROR_FOR); Assert.isTrue( ObjectUtil.isNotNull(mirrorAttributeAnnotation) && RelationType.MIRROR_FOR.equals(mirrorAttributeAnnotation.type()), "mirror attribute [{}] of original attribute [{}] must marked by @Link, and also @LinkType.type() must is [{}]", diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java index 2dcd0dd1d..20d2305ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java @@ -9,28 +9,25 @@ import cn.hutool.core.lang.Assert; * @see MirrorLinkAttributePostProcessor * @see RelationType#MIRROR_FOR */ -public class MirroredAnnotationAttribute extends AnnotationAttributeWrapper implements AnnotationAttribute { +public class MirroredAnnotationAttribute extends AbstractAnnotationAttributeWrapper { - private final AnnotationAttribute mirrorAttribute; - - public MirroredAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute mirrorAttribute) { - super(origin); - this.mirrorAttribute = mirrorAttribute; + public MirroredAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); } @Override public Object getValue() { - boolean originIsDefault = origin.isValueEquivalentToDefaultValue(); - boolean targetIsDefault = mirrorAttribute.isValueEquivalentToDefaultValue(); - Object originValue = origin.getValue(); - Object targetValue = mirrorAttribute.getValue(); + final boolean originIsDefault = original.isValueEquivalentToDefaultValue(); + final boolean targetIsDefault = linked.isValueEquivalentToDefaultValue(); + final Object originValue = original.getValue(); + final Object targetValue = linked.getValue(); // 都为默认值,或都为非默认值时,两方法的返回值必须相等 if (originIsDefault == targetIsDefault) { Assert.equals( originValue, targetValue, "the values of attributes [{}] and [{}] that mirror each other are different: [{}] <==> [{}]", - origin.getAttribute(), mirrorAttribute.getAttribute(), originValue, targetValue + original.getAttribute(), linked.getAttribute(), originValue, targetValue ); return originValue; } @@ -38,4 +35,14 @@ public class MirroredAnnotationAttribute extends AnnotationAttributeWrapper impl // 两者有一者不为默认值时,优先返回非默认值 return originIsDefault ? targetValue : originValue; } + + /** + * 当{@link #original}与{@link #linked}都为默认值时返回{@code true} + * + * @return 是否 + */ + @Override + public boolean isValueEquivalentToDefaultValue() { + return original.isValueEquivalentToDefaultValue() && linked.isValueEquivalentToDefaultValue(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java index ad016430e..5ff9e9bb3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import java.lang.annotation.Annotation; import java.util.Map; +import java.util.function.UnaryOperator; /** * 用于在{@link SyntheticAnnotation}中表示一个处于合成状态的注解对象 @@ -82,7 +83,7 @@ public interface SynthesizedAnnotation extends Annotation { */ default void setAttributes(Map attributes) { if (CollUtil.isNotEmpty(attributes)) { - attributes.forEach(this::setAttributes); + attributes.forEach(this::setAttribute); } } @@ -92,7 +93,15 @@ public interface SynthesizedAnnotation extends Annotation { * @param attributeName 属性名称 * @param attribute 注解属性 */ - void setAttributes(String attributeName, AnnotationAttribute attribute); + void setAttribute(String attributeName, AnnotationAttribute attribute); + + /** + * 替换属性值 + * + * @param attributeName 属性名 + * @param operator 替换操作 + */ + void replaceAttribute(String attributeName, UnaryOperator operator); /** * 获取属性值 diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java index c8b5513e3..e71208c8a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java @@ -88,7 +88,7 @@ class SyntheticAnnotationProxy implements InvocationHandler { methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String)args[0], (Class)args[1])); methods.put("getAttributes", (method, args) -> annotation.getAttributes()); - methods.put("setAttributes", (method, args) -> { + methods.put("setAttribute", (method, args) -> { throw new UnsupportedOperationException("proxied annotation can not reset attributes"); }); methods.put("getAttributeValue", (method, args) -> annotation.getAttributeValue((String)args[0])); diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationUtil.java index 1cec36aaa..a007adc75 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationUtil.java @@ -23,7 +23,7 @@ class SyntheticAnnotationUtil { */ static Link getLink(AnnotationAttribute attribute, RelationType... relationTypes) { return Opt.ofNullable(attribute) - .map(t -> t.getAnnotation(Link.class)) + .map(t -> AnnotationUtil.getSyntheticAnnotation(attribute.getAttribute(), Link.class)) .filter(a -> ArrayUtil.contains(relationTypes, a.type())) .get(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java index 3299421ea..11b04a714 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java @@ -10,6 +10,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.*; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -117,6 +118,7 @@ public class SyntheticMetaAnnotation implements SyntheticAnnotation { this.metaAnnotationMap = new LinkedHashMap<>(); // 初始化元注解信息,并进行后置处理 + // TODO 缓存元注解信息,避免重复解析 loadMetaAnnotations(); annotationPostProcessors.forEach(processor -> metaAnnotationMap.values().forEach(synthesized -> processor.process(synthesized, this)) @@ -411,10 +413,24 @@ public class SyntheticMetaAnnotation implements SyntheticAnnotation { * @param attribute 注解属性 */ @Override - public void setAttributes(String attributeName, AnnotationAttribute attribute) { + public void setAttribute(String attributeName, AnnotationAttribute attribute) { attributeMethodCaches.put(attributeName, attribute); } + /** + * 替换属性值 + * + * @param attributeName 属性名 + * @param operator 替换操作 + */ + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + AnnotationAttribute old = attributeMethodCaches.get(attributeName); + if (ObjectUtil.isNotNull(old)) { + attributeMethodCaches.put(attributeName, operator.apply(old)); + } + } + /** * 获取属性值 * diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java index 31403ed79..1deacec39 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.annotation; +import cn.hutool.core.util.ReflectUtil; import org.junit.Assert; import org.junit.Test; @@ -7,6 +8,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Method; /** * 合成注解{@link SyntheticMetaAnnotation}的测试用例 @@ -53,6 +55,15 @@ public class SyntheticMetaAnnotationTest { Assert.assertThrows(IllegalArgumentException.class, () -> new SyntheticMetaAnnotation(grandParentAnnotation)); } + @Test + public void linkTest() { + Method method = ReflectUtil.getMethod(AnnotationForLinkTest.class, "value"); + SyntheticAnnotation syntheticAnnotation = new SyntheticMetaAnnotation(method.getAnnotation(AliasFor.class)); + Link link = syntheticAnnotation.syntheticAnnotation(Link.class); + Assert.assertEquals(AnnotationForLinkTest.class, link.annotation()); + Assert.assertEquals("name", link.attribute()); + } + @Test public void mirrorAttributeTest() { AnnotationForMirrorTest annotation = ClassForMirrorTest.class.getAnnotation(AnnotationForMirrorTest.class); @@ -108,6 +119,31 @@ public class SyntheticMetaAnnotationTest { Assert.assertEquals("Foo", childAnnotation.value()); } + @Test + public void aliasForAndMirrorTest() { + AnnotationForMirrorThenAliasForTest annotation = ClassForAliasForAndMirrorTest.class.getAnnotation(AnnotationForMirrorThenAliasForTest.class); + SyntheticAnnotation synthetic = new SyntheticMetaAnnotation(annotation); + MetaAnnotationForMirrorThenAliasForTest metaAnnotation = synthetic.syntheticAnnotation(MetaAnnotationForMirrorThenAliasForTest.class); + Assert.assertEquals("test", metaAnnotation.name()); + Assert.assertEquals("test", metaAnnotation.value()); + AnnotationForMirrorThenAliasForTest childAnnotation = synthetic.syntheticAnnotation(AnnotationForMirrorThenAliasForTest.class); + Assert.assertEquals("test", childAnnotation.childValue()); + } + + @Test + public void multiAliasForTest() { + AnnotationForMultiAliasForTest annotation = ClassForMultiAliasForTest.class.getAnnotation(AnnotationForMultiAliasForTest.class); + SyntheticAnnotation synthetic = new SyntheticMetaAnnotation(annotation); + + MetaAnnotationForMultiAliasForTest1 metaAnnotation1 = synthetic.syntheticAnnotation(MetaAnnotationForMultiAliasForTest1.class); + Assert.assertEquals("test", metaAnnotation1.name()); + Assert.assertEquals("test", metaAnnotation1.value1()); + MetaAnnotationForMultiAliasForTest2 metaAnnotation2 = synthetic.syntheticAnnotation(MetaAnnotationForMultiAliasForTest2.class); + Assert.assertEquals("test", metaAnnotation2.value2()); + AnnotationForMultiAliasForTest childAnnotation = synthetic.syntheticAnnotation(AnnotationForMultiAliasForTest.class); + Assert.assertEquals("test", childAnnotation.value3()); + } + // 注解结构如下: // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation // -> @GrandParentAnnotation @@ -143,9 +179,11 @@ public class SyntheticMetaAnnotationTest { @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @interface AnnotationForMirrorTest { - @Link(attribute = "name") + //@Link(attribute = "name") + @MirrorFor(attribute = "name") String value() default ""; - @Link(attribute = "value") + //@Link(attribute = "value") + @MirrorFor(attribute = "value") String name() default ""; } @AnnotationForMirrorTest("Foo") @@ -164,10 +202,9 @@ public class SyntheticMetaAnnotationTest { @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @interface AnnotationForAliasForTest { - @Link( + @AliasFor( annotation = MetaAnnotationForAliasForTest.class, - attribute = "name", - type = RelationType.ALIAS_FOR + attribute = "name" ) String value() default ""; } @@ -197,4 +234,53 @@ public class SyntheticMetaAnnotationTest { @AnnotationForceForAliasForTest("Foo") static class ClassForForceAliasForTest2 {} + @interface AnnotationForLinkTest { + @AliasFor(attribute = "name", annotation = AnnotationForLinkTest.class) + String value() default "value"; + String name() default "name"; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMirrorThenAliasForTest { + @MirrorFor(attribute = "value") + String name() default ""; + @MirrorFor(attribute = "name") + String value() default ""; + } + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @MetaAnnotationForMirrorThenAliasForTest("Meta") + @interface AnnotationForMirrorThenAliasForTest { + @AliasFor(attribute = "name", annotation = MetaAnnotationForMirrorThenAliasForTest.class) + String childValue() default "value"; + } + @AnnotationForMirrorThenAliasForTest(childValue = "test") + static class ClassForAliasForAndMirrorTest{} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMultiAliasForTest1 { + @MirrorFor(attribute = "value1") + String name() default ""; + @MirrorFor(attribute = "name") + String value1() default ""; + } + @MetaAnnotationForMultiAliasForTest1 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMultiAliasForTest2 { + @AliasFor(attribute = "name", annotation = MetaAnnotationForMultiAliasForTest1.class) + String value2() default ""; + } + @MetaAnnotationForMultiAliasForTest2 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForMultiAliasForTest { + @AliasFor(attribute = "value2", annotation = MetaAnnotationForMultiAliasForTest2.class) + String value3() default "value"; + } + @AnnotationForMultiAliasForTest(value3 = "test") + static class ClassForMultiAliasForTest{} + }