使用注解的坑

  |   0 评论   |   1,433 浏览

怎么获取类,方法上的注解?

isAnnotationPresent判断是否加了注解,getAnnotation获取注解对象,然后获取注解中的值。

Class clz = bean.getClass(); 
Method[] methods = clz.getMethods(); 
for (Method method : methods) { 
  if (method.isAnnotationPresent(Encrypt.class)) { 
	String uri = method.getAnnotation(Encrypt.class).value(); 
  } 
}

有没有什么情况会导致不能成功判断和读取的呢?
当被AOP切入后,注解是读取不到的,这就是今天要分享的问题。
正常情况下,我们的class是com.gofun.controller.TestController这种形式,如果用了AOP后,那么就会变成com.gofun.controller.TestController$$EnhancerBySpringCGLIB$$4414dd1e这样了。

解决方案一

这种情况下拿到的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());
}

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

clazz = clazz.getSuperclass();

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

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

评论

发表评论

validate