浅谈组合注解 & 注解别名
前言
这篇文章很早就躺在了草稿里了,一直没有写 2333,最近在考试,因为都是一些相对简单的考试,同时又暂停的项目的开发,所以最近相对较闲,便打算把这个坑填一下。
什么是组合注解和注解别名?
如果你看过 Spring 的注解的源码,那么这两个概念一定不会陌生。
注解别名 指的注解的属性拥有别名的功能,让多个属性值表达同一个意思,如 Spring 的 @Bean
注解:
_7public @interface Bean {_7 @AliasFor("name")_7 String[] value() default {};_7_7 @AliasFor("value")_7 String[] name() default {};_7}
从代码中看到 value
属性和 name
属性是相同的,设置 value
和 name
任意一个值都代表了设置了 Bean 的名称。这就是注解别名。
组合注解 简单来说就是 Spring 自行实现的将多个注解组合到一个注解上的功能。如 Spring 里的 @RestController
注解:
_9@Target(ElementType.TYPE)_9@Retention(RetentionPolicy.RUNTIME)_9@Documented_9@Controller_9@ResponseBody_9public @interface RestController {_9 @AliasFor(annotation = Controller.class)_9 String value() default "";_9}
从代码中看到,@RestController
注解上标记了 @Controller
和 @ResponseBody
注解,这样 @RestController
就组合了 @Controller
和 @ResponseBody
的功能。
除了 组合注解 和 注解别名,Spring 还提供了类似于类继承的 注解继承 功能,比如 @RestController
的 value
属性上标记了 @AliasFor(annotation = Controller.class)
,此时若设置了 @RestContoller
的 value
属性,则代表设置了 @Contoller
的 value
属性。
Spring 是如何实现的?
首先我们先随便写一个 Demo:
_9@Controller_9public class CityController {_9_9 @GetMapping("/city")_9 public String index(Model model) {_9 model.addAttribute("provinces", CityConst.getProvinces());_9 return "city/index";_9 }_9}
_13@SpringBootTest_13@Slf4j_13class ApplicationTests {_13_13 @Test_13 void contextLoads() throws NoSuchMethodException {_13 final RequestMapping annotation = AnnotationUtils.getAnnotation(_13 CityController.class.getMethod("index", Model.class),_13 RequestMapping.class_13 );_13 annotation.path();_13 }_13}
启动调试会话,一路步入就可以看到以下的代码段:
_12final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnnotation<A> {_12_12 // ..._12_12 static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {_12 Assert.notNull(annotation, "Annotation must not be null");_12 AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType());_12 return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 0);_12 }_12_12 // ..._12}
可以看到,Spring 在创建 MergedAnnotation
前会先获取 AnnotationTypeMappings
,该对象保存了 MergedAnnotation.from
传入的注解及其所有父注解(非 JDK 注解)的 AnnotationTypeMapping
信息,AnnotationTypeMapping
里保存了根注解(Spring 保存的方式是自下向上的,所以这个的根注解是 from
传入的注解),源注解(下一级注解,如 RequestMapping
的源注解是 GetMapping
),别名索引数组,别名指向的方法等信息。然后获取当前传入注解的 AnnotationTypeMapping
组成 MergedAnnotation
。
不过有了 MergedAnnotation
那么如何把 MergedAnnotation
变成 Java 的注解呢?如果直接返回通过反射获取的注解,那么别名和子注解的值传上来的值就无法被更改,所以为了获得 Java 的注解,Spring 会重新创建该注解的注解代理类。
如果你看过 Java 注解的源码,Java 注解其实是通过 JDK 代理实现的,通过 JDK 代理从 AnnotationInvocationHandler
里的 memberValues
Map 获取注解值。Spring 就是使用类似的方式通过 MergedAnnotation.synthesize
方法调用了 SynthesizedMergedAnnotationInvocationHandler.createProxy
动态创建了 Java 注解,而 SynthesizedMergedAnnotationInvocationHandler
包装了 MergedAnnotation
和 AttributeMethods
。
_17final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> implements InvocationHandler {_17_17 private final MergedAnnotation<?> annotation;_17 private final AttributeMethods attributes;_17_17 // ..._17_17 static <A extends Annotation> A createProxy(MergedAnnotation<A> annotation, Class<A> type) {_17 ClassLoader classLoader = type.getClassLoader();_17 InvocationHandler handler = new SynthesizedMergedAnnotationInvocationHandler<>(annotation, type);_17 Class<?>[] interfaces = isVisible(classLoader, SynthesizedAnnotation.class) ?_17 new Class<?>[] {type, SynthesizedAnnotation.class} : new Class<?>[] {type};_17 return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);_17 }_17_17 // ..._17}
到此 Spring 处理组合注解的原理的关键部分就差不多讲完了,至于获取值部分就不说明了,只是简单的取到属性对应的方法,然后利用反射获取值。
实现
既然知道了原理,那么就可以自行实现一个组合注解 & 注解别名了。
方式
在进行编码前我们要先确定我们组合注解的实现方式。Spring 的实现方式较为复杂,所以我们不采用 Spring 的实现方法,而是直接对 Java 注解里的 memberValues
Map 动手,通过修改这个 Map 的值,我们就可以修改注解的属性值。不过需要注意,Java 注解是单例的,所以我们不能直接修改从反射获取的注解里的 memberValues
,而是要克隆一份,另外使用 JDK 动态代理创建注解对象。

代码
首先我们先准备 @AliasFor
别名注解:
_9@Target({ ElementType.METHOD })_9@Retention(RetentionPolicy.RUNTIME)_9public @interface AliasFor {_9 String value() default "";_9_9 Class<? extends Annotation> annotation() default Annotation.class;_9_9 String attribute() default "";_9}
为了方便实现重复注解我添加了一个 @RepeatItem
注解:
_5@Target({ ElementType.ANNOTATION_TYPE })_5@Retention(RetentionPolicy.RUNTIME)_5public @interface RepeatItem {_5 Class<? extends Annotation> value();_5}
然后就是注解工具类的一些基础方法:
_75public class AnnotationUtils {_75 @SuppressWarnings("unchecked")_75 private static Map<String, Object> getMemberValues(_75 final Annotation annotation_75 ) {_75 try {_75 final InvocationHandler invocationHandler = Proxy.getInvocationHandler(_75 annotation_75 );_75 final Field field = invocationHandler_75 .getClass()_75 .getDeclaredField("memberValues");_75 field.setAccessible(true);_75 return (Map<String, Object>) field.get(invocationHandler);_75 } catch (final Exception e) {_75 throw new RuntimeException("Get annotation member values failed");_75 }_75 }_75_75 public static boolean isDefaultValue(_75 final Method method,_75 final Map<String, Object> memberValues_75 ) {_75 return isDefaultValue(method, memberValues.get(method.getName()));_75 }_75_75 public static boolean isDefaultValue(_75 final Method method,_75 final Object value_75 ) {_75 final Object defaultValue = method.getDefaultValue();_75 if (method.getReturnType().isArray()) {_75 return Arrays.equals((Object[]) defaultValue, (Object[]) value);_75 } else {_75 return defaultValue.equals(value);_75 }_75 }_75_75 public static boolean isJdkAnnotation(_75 final Class<? extends Annotation> type_75 ) {_75 return (_75 type == Documented.class ||_75 type == Retention.class ||_75 type == Inherited.class ||_75 type == Native.class ||_75 type == Repeatable.class ||_75 type == Target.class_75 );_75 }_75_75 public static Class<? extends Annotation> getRepeatItem(_75 final Class<? extends Annotation> annotationType_75 ) {_75 final RepeatItem repeatItem = annotationType.getAnnotation(_75 RepeatItem.class_75 );_75 if (repeatItem == null) {_75 return null;_75 }_75 return repeatItem.value();_75 }_75_75 public static Class<? extends Annotation> getRepeatable(_75 final Class<? extends Annotation> annotationType_75 ) {_75 final Repeatable repeatable = annotationType.getAnnotation(_75 Repeatable.class_75 );_75 if (repeatable == null) {_75 return null;_75 }_75 return repeatable.value();_75 }_75}
为了创建注解代理类,我们还需要一个 InvocationHandler
用于代理属性方法,我懒得写了就直接把 JDK 里的 AnnotationInvocationHandler
拷贝了出来(JDK 里的是私有的不方便使用),由于代码太长了,这里就 不贴了,可以到 Github 上查看。
然后就是相应的代理工具方法:
_13public class AnnotationUtils {_13 @SuppressWarnings("unchecked")_13 public static <A extends Annotation> A annotationForMap(_13 final Class<A> annotationType,_13 final Map<String, Object> memberValues_13 ) {_13 return (A) Proxy.newProxyInstance(_13 annotationType.getClassLoader(),_13 new Class[] { annotationType },_13 new AnnotationInvocationHandler(annotationType, memberValues)_13 );_13 }_13}
然后就是最关键的合并注解值的方法了:
_56public class AnnotationUtils {_56 private static Map<String, Object> mergeAnnotationValue(_56 final Annotation annotation,_56 final Map<Class<? extends Annotation>, Map<String, Object>> overwriteMap_56 ) {_56 final Class<? extends Annotation> annotationType = annotation.annotationType();_56 final Map<String, Object> overwrite = overwriteMap.get(annotationType);_56 // 原始 memberValues,不可修改,因为这是单例的_56 final Map<String, Object> memberValues = getMemberValues(annotation);_56 // 实际复制并操作后的 memberValues_56 final Map<String, Object> values = new HashMap<>(memberValues.size());_56 for (Entry<String, Object> entry : memberValues.entrySet()) {_56 final String attributeName = entry.getKey();_56 Object attributeValue = entry.getValue();_56 final Method method = ReflectUtil.getMethod(_56 annotationType,_56 attributeName_56 );_56 final AliasFor aliasFor = method.getAnnotation(AliasFor.class);_56 if (overwrite != null && overwrite.containsKey(attributeName)) {_56 // 如果从子元素设置了重写的值,那么就设置该值_56 attributeValue = overwrite.get(attributeName);_56 } else if (_56 aliasFor != null &&_56 !aliasFor.value().isEmpty() &&_56 isDefaultValue(method, memberValues)_56 ) {_56 // 如果为默认值,同时设置了 AliasFor.value 那么就使用别名的值(即使是默认值也一样)_56 final String alias = aliasFor.value();_56 if (overwrite != null && overwrite.containsKey(alias)) {_56 attributeValue = overwrite.get(alias);_56 } else {_56 attributeValue = memberValues.get(alias);_56 }_56 }_56 // 否则把自身 memberValues 值设置到新 Map 中_56 values.put(attributeName, attributeValue);_56 // 如果设置了 AliasFor.annotation 那么就设置父注解的重写值_56 if (aliasFor != null && aliasFor.annotation() != Annotation.class) {_56 final Class<? extends Annotation> parentType = aliasFor.annotation();_56 final Map<String, Object> parentOverwrite = overwriteMap.getOrDefault(_56 parentType,_56 new HashMap<>()_56 );_56 parentOverwrite.put(_56 aliasFor.attribute().isEmpty()_56 ? attributeName_56 : aliasFor.attribute(),_56 attributeValue_56 );_56 overwriteMap.put(parentType, parentOverwrite);_56 }_56 }_56 return values;_56 }_56}
梳理下流程会更方便阅读:
首先我们要明确一点,注解的处理顺序是先子注解,然后父注解。
- 获取原始
memberValues
的 Map - 创建克隆的空
memberValues
- 循环遍历原始
memberValues
,取出每一项的值 - 通过属性名称从注解
Class
查找Method
,然后取得@AliasFor
的注解 - 如果子注解有传上来覆盖的值,那么就使用这个值(
overwriteMap
里存放) - 否则看下有没有设置
@AliasFor
注解及value
值(别名)如果设置了,则判断是否是默认值(Method
可以获取方法默认值,也就是属性默认值),如果不是默认值,那么这个值就是被设置过的,此时就不应该覆盖它。 - 如果设置了
@AliasFor
注解同时不是默认值,那么就获取别名的值(注意这个 别名的值也有可能是子注解传上来的,所以需要判断下overwriteMap
里有没有设置) - 最后将获得的最终的值存入克隆的
memberValues
中 - 然后处理要传到父注解的值,如果设置了
@AliasFor
的annotation
值,那么就将这个值设置到对应的overwriteMap
里,这样父注解在处理的时候就可以获取到这个值了
有了处理注解值的方法,那么就可以写遍历注解的方法了:
_44public class AnnotationUtils {_44 private static void walkAnnotation(_44 final AnnotatedElement element,_44 Map<Class<? extends Annotation>, List<Map<String, Object>>> annotationMap,_44 Map<Class<? extends Annotation>, Map<String, Object>> overwriteMap_44 ) {_44 for (Annotation annotation : element.getAnnotations()) {_44 final Class<? extends Annotation> annotationType = annotation.annotationType();_44 if (isJdkAnnotation(annotationType)) {_44 continue;_44 }_44 // 处理当前注解_44 final List<Map<String, Object>> annotations = annotationMap.getOrDefault(_44 annotationType,_44 new ArrayList<>()_44 );_44 for (Annotation item : element.getAnnotationsByType(_44 annotationType_44 )) {_44 annotations.add(mergeAnnotationValue(item, overwriteMap));_44 }_44 annotationMap.put(annotationType, annotations);_44 // 处理重复注解_44 final Class<? extends Annotation> repeatItem = getRepeatItem(_44 annotationType_44 );_44 if (repeatItem != null) {_44 final List<Map<String, Object>> itemList = annotationMap.getOrDefault(_44 repeatItem,_44 new ArrayList<>()_44 );_44 for (final Annotation item : (Annotation[]) ReflectUtil.invoke(_44 annotation,_44 "value"_44 )) {_44 itemList.add(mergeAnnotationValue(item, overwriteMap));_44 }_44 annotationMap.put(repeatItem, itemList);_44 }_44 // 处理父注解_44 walkAnnotation(annotationType, annotationMap, overwriteMap);_44 }_44 }_44}
照样写个流程吧:
- 因为传入的是可被标注的元素,所以就取出这个元素所标注的所有注解,遍历
- 如果是 JDK 注解就直接跳过,因为这些注解对我们的程序无用,而且会导致无限递归
- 接着就是使用
AnnotatedElement
的getAnnotationsByType
方法获取标注在元素上的所有指定注解类型的值。之所以要这么做是因为 Java 支持重复注解,通过getAnnotations
的方法只能取得一个同类型的注解 - 取得注解后就调用
mergeAnnotationValue
方法合并注解值,同时把要传到父注解的值存入overwriteMap
中 - 除了 Java 标准的重复注解,我们还常用带 s 的注解包裹来做到重复注解,如
@MapperScans
和@MapperScan
此时就需要特殊处理,这里使用的方法是在带 s 的注解里添加一个@RepeatItem
注解,然后该注解存储单个注解的类型,这样就能把带 s 注解里的单个注解存到它对应类型的注解里。 - 处理完当前注解后就接着处理父注解(递归处理)
遍历完所有的注解后,就取得了可标注元素的所有注解处理过的 memberValues
,此时就可以将 memberValues
转成注解了。
_45public class AnnotationUtils {_45 private static Map<Class<? extends Annotation>, List<Annotation>> annotationForMergeAnnotation(_45 Map<Class<? extends Annotation>, List<Map<String, Object>>> annotationMap_45 ) {_45 return annotationMap_45 .entrySet()_45 .stream()_45 .collect(_45 Collectors.toMap(_45 Entry::getKey,_45 e ->_45 e_45 .getValue()_45 .stream()_45 .map(_45 i ->_45 AnnotationUtils.annotationForMap(_45 e.getKey(),_45 i_45 )_45 )_45 .collect(Collectors.toList()),_45 (u, v) -> {_45 throw new IllegalStateException(_45 String.format("Duplicate key %s", u)_45 );_45 },_45 LinkedHashMap::new_45 )_45 );_45 }_45_45 public static Map<Class<? extends Annotation>, List<Annotation>> mergeAnnotation(_45 final AnnotatedElement element_45 ) {_45 return MERGED_ANNOTATION_CACHE.computeIfAbsent(_45 element,_45 k -> {_45 final Map<Class<? extends Annotation>, List<Map<String, Object>>> annotationMap = new LinkedHashMap<>();_45 walkAnnotation(k, annotationMap, new HashMap<>());_45 return annotationForMergeAnnotation(annotationMap);_45 }_45 );_45 }_45}
此时就有了标注在可标注元素上所有的注解了,有了这些就可以封装成组合注解了:
首先是 MergedAnnotation
的接口:
_237public interface MergedAnnotation {_237 String VALUE = "value";_237 Logger log = LoggerFactory.getLogger(MergedAnnotation.class);_237_237 /**_237 * 获取所有注解_237 *_237 * @return 注解 Map_237 */_237 Map<Class<? extends Annotation>, List<Annotation>> annotations();_237_237 /**_237 * 获取注解_237 *_237 * @param annotationType 注解类型_237 * @param <A> 注解类型_237 * @return 注解_237 */_237 default <A extends Annotation> A getAnnotation(_237 final Class<A> annotationType_237 ) {_237 return this.getAnnotation(annotationType, 0);_237 }_237_237 /**_237 * 获取注解_237 *_237 * @param annotationType 注解类型_237 * @param index 索引_237 * @param <A> 注解类型_237 * @return 注解_237 */_237 @SuppressWarnings("unchecked")_237 default <A extends Annotation> A getAnnotation(_237 final Class<A> annotationType,_237 final int index_237 ) {_237 final List<Annotation> annotations =_237 this.annotations().get(annotationType);_237 if (_237 annotations == null ||_237 annotations.isEmpty() ||_237 index < 0 ||_237 index >= annotations.size()_237 ) {_237 return null;_237 }_237 if (annotations.size() > 1) {_237 log.warn(_237 "Annotation [{}] is multi, but only get one",_237 annotationType.getName()_237 );_237 }_237 return (A) annotations.get(index);_237 }_237_237 /**_237 * 获取指定类型的注解列表_237 *_237 * @param annotationType 注解类型_237 * @param <A> 注解类型_237 * @return 注解列表_237 */_237 @SuppressWarnings("unchecked")_237 default <A extends Annotation> List<A> getAnnotations(_237 Class<A> annotationType_237 ) {_237 return (List<A>) this.annotations()_237 .getOrDefault(annotationType, Collections.emptyList());_237 }_237_237 /**_237 * 是否存在注解_237 *_237 * @param annotationType 注解类型_237 * @return 是否存在_237 */_237 default boolean hasAnnotation(_237 final Class<? extends Annotation> annotationType_237 ) {_237 return this.annotations().containsKey(annotationType);_237 }_237_237 /**_237 * 是否不存在注解_237 *_237 * @param annotationType 注解类型_237 * @return 是否不存在_237 */_237 default boolean notAnnotation(Class<? extends Annotation> annotationType) {_237 return !this.hasAnnotation(annotationType);_237 }_237_237 /**_237 * 是否包含多个相同类型的注解_237 *_237 * @param annotationType 注解类型_237 * @return 是否包含_237 */_237 default boolean hasMultiAnnotation(_237 final Class<? extends Annotation> annotationType_237 ) {_237 return (_237 this.annotations().containsKey(annotationType) &&_237 this.annotations().get(annotationType).size() != 1_237 );_237 }_237_237 /**_237 * 添加注解_237 *_237 * @param annotation 注解_237 */_237 default void addAnnotation(Annotation annotation) {_237 throw new UnsupportedOperationException(_237 "Unsupported add annotation to merge annotation"_237 );_237 }_237_237 /**_237 * 删除注解_237 *_237 * @param annotationType 注解类型_237 */_237 default void removeAnnotation(Class<? extends Annotation> annotationType) {_237 throw new UnsupportedOperationException(_237 "Unsupported remove annotation to merge annotation"_237 );_237 }_237_237 /**_237 * 删除注解_237 *_237 * @param annotationType 注解类型_237 * @param index 索引_237 */_237 default void removeAnnotation(_237 Class<? extends Annotation> annotationType,_237 int index_237 ) {_237 throw new UnsupportedOperationException(_237 "Unsupported add annotation to merge annotation"_237 );_237 }_237_237 /**_237 * 获取注解值_237 *_237 * @param annotationType 注解类型_237 * @return 注解值_237 */_237 default Object get(Class<? extends Annotation> annotationType) {_237 return this.get(annotationType, VALUE);_237 }_237_237 /**_237 * 获取注解值_237 *_237 * @param annotationType 注解类型_237 * @param name 方法名称_237 * @return 注解值_237 */_237 default Object get(_237 Class<? extends Annotation> annotationType,_237 String name_237 ) {_237 return this.get(annotationType, name, Object.class);_237 }_237_237 /**_237 * 获取注解值_237 *_237 * @param annotationType 注解类型_237 * @return 注解值_237 */_237 default <T> T get(_237 Class<? extends Annotation> annotationType,_237 Class<T> returnType_237 ) {_237 return this.get(annotationType, VALUE, returnType);_237 }_237_237 /**_237 * 获取注解值_237 *_237 * @param annotationType 注解类型_237 * @param name 方法名称_237 * @param returnType 返回类型_237 * @param <T> 注解值类型_237 * @return 注解值_237 */_237 default <T> T get(_237 Class<? extends Annotation> annotationType,_237 String name,_237 Class<T> returnType_237 ) {_237 return this.get(annotationType, name, returnType, 0);_237 }_237_237 /**_237 * 获取注解值_237 *_237 * @param annotationType 注解类型_237 * @param name 方法名称_237 * @param returnType 返回类型_237 * @param index 索引_237 * @param <T> 注解值类型_237 * @return 注解值_237 */_237 default <T> T get(_237 Class<? extends Annotation> annotationType,_237 String name,_237 Class<T> returnType,_237 int index_237 ) {_237 final Annotation annotation = this.getAnnotation(annotationType, index);_237 final Method method = ReflectUtil.getMethodByName(annotationType, name);_237 if (annotation == null || method == null) {_237 return null;_237 }_237 return Convert.convert(_237 returnType,_237 ReflectUtil.invoke(annotation, method)_237 );_237 }_237_237 static MergedAnnotation from(AnnotatedElement element) {_237 return new MergedAnnotationImpl(element);_237 }_237_237 static boolean has(_237 AnnotatedElement element,_237 Class<? extends Annotation> annotationType_237 ) {_237 return from(element).getAnnotation(annotationType) != null;_237 }_237}
然后就是实现类:
_13public class MergedAnnotationImpl implements MergedAnnotation {_13_13 private final Map<Class<? extends Annotation>, List<Annotation>> annotations;_13_13 public MergedAnnotationImpl(final AnnotatedElement element) {_13 this.annotations = AnnotationUtils.mergeAnnotation(element);_13 }_13_13 @Override_13 public Map<Class<? extends Annotation>, List<Annotation>> annotations() {_13 return this.annotations;_13 }_13}
到这里我们自己的组合注解就实现完成了,让我们来使用下吧:
_5@Target(ElementType.TYPE)_5@Retention(RetentionPolicy.RUNTIME)_5@Parent(name = "123")_5public @interface Children1 {_5}
_13@Children1_13class MergedAnnotationTest {_13_13 @Test_13 void from() {_13 final MergedAnnotation annotation = MergedAnnotation.from(_13 MergedAnnotationTest.class_13 );_13 final Parent parent = annotation.getAnnotation(Parent.class);_13 assertEquals("123", parent.name());_13 assertEquals("123", parent.value());_13 }_13}
结语
组合注解简单化注解配置,用很少的注解来标注特定含义的多个元注解,同时提供了很好的扩展性,可以根据实际需要灵活的自定义注解。经过组合注解的重构后,我们就不再需要写很多注解处理器,避免了重复,同时也不易出错。
浅谈组合注解 & 注解别名
https://blog.ixk.me/post/talking-about-merged-annotation许可协议
发布于
2021-01-15
本文作者
Otstar Lin
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!