好久好久前,在我仍是青铜的时候(如今依旧是青铜段位)去面试,面试官问我怎么获取类,方法上的注解。git
当时的我也算用过注解,顺口就回答了,用isAnnotationPresent
判断是否加了注解,getAnnotation
获取注解对象,而后获取注解中的值。github
大体的代码是这样子的:面试
Class<?> clz = bean.getClass();
Method[] methods = clz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Encrypt.class)) {
String uri = method.getAnnotation(Encrypt.class).value();
}
}
复制代码
正在我沾沾自喜的时候,面试官又乘胜追击了,那么在读取注解的时候,有没有什么状况会致使刚刚你说的方式是不能成功判断和读取的呢?api
这我一下蒙圈了,还会有读取不到的状况么?以前没遇到过啊,因而我斩钉截铁的回答面试官,不可能读取不到的,面试官笑了笑............缓存
在个人加密框架monkey-api-encrypt(github.com/yinjihuan/m…)中,支持了注解标识加解密的功能,实际上是经过读取注解,转换成uri的操做。一开始也是用的上面的方式进行注解的读取操做,当咱们程序中的Controller被AOP切入后,注解读取不到了,这就是今天要分享的问题。bash
正常状况下,咱们的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来读取注解。spa
Encrypt encrypt = AnnotationUtils.findAnnotation(method, Encrypt.class);
if (encrypt != null) {
String uri = encrypt.value();
}
复制代码
AnnotationUtils.findAnnotation()原理是什么呢?为何它能够获取到被代理后方法上的注解呢?3d
要想知道原理,那就只能看源码啦,源码多,不贴出来了,贴一点点关键的就好了
首先会会构建一个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());
}
复制代码
后面就不继续下去了,最关键的代码实际上是这句:
clazz = clazz.getSuperclass();
复制代码
由于CGLIB代理会为目标类动态生成一个子类,因此咱们要获取最原始的类,直接使用getSuperclass就能够了,跟第一种方案是一致的,只是第一种看起来有点那啥哈.....
推荐你们用AnnotationUtils去获取,这里面封装了不少的逻辑,考虑了不少场景下的问题,切莫重复造轮子。