前言
本文主要聊聊Spring提供的多个关于注解相关的工具类:AnnotationUtils和AnnotatedElementUtils等等
因为很多逻辑都封装在了工具类里面,因此要理解Spring的深层意思,有时候不了解工具类也是比较麻烦的
虽然说都是Spring内部去使用,但是有些工具类是public的(下面会有介绍),所以我们平时若有需要,也是可以使用的
本文要说明的工具类如上图,一共会讲述9个(排名不分先后)。(ParserStrategyUtils忽略,没什么好说的,只有一个方法处理Aware的注入。AnnotationReadingVisitorUtils也忽略,它和ASM字节码相关,后面再讨论)
AnnotationUtils(最重要)
总之,从类名就能看出来。这是Spring提供的获取、处理注解的工具类。
可能有小伙伴就非常好奇了:JDK已经提供给我们获取注解的方法了,Spring为何要多此一举呢?如下:
@MyAnno interface Eat { } class Parent implements Eat { } class Child extends Parent { } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @RequestMapping // 特意注解上放一个注解,方面测试看结果 @Inherited @interface MyAnno { } // 现在我们来获取类上面的注解如下 public static void main(String[] args) { MyAnno anno1 = Eat.class.getAnnotation(MyAnno.class); MyAnno anno2 = Parent.class.getAnnotation(MyAnno.class); MyAnno anno3 = Child.class.getAnnotation(MyAnno.class); System.out.println(anno1); //@com.fsx.maintest.MyAnno() System.out.println(anno2); //null System.out.println(anno3); //null }
此处必须对结果说明一点:我们的注解标注了@Inherited表示该注解可以被继承,但是anno2和anno3还是null。需要注意:@Inherited继承只能发生在类上,而不能发生在接口上(也就是说标注在接口上仍然是不能被继承的)
介绍Class提供的获取注解相关方法:
- <A extends Annotation>A getAnnotation(Class<A>annotationClass):获取该class对象对应类上指定类型的Annotation,如果该类型注解不存在,则返回null
Annotation[] getAnnotations():返回修饰该class对象对应类上存在的所有Annotation
- <A extends Annotation>A getDeclaredAnnotation(Class<A>annotationClass):这是Java 8中新增的,该方法获取直接修饰该class对象对应类的指定类型的Annotation,如果不存在,则返回null(也就说只找自己的,继承过来的注解这个方法就不管了)
- Annotation[] getDeclaredAnnotations():返回修饰该Class对象对应类上存在的所有Annotation(同上,继承的不管)
- <A extends Annotation>A[] getAnnotationByType(Class<A>annotationClass):该方法的功能与前面介绍的getAnnotation()方法基本相似,但由于Java8增加了重复注解功能,因此需要使用该方法获取修饰该类的指定类型的多个Annotation(会考虑继承的注解)
- <A extends Annotation>A[] getDeclaredAnnotationByType(Class<A>annotationClass):相信不解释你也懂
更多Class中的方法使用,请参阅:Java反射获取类和对象信息全解析
既然JDK提供给了我们这么多的注解相关方法,乍一看是够用了呢?为何Spring还自己写个工具类呢?我觉得这也是Spring的强大之处,往往写出来的东西比JDK的还强大。比如试想一下下面两个场景,你就没觉得疑惑?
1.@AliasFor(Spring4.2之后才有的)为何能生效呢?大大的方便了我们注解的使用
2.@RequestMapping注解明明不能继承(即使有@Inherited也不能写在接口上嘛),但为何我们把它写在接口上时,用Controller去实现的时候却像是被继承了一样呢?
注解支持的数据类型:
- 所有基本类型(int,float,boolean,byte,double,char,long,short) 注意:包装类型不支持
- String
- Class
- enum
- Annotation
- 上述类型的数组
注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口(从反编译代码里可以看出,类似于Enum)
*如果你不能解释至少上面两个疑问,那么你就很有必要来看看这个工具类了~~~~*
AnnotationUtils方法介绍
AnnotationUtils内部使用了很多ConcurrentReferenceHashMap(弱、软引用)作为缓存,来各种提高效率
开门见山,先直接上一个最强的方法,也是该工具类的核心:synthesizeAnnotation
static <A extends Annotation> A synthesizeAnnotation(A annotation) { return synthesizeAnnotation(annotation, null); } public static <A extends Annotation> A synthesizeAnnotation( A annotation, @Nullable AnnotatedElement annotatedElement) { return synthesizeAnnotation(annotation, (Object) annotatedElement); } static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) { if (annotation instanceof SynthesizedAnnotation) { return annotation; } Class<? extends Annotation> annotationType = annotation.annotationType(); if (!isSynthesizable(annotationType)) { return annotation; } DefaultAnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation, annotatedElement); InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a // synthesizable annotation before (which needs to declare @AliasFor from the same package) Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class}; return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler); }
作用:来获取一个动态代理注解(相当于调用者传进来的注解会被代理掉),该方法是别名注解@AliasFor的核心原理。
- public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType):
public static void main(String[] args) { MyAnno anno1 = Eat.class.getAnnotation(MyAnno.class); // 注解交给这么一处理 相当于就会被Spring代理了 这就是优势 MyAnno sAnno1 = AnnotationUtils.getAnnotation(anno1, MyAnno.class); System.out.println(sAnno1); //@com.fsx.maintest.MyAnno() // 这样前后类型不一致的话,会把这个注解上面的注解给获取出来 RequestMapping annotation = AnnotationUtils.getAnnotation(anno1, RequestMapping.class); System.out.println(annotation); //@org.springframework.web.bind.annotation.RequestMapping }
- public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType):重载方法。上面annotation.annotationType();其实就是annotatedElement了
- public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType):强大之处在于:它连桥接方法(BridgeMethod)都支持。参考;java中什么是bridge method(桥接方法)
- public static Annotation[] getAnnotations(AnnotatedElement annotatedElement):获取指定类型上所有注解
public static void main(String[] args) { MyAnno anno1 = Eat.class.getAnnotation(MyAnno.class); // 注意这两种写法的区别: // 这个相当于是获取Child.class的它上面的所有注解, 所以只有继承过来的一个@MyAnno Annotation[] sAnno1 = AnnotationUtils.getAnnotations(Child.class); // 而这里传入的为anno1.annotationType,所以相当于获取该注解上面的注解 所以使用的时候需要注意 Annotation[] sAnno2 = AnnotationUtils.getAnnotations(anno1.annotationType()); System.out.println(sAnno1); System.out.println(sAnno2); }
- public static Annotation[] getAnnotations(Method method):支持桥接
- public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType):支持了Java8的重复注解
- getDeclaredRepeatableAnnotations(重载方法略):不解释
- public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType):他的特点就是,会递归去你的父类、接口里把注解找到,找到既返回,返回第一个匹配的注解信息 顺序是:先接口后父类
- public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType):(其它重载方法略)
@MyAnno interface Eat { } class Parent implements Eat { } //本来,接口上的注解我们无论如何都继承不了了,但用了Spring的,你就可以 public static void main(String[] args) { MyAnno annotation = AnnotationUtils.findAnnotation(Child.class, MyAnno.class); System.out.println(annotation); } //备注:哪怕@MyAnno上没有标注@Inherited,也是能找出来的(这是后面讲解@RequestMapping为何能被子类继承的重要原因)
- public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, @Nullable Class<?> clazz):找到第一个(自己有就自己了,否则去父类继续找)有这个注解的。Class~~(有可能是自己,有可能是父类) 备注:接口不找
public static void main(String[] args) { Class<?> annotationDeclaringClass = AnnotationUtils.findAnnotationDeclaringClass(MyAnno.class, Child.class); System.out.println(annotationDeclaringClass); }
- public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends Annotation>> annotationTypes, @Nullable Class<?> clazz):annotationTypes和上面相比,只有类里面有一个这个注解,就return了
- public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz):简单的说就是自己本身Class是否含有指定的这个注解
- public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz):判断该Class上指定的注解是否是继承过来的。
public static void main(String[] args) { System.out.println(AnnotationUtils.isAnnotationInherited(MyAnno.class, Parent.class)); //false // 说明一下:clazz.isAnnotationPresent(annotationType) JDK的。表示注解存在就行,不管你是自己的还是继承来的 System.out.println(AnnotationUtils.isAnnotationInherited(MyAnno.class, Child.class)); //true 很显然,Child的这个注解是继承来的 // Child的MyAnno注解是父类的,但这里还是会返回true System.out.println(Child.class.isAnnotationPresent(MyAnno.class)); //true System.out.println(Child.class.getAnnotation(MyAnno.class)); // @com.fsx.maintest.MyAnno(c... }
- public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType, @Nullable Class<? extends Annotation> metaAnnotationType):简单的说:就是annotationType这个注解类型上面,是否标注有metaAnnotationType这个类型的注解
public static void main(String[] args) { System.out.println(AnnotationUtils.isAnnotationMetaPresent(MyAnno.class, RequestMapping.class)); //true System.out.println(AnnotationUtils.isAnnotationMetaPresent(MyAnno.class, Component.class)); //false }