註解的使用踩過坑

參考:https://mp.weixin.qq.com/s/_41ebOwXhP3QGwiwYk1QLQgit

問題背景

好久好久前,在我仍是青銅的時候(如今依舊是青銅段位)去面試,面試官問我怎麼獲取類,方法上的註解。github

當時的我也算用過註解,順口就回答了,用 isAnnotationPresent判斷是否加了註解, getAnnotation獲取註解對象,而後獲取註解中的值。面試

大體的代碼是這樣子的:api

Class<?> clz = bean.getClass();Method[] methods = clz.getMethods();for (Method method : methods) {    if (method.isAnnotationPresent(Encrypt.class)) {         String uri = method.getAnnotation(Encrypt.class).value();    }}複製代碼

正在我沾沾自喜的時候,面試官又乘勝追擊了,那麼在讀取註解的時候,有沒有什麼狀況會致使剛剛你說的方式是不能成功判斷和讀取的呢?緩存

這我一下蒙圈了,還會有讀取不到的狀況麼?以前沒遇到過啊,因而我斬釘截鐵的回答面試官,不可能讀取不到的,面試官笑了笑............bash

在個人加密框架monkey-api-encrypt(https://github.com/yinjihuan/monkey-api-encrypt)中,支持了註解標識加解密的功能,實際上是經過讀取註解,轉換成uri的操做。框架

一開始也是用的上面的方式進行註解的讀取操做,當咱們程序中的Controller被AOP切入後,註解讀取不到了,這就是今天要分享的問題。加密

正常狀況下,咱們的class是 com.cxytiandi.eureka_client.controller.ArticleController這種形式,若是用了AOP後,那麼就會變成 com.cxytiandi.eureka_client.controller.ArticleController$$EnhancerBySpringCGLIB$$3323dd1e這樣了。spa

解決方案一

這種狀況下拿到的Method也是被代理了的,因此Method上的註解天然獲取不到,既然知道緣由了,最簡單快速的解決方法就是將多餘的內容截取掉,而後從新獲得一個沒有被代理的Class對象,經過這個Class對象來獲取Method,這樣就能夠獲取到Method上的註解。3d

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去獲取,這裏面封裝了不少的邏輯,考慮了不少場景下的問題,切莫重複造輪子。

相關文章
相關標籤/搜索