Java获取类方法上的注解

 Java 获取类上的注解有下面 3 个方法:

  • Class.getAnnotations() 获取所有的注解,包括自己声明的以及继承的
  • Class.getAnnotation(Class< A > annotationClass) 获取指定的注解,该注解可以是自己声明的,也可以是继承的
  • Class.getDeclaredAnnotations() 获取自己声明的注解
  1. 注解只有标注了 @Inherited 才能被子类继承
  2. 当某个类没有标注任何注解时,getAnnotations()和 getDeclaredAnnotations() 返回空数组
  3. 当某个注解查询不到时,getAnnotation(Class< A > annotationType) 方法返回 null
  4. 子类重写的方法,注解无法被继承,针对方法而言,getAnnotations()与 getDeclaredAnnotations() 返回的结果似乎永远都是一样的。 
如果类被代理,如何获取到类的注解呢?

正常情况下,我们的 class 是 com.cxytiandi.eureka_client.controller.ArticleController这种形式,如果用了 AOP 后,那么就会变成 com.cxytiandi.eureka_client.controller.ArticleController$$EnhancerBySpringCGLIB$$3323dd1e这样了。

解决方案一

这种情况下拿到的 Method 也是被代理了的,所以 Method 上的注解自然获取不到,既然知道原因了,最简单快速的解决方法就是将多余的内容截取掉,然后重新得到一个没有被代理的 Class 对象,通过这个 Class 对象来获取 Method,这样就可以获取到 Method 上的注解。

Class
<?>
 clz 
=
 bean
.
getClass
();
String
 fullName 
=
 clz
.
getName
();
if
(
fullName
.
contains
(
"EnhancerBySpringCGLIB"
)
||
 fullName
.
contains
(
"$$"
))
{
    fullName 
=
 fullName
.
substring
(
0
,
 fullName
.
indexOf
(
"$$"
));
try
{
        clz 
=
Class
.
forName
(
fullName
);
}
catch
(
ClassNotFoundException
 e
)
{
throw
new
RuntimeException
(
e
);
}
}
Method
[]
 methods 
=
 clz
.
getMethods
();
for
(
Method
 method 
:
 methods
)
{
if
(
method
.
isAnnotationPresent
(
Encrypt
.
class
))
{
String
 uri 
=
 method
.
getAnnotation
(
Encrypt
.
class
).
value
();
}
}

解决方案二

虽然问题解决了,但是还是觉得不够优雅,有没有更好的方式呢?我们可以用 Spring 里面提供的 AnnotationUtils 来读取注解。

Encrypt
 encrypt 
=
AnnotationUtils
.
findAnnotation
(
method
,
Encrypt
.
class
);
if
(
encrypt 
!=
null
)
{
String
 uri 
=
 encrypt
.
value
();
}

AnnotationUtils.findAnnotation() 原理是什么呢?为什么它可以获取到被代理后方法上的注解呢?

要想知道原理,那就只能看源码啦,源码多,不贴出来了,贴一点点关键的就行了

首先会构建一个 AnnotationCacheKey,从本地缓存中获取,如果有的话直接返回,也就意味着只要读取过就会被缓存起来:

AnnotationCacheKey
 cacheKey 
=
new
AnnotationCacheKey
(
method
,
 annotationType
);
A result 
=
(
A
)
 findAnnotationCache
.
get
(
cacheKey
);

然后就是判断是否桥接方法,如果不是就直接返回,是的话则获取桥接方法的注解,如果还获取不到就通过接口来获取。

Method
 resolvedMethod 
=
BridgeMethodResolver
.
findBridgedMethod
(
method
);
result 
=
 findAnnotation
((
AnnotatedElement
)
 resolvedMethod
,
 annotationType
);
if
(
result 
==
null
)
{
    result 
=
 searchOnInterfaces
(
method
,
 annotationType
,
 method
.
getDeclaringClass
().
getInterfaces
());}

后面就不继续下去了,最关键的代码其实是这句:

  1. clazz = clazz.getSuperclass();

因为 CGLIB 代理会为目标类动态生成一个子类,所以我们要获取最原始的类,直接使用 getSuperclass 就可以了,跟第一种方案是一致的,只是第一种看起来有点那啥哈.....

推荐大家用 AnnotationUtils 去获取,这里面封装了很多的逻辑,考虑了很多场景下的问题,切莫重复造轮子。