spring探秘之组合注解的处理

您所在的位置:网站首页 快门网络科技有限公司怎么样 spring探秘之组合注解的处理

spring探秘之组合注解的处理

2024-07-15 02:11:51| 来源: 网络整理| 查看: 265

注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:funcy/spring-framework.

1. 什么是组合注解?

在spring中,有一类特别的注解:组合注解。举例来说,springmvc中,@Controller注解用来配置访问路径等,@ResponseBody 注解用来表明不做视图渲染,直接展示方法的运行结果(一般是转成json返回),而@RestController组合了两者的功能,可以配置访问路径,同时也可以直接展示方法的运行结果,代码如下:

@Controller @ResponseBody public @interface RestController { /** * 注解别名 */ @AliasFor(annotation = Controller.class) String value() default ""; }

可以看到,@RestController上标记了两个注解:@Controller与@ResponseBody,这样它就同时拥有了两者的功能。

再来看一个例子,spring中,我们在标识一个类为spring bean的时候,可以用到这些注解:@Component、@Repository、@Service等,再进一步看其代码,发现@Repository、@Service中都有@Component:

@Component public @interface Repository { @AliasFor(annotation = Component.class) String value() default ""; } @Component public @interface Service { @AliasFor(annotation = Component.class) String value() default ""; }

也就是说,@Repository、@Service都组合了@Component的功能!

实际上,如果我们自己写一个注解,像这样:

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface MyComponent { @AliasFor(annotation = Component.class) String value() default ""; }

然后这样使用:

@MyComponent("beanObj3") public class BeanObj3 { ... }

spring 依然会把BeanObj3初始化为spring bean。

那么spring是如何做到这一步的呢?实际上,spring在处理@MyComponent时,会判断该注解中是否包含@Component注解,如果包含,就获取该注解的配置,然后按@Component的处理逻辑来进行处理。

同样地,spring在处理@RestController时,如果当前是处理@Controller的逻辑,就从@RestController中获取@Controller的配置然后进行处理,如果当前是处理@ResponseBody逻辑,就从@RestController中获取@ResponseBody的配置然后进行处理。

2. 递归获取指定类的所有注解

问题又来了:组合注解中的注解要怎么获取呢?

如果按照jdk提供的方法,像这样:

RestController annotation = BeanObj3.class.getAnnotation(MyComponent.class);

得到的annotation必定为null,原因是Class#getAnnotation方法只能获取到类上直接出现的注解,BeanObj3是没有直接出现@Component的,因此得到的结果为null,办法也许你也想到了,就是继续往下读取"注解的注解",用代码示意下,类似这样:

public class AnnotationHandler { /** * 存放jdk提供的元注解 */ private static Set> list = getAnnotations(BeanObj3.class); System.out.println(list); } /** * 获取操作,递归调用 */ public static List> list = new ArrayList(); // 调用 doGetAnnotations(...) 获取 doGetAnnotations(list, cls); return list; } /** * 获取注解的具体操作 */ private static void doGetAnnotations(List cls) { // 获取所有的注解 Annotation[] annotations = cls.getAnnotations(); if(annotations != null && annotations.length > 0) { for(Annotation annotation : annotations) { // 获取注解的类型 Class annotationType = annotation.annotationType(); // 过滤jdk提供的元注解 if(metaAnnotations.contains(annotationType)) { continue; } // 递归调用 doGetAnnotations(list, annotationType); } } // 如果是注解,就添加到 list 中 if(cls.isAnnotation()) { list.add(cls); } } }

我们要获取BeanObj3上所有注解,就可以这样操作了:

// 得到 BeanObj3 上的所有注解,包括“注解的注解” List annotation = getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent); if (!annotation.isPresent()) { return Collections.emptySet(); } return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS) .stream() .map(mergedAnnotation -> mergedAnnotation.getType().getName()) .collect(Collectors.toCollection(LinkedHashSet::new)); } default boolean hasAnnotation(String annotationName) { // 调用了 getAnnotations() return getAnnotations().isDirectlyPresent(annotationName); } ... }

再进一步查看getAnnotations()方法,进入了AnnotatedTypeMetadata:

public interface AnnotatedTypeMetadata { /** * 获取注解 */ MergedAnnotations getAnnotations(); ... }

这一样看,似乎注解得到的终极类就是MergedAnnotations了?我们继续探索。

MergedAnnotations

MergedAnnotations的部分注释如下:

Provides access to a collection of merged annotations, usually obtained from a source such as a {@link Class} or {@link Method}.

提供对组合注解的集合的访问,这些注解通常是从Class或Method之类的来源获得的。

看来,MergedAnnotations 才是最终的组合注解的集合了,我们来看看它的几个方法:

// 判断注解是否存在,会从所有的注解中判断 boolean isPresent(Class annotationType); // 判断注解是否存在,会从所有的注解中判断,与上面的方法不同的是,这里传入的是字符串 boolean isPresent(String annotationType); // 判断直接注解是否存在,也就是只判断当前类上有没有该注解,不判断注解的注解 boolean isDirectlyPresent(Class annotationType); // 功能同上,这里传入的类型是字符串,格式为"包名.类名" boolean isDirectlyPresent(String annotationType); // 获取注解 MergedAnnotation get(Class annotationType); // 获取注解,这里传入的类型是字符串,格式为"包名.类名" MergedAnnotation get(String annotationType);

从方法上大致可以看出,MergedAnnotations是组合注解的集合,提供的注解可以判断某注解是否存在,也可以获取其中的某个注解。

MergedAnnotation

MergedAnnotations 是注解的集合,那这个集合中放的是啥呢?从它的get(...)方法来看,它存放的是MergedAnnotation,我们再来看看MergedAnnotation支持的方法:

从以上的方法可以看到,MergedAnnotation 就是注解的数据抽象,它提供了丰富的api用来获取注解的数据。

使用示例

下面来看个示例:

// 得到 SimpleMetadataReaderFactory 实例,最终调用的是 SimpleAnnotationMetadataReadingVisitor 来读取 SimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(); MetadataReader metadataReader = readerFactory.getMetadataReader(BeanObj3.class.getName()); AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); // AnnotationMetadata 提供了许多的操作,重点关注注解相关的 Set annotationTypes = annotationMetadata.getAnnotationTypes(); System.out.println("-------------"); annotationTypes.forEach(type -> System.out.println(type)); System.out.println("-------------"); // 这里是直接获取,BeanObj3 上直接标记 @MyComponent的,返回的是true boolean exist1 = annotationMetadata.hasAnnotation(MyComponent.class.getName()); System.out.println("hasAnnotation @MyComponent:" + exist1); // 这里是直接获取,BeanObj3 上是没有直接标记 @Component的,返回的是false boolean exist2 = annotationMetadata.hasAnnotation(Component.class.getName()); System.out.println("hasAnnotation @Component:" + exist2); // 获取 MergedAnnotations MergedAnnotations annotations = annotationMetadata.getAnnotations(); System.out.println("-------------"); annotations.forEach(annotationMergedAnnotation -> System.out.println(annotationMergedAnnotation)); System.out.println("-------------"); // 这里是直接获取,BeanObj3 上是没有直接标记 @Component的,返回的是false boolean directlyPresent = annotations.isDirectlyPresent(Component.class); System.out.println("directlyPresent Component:" + directlyPresent); // 判断有没有这个注解,BeanObj3 上的@MyComponent中,标记了 @Component 的,返回的是true boolean present = annotations.isPresent(Component.class); System.out.println("present Component:" + present); // 获取 @Component 注解 MergedAnnotation mergedAnnotation = annotations.get(Component.class); // 由于 @MyComponent 的 value() 加了 @AliasFor(annotation = Component.class) // 因此这里得到的 value 是 beanObj3 (BeanObj3里这么指定的:@MyComponent("beanObj3")) String value = mergedAnnotation.getString("value"); System.out.println("Component value:" + value); // 将 @Component 的注解的数据转换为 AnnotationAttributes AnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes(); System.out.println(annotationAttributes);

运行,结果如下:

------------- org.springframework.learn.explore.demo01.MyComponent ------------- hasAnnotation @MyComponent:true hasAnnotation @Component:false ------------- @org.springframework.learn.explore.demo01.MyComponent(value=beanObj3) @org.springframework.stereotype.Component(value=beanObj3) @org.springframework.stereotype.Indexed() ------------- directlyPresent Component:false present Component:true Component value:beanObj3 {value=beanObj3} 补充:AnnotationAttributes

补充说明下AnnotationAttributes:

public class AnnotationAttributes extends LinkedHashMap { ... }

它实现了LinkedHashMap,提供的部分方法如下:

从这里不难看出,AnnotationAttributes就是包含注解所有属性值的map,key为属性名,value为属性值。

3.2 StandardAnnotationMetadata

我们接着来看看StandardAnnotationMetadata:

public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { /** * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class. * @param introspectedClass the Class to introspect * @see #StandardAnnotationMetadata(Class, boolean) * @deprecated since 5.2 in favor of the factory method * {@link AnnotationMetadata#introspect(Class)} */ @Deprecated public StandardAnnotationMetadata(Class introspectedClass) { this(introspectedClass, false); } ... }

StandardAnnotationMetadata实现了AnnotationMetadata接口,对于注解的操作与上面介绍的AnnotationMetadata并无太大区别,这里就不赘述了。

从StandardAnnotationMetadata的构造方法来看,它已经废弃了,让我们使用AnnotationMetadata#introspect(Class)来获取StandardAnnotationMetadata的实例,于是,我们可以像这样来操作:

// 获取到的 annotationMetadata 实际上是 StandardAnnotationMetadata AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(BeanObj3.class); //----------- 以下内容与SimpleAnnotationMetadataReadingVisitor 一模一样 // AnnotationMetadata 提供了许多的操作,重点关注注解相关的 Set annotationTypes = annotationMetadata.getAnnotationTypes(); System.out.println("-------------"); annotationTypes.forEach(type -> System.out.println(type)); System.out.println("-------------"); // 这里是直接获取,BeanObj3 上直接标记 @MyComponent的,返回的是true boolean exist1 = annotationMetadata.hasAnnotation(MyComponent.class.getName()); System.out.println("hasAnnotation @MyComponent:" + exist1); // 这里是直接获取,BeanObj3 上是没有直接标记 @Component的,返回的是false boolean exist2 = annotationMetadata.hasAnnotation(Component.class.getName()); System.out.println("hasAnnotation @Component:" + exist2); // 获取 MergedAnnotations MergedAnnotations annotations = annotationMetadata.getAnnotations(); System.out.println("-------------"); annotations.forEach(annotationMergedAnnotation -> System.out.println(annotationMergedAnnotation)); System.out.println("-------------"); // 这里是直接获取,BeanObj3 上是没有直接标记 @Component的,返回的是false boolean directlyPresent = annotations.isDirectlyPresent(Component.class); System.out.println("directlyPresent Component:" + directlyPresent); // 判断有没有这个注解,BeanObj3 上的@MyComponent中,标记了 @Component 的,返回的是true boolean present = annotations.isPresent(Component.class); System.out.println("present Component:" + present); // 获取 @Component 注解 MergedAnnotation mergedAnnotation = annotations.get(Component.class); // 由于 @MyComponent 的 value() 加了 @AliasFor(annotation = Component.class) // 因此这里得到的 value 是 beanObj3 (BeanObj3里这么指定的:@MyComponent("beanObj3")) String value = mergedAnnotation.getString("value"); System.out.println("Component value:" + value); // 将 @Component 的注解的数据转换为 AnnotationAttributes AnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes(); System.out.println(annotationAttributes);

运行结果如下:

------------- org.springframework.learn.explore.demo01.MyComponent ------------- hasAnnotation @MyComponent:true hasAnnotation @Component:false ------------- @org.springframework.learn.explore.demo01.MyComponent(value=beanObj3) @org.springframework.stereotype.Component(value=beanObj3) @org.springframework.stereotype.Indexed() ------------- directlyPresent Component:false present Component:true Component value:beanObj3 {value=beanObj3}

从上面的示例来看,我们历经千辛万苦,最终得到了MergedAnnotations,然后通过它来判断注解是否存在、获取注解的值。

3.3 两者的使用场景

SimpleAnnotationMetadataReadingVisitor与StandardAnnotationMetadata的主要区别在于,SimpleAnnotationMetadataReadingVisitor是基于asm的实现,StandardAnnotationMetadata是基于反射的实现,那我们在使用时,应该要怎么选呢?

由于基于反射是要先加类加载到jvm中的,因此我的判断是,如果当前类没有加载到jvm中,就使用SimpleAnnotationMetadataReadingVisitor,如果类已经加载到jvm中了,两者皆可使用。

事实上,在spring包扫描阶段,读取类上的注解时,使用的都是SimpleAnnotationMetadataReadingVisitor,因为此时类并没有加载到jvm,如果使用StandardAnnotationMetadata读取,就会导致类提前加载。类提前加载有什么问题呢?java类是按需加载的,有的类可能在整个jvm生命周期内都没用到,如果全都加载了,就白白浪费内存了。

4. spring提供的注解工具类

在前面的示例中,我们是这样读取注解的:

// 读取 annotationMetadata,也可以使用 SimpleMetadataReaderFactory 读取 AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(BeanObj3.class); MergedAnnotations annotations = annotationMetadata.getAnnotations(); // 判断注解是否存在 boolean present = annotations.isPresent(Component.class); // 获取注解的属性 MergedAnnotation mergedAnnotation = annotations.get(Component.class); AnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes();

相对来说,获取注解的属性步骤比较多,聪明如你,就想到可以将这些步骤封装到一个方法中进行处理,spring也是这么做的,这就得介绍spring中与注解相关的两个类:AnnotationUtils与AnnotatedElementUtils。AnnotationUtils 是直接获取注解的值,不会处理属性覆盖,而AnnotatedElementUtils会处理属性覆盖。

什么是属性覆盖呢?

举例来说,@MyComponent 长这样:

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented // 注意Component指定的值:123 @Component("123") public @interface MyComponent { @AliasFor(annotation = Component.class) String value() default ""; }

在@MyComponent注解中,我们指定了@Component的value值为“123”,然后又这么指定@MyComponent的value值:

@MyComponent("beanObj3") public class BeanObj3 { ... }

最终spring初始化得到的BeanObj3的名称是123还是beanObj3呢?从我们设置@MyComponent的value为beanObj3来说,当然是希望 bean 的名称为beanObj3,而最终spring也是这么做的,这就是属性覆盖了:@MyComponent的value覆盖了@Component的value值。

AnnotationUtils/AnnotatedElementUtils与上面介绍的SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata是何关系呢?

在我们使用SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata时,我们需要得到MergedAnnotations再进行一系列操作(判断注解是否存在、获取注解的属性值等),如果进入AnnotationUtils/AnnotatedElementUtils的源码,就会发现它们的相关方法也是操作MergedAnnotations类,比如获取注解:

AnnotationUtils#getAnnotation(AnnotatedElement, Class) 方法:

public static A getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { if (AnnotationFilter.PLAIN.matches(annotationType) || AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) { return annotatedElement.getAnnotation(annotationType); } // 通过操作 MergedAnnotations 进行获取 return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none()) .get(annotationType).withNonMergedAttributes() .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null); }

AnnotatedElementUtils#getAllMergedAnnotations(AnnotatedElement, Class) 方法:

public static Set getAllMergedAnnotations( AnnotatedElement element, Class annotationType) { return getAnnotations(element).stream(annotationType) .collect(MergedAnnotationCollectors.toAnnotationSet()); } // AnnotatedElementUtils#getAnnotations 方法,也是操作 MergedAnnotations 的方法 private static MergedAnnotations getAnnotations(AnnotatedElement element) { return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none()); }

因此,AnnotationUtils/AnnotatedElementUtils与SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata底层都是操作MergedAnnotations类的。

4.1 AnnotationUtils

AnnotationUtils支持的部分方法如下:

我们来实际使用下这些方法:

// 在 BeanObj3 获取 @Component Annotation annotation = AnnotationUtils.getAnnotation(BeanObj3.class, Component.class); if(null == annotation) { System.out.println("注解不存在!"); return; } System.out.println("annotation: " + annotation); // 获取 AnnotationAttributes AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(BeanObj3.class, annotation); System.out.println("AnnotationAttributes: " + annotationAttributes); // 获取 annotationAttributeMap Map annotationAttributeMap = AnnotationUtils.getAnnotationAttributes(annotation); System.out.println("annotationAttributeMap: " + annotationAttributeMap); // 获取value的值 Object value = AnnotationUtils.getValue(annotation, "value"); System.out.println("value: " + value);

结果如下:

annotation: @org.springframework.stereotype.Component(value=123) AnnotationAttributes: {value=123} annotationAttributeMap: {value=123} value: 123

从结果来看,直接通过 AnnotationUtils.getAnnotation(...) 也是能获取到@Component注解的,尽管BeanObj3并没有直接标记@Component.需要注意的是,这样获取到的@Component的value值是"123",并不是@MyComponent设置的beanObj3,这也证明了AnnotationUtils获取属性值时并不进行属性覆盖操作。

4.2 AnnotatedElementUtils

AnnotatedElementUtils支持的部分方法如下:

给个示例吧:

// 1. 判断是否有 Component 注解 boolean result = AnnotatedElementUtils.hasAnnotation(BeanObj3.class, Component.class); System.out.println("hasAnnotation: " + result); // 2. 获取 attributeMap,可以看到的是,获取 @Component 与 @MyComponent 得到的结果不一样 // Component attributeMap: {value=[123]} MultiValueMap attributeMap1 = AnnotatedElementUtils .getAllAnnotationAttributes(BeanObj3.class, Component.class.getName()); System.out.println("Component attributeMap: " + attributeMap1); // MyComponent attributeMap: {value=[beanObj3]} MultiValueMap attributeMap2 = AnnotatedElementUtils .getAllAnnotationAttributes(BeanObj3.class, MyComponent.class.getName()); System.out.println("MyComponent attributeMap: " + attributeMap2); // 3. 获取所有的 @Component 注解,value=beanObj3 Set mergedAnnotations = AnnotatedElementUtils .getAllMergedAnnotations(BeanObj3.class, Component.class); System.out.println("mergedAnnotations: " + mergedAnnotations); // 4. 获取属性值,{value=beanObj3} AnnotationAttributes attributes = AnnotatedElementUtils .getMergedAnnotationAttributes(BeanObj3.class, Component.class); System.out.println("attributes: " + attributes); // 5. 获取 MyComponent 上的注解 Set types = AnnotatedElementUtils .getMetaAnnotationTypes(BeanObj3.class, MyComponent.class); System.out.println("types: " + types);

结果如下:

hasAnnotation: true Component attributeMap: {value=[123]} MyComponent attributeMap: {value=[beanObj3]} mergedAnnotations: [@org.springframework.stereotype.Component(value=beanObj3)] attributes: {value=beanObj3} types: [org.springframework.stereotype.Component, org.springframework.stereotype.Indexed]

从代码来看,在得到的Set与AnnotationAttributes中,属性值已经合并了.

在选择使用AnnotationUtils还是AnnotatedElementUtils时,可以根据要不要属性覆盖来选择,如果需要处理属性覆盖,就使用AnnotatedElementUtils,如果不需要,就使用AnnotationUtils吧!

5. 总结

本文介绍了spring处理注解的操作,主要介绍了SimpleAnnotationMetadataReadingVisitor与StandardAnnotationMetadata的区别与使用方法。由于这两个类使用起来步骤比较多,文中又介绍了spring提供的两个工具类:AnnotationUtils与AnnotatedElementUtils,如果需要处理属性覆盖,需要使用AnnotatedElementUtils,如果不需要,就使用AnnotationUtils。

本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭