欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中獲取Bean方法上的自定義注解問(wèn)題解析

 更新時(shí)間:2023年06月14日 15:07:16   作者:墨、魚(yú)  
這篇文章主要介紹了Spring中如何獲取Bean方法上的自定義注解,基本的思路就是通過(guò)Spring提供的ApplicationContext#getBeansWithAnnotation+反射來(lái)實(shí)現(xià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)文章

  • 實(shí)例講解JAVA 模板方法模式

    實(shí)例講解JAVA 模板方法模式

    這篇文章主要介紹了JAVA 模板方法模式的的相關(guān)資料,文中示例代碼非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • JVM內(nèi)存區(qū)域劃分相關(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多線程wait,notify

    淺談java多線程wait,notify

    這篇文章主要介紹了java多線程wait,notify,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編和大家一起來(lái)學(xué)習(xí)一下吧
    2019-05-05
  • Java通過(guò)反射來(lái)打印類(lèi)的方法實(shí)現(xiàn)

    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)

    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
  • Ubuntu16.04 64位下JDK1.7的安裝教程

    Ubuntu16.04 64位下JDK1.7的安裝教程

    這篇文章主要為大家詳細(xì)介紹了Ubuntu16.04 64位下JDK1.7的安裝教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • Java常用字節(jié)流和字符流實(shí)例匯總

    Java常用字節(jié)流和字符流實(shí)例匯總

    這篇文章主要介紹了Java常用字節(jié)流和字符流實(shí)例匯總,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù)

    Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù)

    這篇文章主要介紹了Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Springboot連接數(shù)據(jù)庫(kù)及查詢(xún)數(shù)據(jù)完整流程

    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
  • 關(guān)于Kafka消費(fèi)者訂閱方式

    關(guān)于Kafka消費(fèi)者訂閱方式

    這篇文章主要介紹了關(guān)于Kafka消費(fèi)者訂閱方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05

最新評(píng)論