亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring中獲取Bean方法上的自定義注解問題解析

 更新時間:2023年06月14日 15:07:16   作者:墨、魚  
這篇文章主要介紹了Spring中如何獲取Bean方法上的自定義注解,基本的思路就是通過Spring提供的ApplicationContext#getBeansWithAnnotation+反射來實現(xiàn),需要的朋友可以參考下

背景描述

項目中需要掃描出來所有 標(biāo)注了自定義注解A的Service里面標(biāo)注了自定義注解B的方法 來做后續(xù)處理。

基本的思路就是通過Spring提供的ApplicationContext#getBeansWithAnnotation+反射 來實現(xiàn)。

但是,隨著在Service里面引入了聲明式事務(wù)(@Transactional),上述的方法也就隨之失效。

場景復(fù)現(xiàn)

這里通過構(gòu)造一個case來說明問題

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("寫入數(shù)據(jù)庫");
    }
}

自定義注解掃描代碼

@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)) {
            // 如果方法上有自定義注解,則獲取這個注解
                MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
                System.out.println(annotation.value());
            }
        }
    }
}

測試類

@SpringBootTest
public class FindAnnotationServiceTests {
    @Autowired
    private UserService annoUserService;
    @Test
    public void testPrint() {
        annoUserService.print();
    }
}

當(dāng)對UserServiceImpl#print()方法加上@Transactional注解時,上面獲取bean的地方,拿到的已經(jīng)不是UserServiceImpl對象了,而是一個CGLIB代理類,如下所示:

在這里插入圖片描述

我們都知道Spring確實會為聲明式事物生成代理類。

對這個代理類通過反射并沒有獲取到帶有自定義注解的方法。

問題追蹤

最直接的原因推測是生成的代理類并不包含原始類中用戶自定義的注解。

CGLIB動態(tài)代理以及生成的代理類可以參考《深入理解JVM字節(jié)碼》。

為了驗證猜想,我們自己手動為UserServiceImpl生成一個CGLIB代理類,同時去掉@Transactional注解。
這里通過BeanPostProcessor創(chuàng)建代理類:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // CGLIB動態(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é)果跟剛才一樣,由于生成了代理類而獲取不到自定義注解。

解決方案

既然CGLIB代理類是罪魁禍?zhǔn)祝蔷偷脧乃率帧?/p>

由于CGLIB生成的代理類繼承了原始類,那在拿到這個代理類的時候,去找到它的父類(原始類),不就可以拿到自定義注解了嗎?

對代碼作如下改動:

@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();
            // 獲取父類(代理類的原始類)
            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());
            }
        }
    }
}

在這里插入圖片描述

這樣果然拿到了自定義注解。

對于這種情況,Spring早已預(yù)判到了,并提供了一個工具方法AnnotationUtils.findAnnotation用來獲取bean方法上的注解,不管這個bean是否被代理。

通過這個工具方法優(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動態(tài)代理有這種問題,那JDK動態(tài)代理呢?

手動為UserServiceImpl生成JDK動態(tài)代理:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // JDK動態(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的時候果然還是獲取不到自定義注解。

但是加上AnnotationUtils.findAnnotation以后發(fā)現(xiàn)還是獲取不到!??!

為了探究原因,對AnnotationUtils.findAnnotation源碼作簡要分析以后發(fā)現(xiàn):

AnnotationsScanner#processMethodHierarchy(C context, int[] aggregateIndex, Class<?> sourceClass, AnnotationsProcessor<C, R> processor, Method rootMethod, boolean includeInterfaces)

            // 如果當(dāng)前代理類實現(xiàn)了接口(JDK動態(tài)代理方式)
            if (includeInterfaces) {
                Class[] var14 = sourceClass.getInterfaces();
                var9 = var14.length;
                for(var10 = 0; var10 < var9; ++var10) {
                    Class<?> interfaceType = var14[var10];
                    // 對實現(xiàn)的接口遞歸尋找注解
                    R interfacesResult = processMethodHierarchy(context, aggregateIndex, interfaceType, processor, rootMethod, true);
                    if (interfacesResult != null) {
                        return interfacesResult;
                    }
                }
            }
            // 如果當(dāng)前代理類有父類(CGLIB動態(tài)代理方式)
            Class<?> superclass = sourceClass.getSuperclass();
            if (superclass != Object.class && superclass != null) {
                // 對父類遞歸尋找注解
                R superclassResult = processMethodHierarchy(context, aggregateIndex, superclass, processor, rootMethod, includeInterfaces);
                if (superclassResult != null) {
                    return superclassResult;
                }
            }

我們知道CGLIB代理是基于繼承原始類來實現(xiàn)的,而JDK代理是基于實現(xiàn)接口來實現(xiàn)的。

從上面的源碼可以大致判斷出:對于CGLIB代理通過遞歸搜尋父類來找注解;對于JDK代理通過遞歸搜尋實現(xiàn)的接口來找注解。

那么在使用JDK生成代理的時候,把自定義注解放在接口UserService的方法上,而不是實現(xiàn)類UserServiceImpl上:

public interface UserService {
    @MyAnno("xujianadgdgagg")
    void print();
}

這樣就可以通過AnnotationUtils.findAnnotation成功獲取自定義注解了~

其實現(xiàn)在Spring大部分都是通過CGLIB生成的代理,所以無需將自定義注解放在接口上,畢竟放在實現(xiàn)類上才是常規(guī)操作。

到此這篇關(guān)于Spring中如何獲取Bean方法上的自定義注解的文章就介紹到這了,更多相關(guān)Spring Bean注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 實例講解JAVA 模板方法模式

    實例講解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)原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-10-10
  • 淺談java多線程wait,notify

    淺談java多線程wait,notify

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

    Java通過反射來打印類的方法實現(xiàn)

    本文主要介紹了Java通過反射來打印類的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java?Web項目中如何添加Tomcat的Servlet-api.jar包(基于IDEA)

    Java?Web項目中如何添加Tomcat的Servlet-api.jar包(基于IDEA)

    servlet-api.jar是在編寫servlet必須用到的jar包下面這篇文章主要給大家介紹了基于IDEAJava?Web項目中如何添加Tomcat的Servlet-api.jar包的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-04-04
  • Ubuntu16.04 64位下JDK1.7的安裝教程

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

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

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

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

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

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

    Springboot連接數(shù)據(jù)庫及查詢數(shù)據(jù)完整流程

    今天給大家?guī)淼氖顷P(guān)于Springboot的相關(guān)知識,文章圍繞著Springboot連接數(shù)據(jù)庫及查詢數(shù)據(jù)完整流程展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • 關(guān)于Kafka消費(fèi)者訂閱方式

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

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

最新評論