自定义注解那些事(一)

8 篇文章 0 订阅

自定义注解那些事(一)

自定义注解那些事(二)

一、自定义注解

  • 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
@Documented
public @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元注解,常规方法、AnnotationUtilsAnnotatedElementUtils工具类均无法获取自定义注解。❌若自定义注解包含Inherited元注解,常规方法可获取,若不包含Inherited元注解常规方法无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌
方法注解(实现类)不管自定义注解包不包含Inherited元注解,常规方法、AnnotationUtilsAnnotatedElementUtils工具类均无法获取自定义注解。❌不管自定义注解包不包含Inherited元注解,常规方法均无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌
类注解(接口类)不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌
方法注解(接口类)不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌不管自定义注解包不包含Inherited元注解,常规方法无法获取,通过AnnotationUtilsAnnotatedElementUtils工具类可获取。✔️❌

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<A>)org.springframework.core.annotation.AnnotatedElementUtils#findMergedAnnotation

至于,JDK动态代理方式下,仅当注解加在接口类或者接口方法上,才可通过Spring提供的工具类方法org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.Class<?>, java.lang.Class<A>)org.springframework.core.annotation.AnnotatedElementUtils#findMergedAnnotation获取到注解。

总之,总是使用AnnotationUtilsAnnotatedElementUtils工具类获取注解,推荐将Spring AOP全局代理方式设置为CGLIB(默认),不得已的情况下,将注解加在接口类或接口方法上,保证通过AnnotationUtilsAnnotatedElementUtils工具类总是可以获取到注解。

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<A>, java.util.function.Predicate<? super org.springframework.core.annotation.MergedAnnotation<A>>, org.springframework.core.annotation.MergedAnnotationSelector<A>)

=>

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<C,R>, boolean, boolean)

  • 核心代码

C context - 上下文对象(注解类Class对象)

	@Nullable
	private 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#getMergedAnnotationSearchStrategy.TYPE_HIERARCHY(包含父类及接口的注解)
AnnotatedElementUtils#findMergedAnnotationSearchStrategy.TYPE_HIERARCHY(包含父类及接口的注解)

四、扩展

1.Controller可增加事务注解吗?

Controller可增加事务注解,理论上是生效的,但不推荐这么用。为什么哪?你想啊,@RequestMapping也是注解,万一哪一天小A新建了个通用接口,HelloController也实现了这个接口,同时全局的代理方式为JDK动态代理(或者HelloController被标记为final),应用启动起来后你会惊讶的发现,我们用@RequestMapping指定的请求URL地址不存在报404,一群人全蒙了,竟然还有这种骚操作!!!😅

五、参考

Java元注解与Spring组合注解使用

【小家Spring】Spring贡献的多个注解相关的工具类:AnnotationUtils、AnnotatedElementUtils、AnnotationConfigUtils…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬山境KL攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值