好久好久前,在我仍是青銅的時候(如今依舊是青銅段位)去面試,面試官問我怎麼獲取類,方法上的註解。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
這樣了。markdown
這種狀況下拿到的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來讀取註解。oop
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去獲取,這裏面封裝了不少的邏輯,考慮了不少場景下的問題,切莫重複造輪子。