自定义注解那些事(一)
自定义注解那些事(一)
自定义注解那些事(二)
文章目录
- 自定义注解那些事(一)
-
- 一、自定义注解
- 二、自定义注解可以被继承吗?
- 三、动态代理会导致自定义注解丢失吗?
-
- 1.获取自定义注解的可靠方式
- 2.AnnotatedElementUtils工具类源码解析
- 3.AnnotationUtils和AnnotatedElementUtils工具类区别
- 四、扩展
-
- 1.Controller可增加事务注解吗?
- 五、参考
一、自定义注解
- Operate
package com.example.demo.enumeration;public enum Operate { INSERT, DELETE, UPDATE, SELECT, OTHER;}
- LogPoint
package com.example.demo.annotation;import com.example.demo.enumeration.Operate;import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface LogPoint { /** * 模块 * * @return 模块 */ String module(); /** * 操作类型 * * @return 操作类型 */ Operate type() default Operate.OTHER;}
二、自定义注解可以被继承吗?
你可能回答可以被继承,使用
Inherited
就行了,这样回答是不准确的!你可以查看Inherited
源码说明,它仅适用于类注解@Target(ElementType.TYPE)
,查询目标类的父类(包含祖级类,但不可接口)直至Object
类上是否存在该注解,若找到则存在,否则不存在。
注解 | 无Inherited元注解 | 有Inherited元注解 |
---|---|---|
父类注解 | ❌ | ✔️ |
父类方法注解-重写父类方法(含抽象类抽象方法实现) | ❌ | ❌ |
父类方法注解-不重写父类方法① | ✔️ | ✔️ |
接口注解 | ❌ | ❌ |
接口方法注解 | ❌ | ❌ |
接口方法注解(接口默认方法default)同①情况 | ✔️ | ✔️ |
注:以上能否获取到父类中的注解,指的是通过常规的方式获取,如aClass.getAnnotation(LogPoint.class)
、method.getAnnotation(LogPoint.class)
。
三、动态代理会导致自定义注解丢失吗?
如果在面试中,面试官问你“自定义注解可以被继承吗?”你只答了第二章的内容,估计你会被pass!
现在的Java程序员99%的都是Spring程序员,而Spring框架三级缓存干了什么事哪?Bean代理,简单的来说,就是如果有需要的话(Bean中包含基于Spring AOP的注解如事务注解
@Transactional
或请求重试注解@Retryable
),返回给你目标对象的代理类,否则返回目标对象本身。而Spring AOP动态代理分为两种JDK动态代理和CGLIB动态代理,你可能之前就了解到JDK动态代理是基于接口的、CGLIB动态代理是基于类继承的。总的来说,只要返回的是代理类,很可能就会导致自定义注解的丢失!!!那我们该如何规避此类问题?
注解 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
类注解(实现类) | 不管自定义注解包不包含Inherited元注解,常规方法、AnnotationUtils 、AnnotatedElementUtils 工具类均无法获取自定义注解。❌ |
若自定义注解包含Inherited元注解,常规方法可获取,若不包含Inherited元注解常规方法无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
方法注解(实现类) | 不管自定义注解包不包含Inherited元注解,常规方法、AnnotationUtils 、AnnotatedElementUtils 工具类均无法获取自定义注解。❌ |
不管自定义注解包不包含Inherited元注解,常规方法均无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
类注解(接口类) | 不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
方法注解(接口类) | 不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtils 、AnnotatedElementUtils 工具类可获取。✔️❌ |
spring-boot应用可通过设计
application.properties
文件spring.aop.proxy-target-class
属性值来改变全局代理方式。
1.获取自定义注解的可靠方式
建议将Spring AOP全局代理方式设置为CGLIB(默认),另外,获取自定义注解的代码使用Spring提供的工具类方法
org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.Class, java.lang.Class)
或org.springframework.core.annotation.AnnotatedElementUtils#findMergedAnnotation
至于,JDK动态代理方式下,仅当注解加在接口类或者接口方法上,才可通过Spring提供的工具类方法
org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.Class, java.lang.Class)
或org.springframework.core.annotation.AnnotatedElementUtils#findMergedAnnotation
获取到注解。总之,总是使用
AnnotationUtils
、AnnotatedElementUtils
工具类获取注解,推荐将Spring AOP全局代理方式设置为CGLIB(默认),不得已的情况下,将注解加在接口类或接口方法上,保证通过AnnotationUtils
、AnnotatedElementUtils
工具类总是可以获取到注解。以
AnnotatedElementUtils
工具类为例
- 类注解
final Class<? extends TestService> aClass = testService.getClass();final LogPoint annotation = AnnotatedElementUtils.findMergedAnnotation(aClass, LogPoint.class);
- 方法注解
final Class<? extends TestService> aClass = testService.getClass();final Method method = aClass.getMethod("test");final LogPoint annotation = AnnotatedElementUtils.findMergedAnnotation(method, LogPoint.class);
2.AnnotatedElementUtils工具类源码解析
以
AnnotatedElementUtils
工具类为例
- 调用链
入口方法
org.springframework.core.annotation.AnnotatedElementUtils#findMergedAnnotation
=> 重要:
org.springframework.core.annotation.MergedAnnotations.SearchStrategy#TYPE_HIERARCHY
指定注解搜索范围策略
org.springframework.core.annotation.AnnotatedElementUtils#getAnnotations
=>
org.springframework.core.annotation.TypeMappedAnnotations#get(java.lang.Class, java.util.function.Predicate<? super org.springframework.core.annotation.MergedAnnotation>, org.springframework.core.annotation.MergedAnnotationSelector)
=>
org.springframework.core.annotation.TypeMappedAnnotations#scan
=>
org.springframework.core.annotation.AnnotationsScanner#scan
=>
org.springframework.core.annotation.AnnotationsScanner#process
=> 递归查询父类及接口中的注解,并通过
AnnotationsProcessor
注解处理器提取指定的注解信息
org.springframework.core.annotation.AnnotationsScanner#processClassHierarchy(C, java.lang.Class, org.springframework.core.annotation.AnnotationsProcessor, boolean, boolean)
- 核心代码
C context - 上下文对象(注解类Class对象)
@Nullableprivate static <C, R> R processClassHierarchy(C context, int[] aggregateIndex, Class<?> source,AnnotationsProcessor<C, R> processor, boolean includeInterfaces, boolean includeEnclosing) {try {R result = processor.doWithAggregate(context, aggregateIndex[0]);if (result != null) {return result;}if (hasPlainJavaAnnotationsOnly(source)) {return null;}Annotation[] annotations = getDeclaredAnnotations(source, false);result = processor.doWithAnnotations(context, aggregateIndex[0], source, annotations);if (result != null) {return result;}aggregateIndex[0]++;if (includeInterfaces) {for (Class<?> interfaceType : source.getInterfaces()) {R interfacesResult = processClassHierarchy(context, aggregateIndex,interfaceType, processor, true, includeEnclosing);if (interfacesResult != null) {return interfacesResult;}}}Class<?> superclass = source.getSuperclass();if (superclass != Object.class && superclass != null) {R superclassResult = processClassHierarchy(context, aggregateIndex,superclass, processor, includeInterfaces, includeEnclosing);if (superclassResult != null) {return superclassResult;}}if (includeEnclosing) {// Since merely attempting to load the enclosing class may result in// automatic loading of sibling nested classes that in turn results// in an exception such as NoClassDefFoundError, we wrap the following// in its own dedicated try-catch block in order not to preemptively// halt the annotation scanning process.try {Class<?> enclosingClass = source.getEnclosingClass();if (enclosingClass != null) {R enclosingResult = processClassHierarchy(context, aggregateIndex,enclosingClass, processor, includeInterfaces, true);if (enclosingResult != null) {return enclosingResult;}}}catch (Throwable ex) {AnnotationUtils.handleIntrospectionFailure(source, ex);}}}catch (Throwable ex) {AnnotationUtils.handleIntrospectionFailure(source, ex);}return null;}
3.AnnotationUtils和AnnotatedElementUtils工具类区别
两者均是Spring提供的注解工具类,一般来讲,使用AnnotationUtils工具类即可,若需要考虑注解属性值覆盖合并的情况,请使用AnnotatedElementUtils工具类。
常用方法 | 搜索范围 | 属性覆盖合并① |
---|---|---|
AnnotationUtils#getAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class) | SearchStrategy.INHERITED_ANNOTATIONS(基本等同于JDK常规获取方式,但功能更强) | 否 |
AnnotationUtils#findAnnotation(java.lang.Class, java.lang.Class) | SearchStrategy.INHERITED_ANNOTATIONS(基本等同于JDK常规获取方式,但功能更强) | 否 |
AnnotatedElementUtils#getMergedAnnotation | SearchStrategy.TYPE_HIERARCHY(包含父类及接口的注解) | 是 |
AnnotatedElementUtils#findMergedAnnotation | SearchStrategy.TYPE_HIERARCHY(包含父类及接口的注解) | 是 |
四、扩展
1.Controller可增加事务注解吗?
Controller可增加事务注解,理论上是生效的,但不推荐这么用。为什么哪?你想啊,
@RequestMapping
也是注解,万一哪一天小A新建了个通用接口,HelloController
也实现了这个接口,同时全局的代理方式为JDK动态代理(或者HelloController
被标记为final),应用启动起来后你会惊讶的发现,我们用@RequestMapping
指定的请求URL地址不存在报404,一群人全蒙了,竟然还有这种骚操作!!!😅
五、参考
Java元注解与Spring组合注解使用
【小家Spring】Spring贡献的多个注解相关的工具类:AnnotationUtils、AnnotatedElementUtils、AnnotationConfigUtils…