面試官問我註解的使用有沒有踩過坑

問題背景

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

猿天地
相關文章
相關標籤/搜索