Spring中獲取Bean方法上的自定義注解問(wèn)題解析
背景描述
項(xiàng)目中需要掃描出來(lái)所有 標(biāo)注了自定義注解A的Service里面標(biāo)注了自定義注解B的方法 來(lái)做后續(xù)處理。
基本的思路就是通過(guò)Spring提供的ApplicationContext#getBeansWithAnnotation+反射 來(lái)實(shí)現(xiàn)。
但是,隨著在Service里面引入了聲明式事務(wù)(@Transactional),上述的方法也就隨之失效。
場(chǎng)景復(fù)現(xiàn)
這里通過(guò)構(gòu)造一個(gè)case來(lái)說(shuō)明問(wèn)題
Service上的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MyService {
}方法上的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnno {
String value() default "";
}Service代碼
public interface UserService {
void print();
}
@MyService
@Component("annoUserService")
public class UserServiceImpl implements UserService {
@Override
@MyAnno("xujianadgdgagg")
public void print() {
System.out.println("寫(xiě)入數(shù)據(jù)庫(kù)");
}
}自定義注解掃描代碼
@Component
public class FindAnnotationService {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
// 獲取帶有自定義注解的bean
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
for (Object bean : beanMap.values()) {
Class<?> clazz = bean.getClass();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
// 尋找?guī)в凶远x注解的方法
if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
// 如果方法上有自定義注解,則獲取這個(gè)注解
MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
System.out.println(annotation.value());
}
}
}
}測(cè)試類(lèi)
@SpringBootTest
public class FindAnnotationServiceTests {
@Autowired
private UserService annoUserService;
@Test
public void testPrint() {
annoUserService.print();
}
}當(dāng)對(duì)UserServiceImpl#print()方法加上@Transactional注解時(shí),上面獲取bean的地方,拿到的已經(jīng)不是UserServiceImpl對(duì)象了,而是一個(gè)CGLIB代理類(lèi),如下所示:

我們都知道Spring確實(shí)會(huì)為聲明式事物生成代理類(lèi)。
對(duì)這個(gè)代理類(lèi)通過(guò)反射并沒(méi)有獲取到帶有自定義注解的方法。
問(wèn)題追蹤
最直接的原因推測(cè)是生成的代理類(lèi)并不包含原始類(lèi)中用戶(hù)自定義的注解。
CGLIB動(dòng)態(tài)代理以及生成的代理類(lèi)可以參考《深入理解JVM字節(jié)碼》。
為了驗(yàn)證猜想,我們自己手動(dòng)為UserServiceImpl生成一個(gè)CGLIB代理類(lèi),同時(shí)去掉@Transactional注解。
這里通過(guò)BeanPostProcessor創(chuàng)建代理類(lèi):
@Component
public class MyBeanPostProcess implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof UserService) {
// CGLIB動(dòng)態(tài)代理
MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
myMethodInterceptor.setTarget(bean);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(myMethodInterceptor);
return enhancer.create();
} else {
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
}
public class MyMethodInterceptor implements MethodInterceptor {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib增強(qiáng)目標(biāo)方法");
return method.invoke(target,objects);
}
}結(jié)果跟剛才一樣,由于生成了代理類(lèi)而獲取不到自定義注解。
解決方案
既然CGLIB代理類(lèi)是罪魁禍?zhǔn)?,那就得從它下手?/p>
由于CGLIB生成的代理類(lèi)繼承了原始類(lèi),那在拿到這個(gè)代理類(lèi)的時(shí)候,去找到它的父類(lèi)(原始類(lèi)),不就可以拿到自定義注解了嗎?
對(duì)代碼作如下改動(dòng):
@Component
public class FindAnnotationService {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
// 獲取帶有自定義注解的bean
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
for (Object bean : beanMap.values()) {
Class<?> clazz = bean.getClass();
// 獲取父類(lèi)(代理類(lèi)的原始類(lèi))
clazz = clazz.getSuperclass();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
// 尋找?guī)в凶远x注解的方法
if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
System.out.println(annotation.value());
}
}
}
}
這樣果然拿到了自定義注解。
對(duì)于這種情況,Spring早已預(yù)判到了,并提供了一個(gè)工具方法AnnotationUtils.findAnnotation用來(lái)獲取bean方法上的注解,不管這個(gè)bean是否被代理。
通過(guò)這個(gè)工具方法優(yōu)化代碼如下:
@Component
public class FindAnnotationService {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
for (Object bean : beanMap.values()) {
Class<?> clazz = bean.getClass();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
System.out.println(annotation.value());
}
}
}
}擴(kuò)展思考
既然CGLIB動(dòng)態(tài)代理有這種問(wèn)題,那JDK動(dòng)態(tài)代理呢?
手動(dòng)為UserServiceImpl生成JDK動(dòng)態(tài)代理:
@Component
public class MyBeanPostProcess implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof UserService) {
// JDK動(dòng)態(tài)代理
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.setTarget(bean);
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), bean.getClass().getInterfaces(), myInvocationHandler);
} else {
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
}
public class MyInvocationHandler implements InvocationHandler {
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增強(qiáng)目標(biāo)方法");
return method.invoke(target,args);
}
public void setTarget(Object target) {
this.target = target;
}
}在不使用AnnotationUtils.findAnnotation的時(shí)候果然還是獲取不到自定義注解。
但是加上AnnotationUtils.findAnnotation以后發(fā)現(xiàn)還是獲取不到!?。?/p>
為了探究原因,對(duì)AnnotationUtils.findAnnotation源碼作簡(jiǎn)要分析以后發(fā)現(xiàn):
AnnotationsScanner#processMethodHierarchy(C context, int[] aggregateIndex, Class<?> sourceClass, AnnotationsProcessor<C, R> processor, Method rootMethod, boolean includeInterfaces)
// 如果當(dāng)前代理類(lèi)實(shí)現(xiàn)了接口(JDK動(dòng)態(tài)代理方式)
if (includeInterfaces) {
Class[] var14 = sourceClass.getInterfaces();
var9 = var14.length;
for(var10 = 0; var10 < var9; ++var10) {
Class<?> interfaceType = var14[var10];
// 對(duì)實(shí)現(xiàn)的接口遞歸尋找注解
R interfacesResult = processMethodHierarchy(context, aggregateIndex, interfaceType, processor, rootMethod, true);
if (interfacesResult != null) {
return interfacesResult;
}
}
}
// 如果當(dāng)前代理類(lèi)有父類(lèi)(CGLIB動(dòng)態(tài)代理方式)
Class<?> superclass = sourceClass.getSuperclass();
if (superclass != Object.class && superclass != null) {
// 對(duì)父類(lèi)遞歸尋找注解
R superclassResult = processMethodHierarchy(context, aggregateIndex, superclass, processor, rootMethod, includeInterfaces);
if (superclassResult != null) {
return superclassResult;
}
}我們知道CGLIB代理是基于繼承原始類(lèi)來(lái)實(shí)現(xiàn)的,而JDK代理是基于實(shí)現(xiàn)接口來(lái)實(shí)現(xiàn)的。
從上面的源碼可以大致判斷出:對(duì)于CGLIB代理通過(guò)遞歸搜尋父類(lèi)來(lái)找注解;對(duì)于JDK代理通過(guò)遞歸搜尋實(shí)現(xiàn)的接口來(lái)找注解。
那么在使用JDK生成代理的時(shí)候,把自定義注解放在接口UserService的方法上,而不是實(shí)現(xiàn)類(lèi)UserServiceImpl上:
public interface UserService {
@MyAnno("xujianadgdgagg")
void print();
}這樣就可以通過(guò)AnnotationUtils.findAnnotation成功獲取自定義注解了~
其實(shí)現(xiàn)在Spring大部分都是通過(guò)CGLIB生成的代理,所以無(wú)需將自定義注解放在接口上,畢竟放在實(shí)現(xiàn)類(lèi)上才是常規(guī)操作。
到此這篇關(guān)于Spring中如何獲取Bean方法上的自定義注解的文章就介紹到這了,更多相關(guān)Spring Bean注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JVM內(nèi)存區(qū)域劃分相關(guān)原理詳解
這篇文章主要介紹了JVM內(nèi)存區(qū)域劃分相關(guān)原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Java通過(guò)反射來(lái)打印類(lèi)的方法實(shí)現(xiàn)
本文主要介紹了Java通過(guò)反射來(lái)打印類(lèi)的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Java?Web項(xiàng)目中如何添加Tomcat的Servlet-api.jar包(基于IDEA)
servlet-api.jar是在編寫(xiě)servlet必須用到的jar包下面這篇文章主要給大家介紹了基于IDEAJava?Web項(xiàng)目中如何添加Tomcat的Servlet-api.jar包的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù)
這篇文章主要介紹了Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Springboot連接數(shù)據(jù)庫(kù)及查詢(xún)數(shù)據(jù)完整流程
今天給大家?guī)?lái)的是關(guān)于Springboot的相關(guān)知識(shí),文章圍繞著Springboot連接數(shù)據(jù)庫(kù)及查詢(xún)數(shù)據(jù)完整流程展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06

