Spring中如何获取Bean方法上的自定义注解

文章讨论了在Spring项目中,由于使用CGLIB代理和声明式事务(@Transactional)导致无法通过反射获取到自定义注解的问题。通过分析问题根源,即CGLIB代理类不包含原始类的自定义注解,提出了解决方案,即通过获取代理类的父类(原始类)来找到注解。此外,还提到了Spring的AnnotationUtils工具类可以帮助处理这种情况。最后,文章对比了CGLIB和JDK动态代理在处理注解时的区别,并给出了JDK代理的情况处理。
摘要由CSDN通过智能技术生成

背景描述

项目中需要扫描出来所有 标注了自定义注解A的Service里面标注了自定义注解B的方法 来做后续处理。

基本的思路就是通过Spring提供的ApplicationContext#getBeansWithAnnotation+反射 来实现。

但是,随着在Service里面引入了声明式事务(@Transactional),上述的方法也就随之失效。

场景复现

这里通过构造一个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("写入数据库");
    }
}

自定义注解扫描代码

@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) {
            // 寻找带有自定义注解的方法
            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();
    }
}

当对UserServiceImpl#print()方法加上@Transactional注解时,上面获取bean的地方,拿到的已经不是UserServiceImpl对象了,而是一个CGLIB代理类,如下所示:
在这里插入图片描述

我们都知道Spring确实会为声明式事物生成代理类。

对这个代理类通过反射并没有获取到带有自定义注解的方法。

问题追踪

最直接的原因推测是生成的代理类并不包含原始类中用户自定义的注解。

CGLIB动态代理以及生成的代理类可以参考《深入理解JVM字节码》。

为了验证猜想,我们自己手动为UserServiceImpl生成一个CGLIB代理类,同时去掉@Transactional注解。
这里通过BeanPostProcessor创建代理类:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // CGLIB动态代理
            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增强目标方法");
        return method.invoke(target,objects);
    }
}

结果跟刚才一样,由于生成了代理类而获取不到自定义注解。

解决方案

既然CGLIB代理类是罪魁祸首,那就得从它下手。

由于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) {
            // 寻找带有自定义注解的方法
            if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
                MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
                System.out.println(annotation.value());
            }
        }
    }
}

在这里插入图片描述
这样果然拿到了自定义注解。

对于这种情况,Spring早已预判到了,并提供了一个工具方法AnnotationUtils.findAnnotation用来获取bean方法上的注解,不管这个bean是否被代理。

通过这个工具方法优化代码如下:

@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());
            }
        }
    }
}

扩展思考

既然CGLIB动态代理有这种问题,那JDK动态代理呢?

手动为UserServiceImpl生成JDK动态代理:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // JDK动态代理
            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("增强目标方法");
        return method.invoke(target,args);
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}

在不使用AnnotationUtils.findAnnotation的时候果然还是获取不到自定义注解。

但是加上AnnotationUtils.findAnnotation以后发现还是获取不到!!!

为了探究原因,对AnnotationUtils.findAnnotation源码作简要分析以后发现:

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

            // 如果当前代理类实现了接口(JDK动态代理方式)
            if (includeInterfaces) {
                Class[] var14 = sourceClass.getInterfaces();
                var9 = var14.length;

                for(var10 = 0; var10 < var9; ++var10) {
                    Class<?> interfaceType = var14[var10];
                    // 对实现的接口递归寻找注解
                    R interfacesResult = processMethodHierarchy(context, aggregateIndex, interfaceType, processor, rootMethod, true);
                    if (interfacesResult != null) {
                        return interfacesResult;
                    }
                }
            }

            // 如果当前代理类有父类(CGLIB动态代理方式)
            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代理是基于继承原始类来实现的,而JDK代理是基于实现接口来实现的。

从上面的源码可以大致判断出:对于CGLIB代理通过递归搜寻父类来找注解;对于JDK代理通过递归搜寻实现的接口来找注解。

那么在使用JDK生成代理的时候,把自定义注解放在接口UserService的方法上,而不是实现类UserServiceImpl上:

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

这样就可以通过AnnotationUtils.findAnnotation成功获取自定义注解了~

其实现在Spring大部分都是通过CGLIB生成的代理,所以无需将自定义注解放在接口上,毕竟放在实现类上才是常规操作。

墨、鱼
关注 关注
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java spring自定义注解_扫描自定义注解并在spring容器注入自定义bean
weixin_29220405的博客
02-24 878
import org.apache.commons.lang.StringUtils;import org.springframework.beans.MutablePropertyValues;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory...
spring初始化后获取自定义注解bean
qq_36874177的博客
04-01 1863
目的是通过注解将特定类的信息(如接口编号)与类关联,之后可通过接口编号获取对应bean来执行对应逻辑。 1.新建注解类: @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Service public @interface ServiceCode {...
spring自定义注解(annotation)与AOP获取注解
09-24
spring自定义注解(annotation)与AOP获取注解.通过实例演示自定义注解
Spring 简单存取 Bean 的相关注解
qq_71360748的博客
07-24 1330
介绍 Spring 简单存取 bean 的几种注解,让存取更简单!
Spring Boot获取Bean的三种方式
最新发布
2301_76419561的博客
08-07 2963
这些方式各有优势,选择哪一种取决于具体的应用场景和需求。通过BeanFactory的方式适合轻量级应用和移动设备,而BeanFactoryAware和ApplicationContext则更适合需要完整Spring功能的场合。
Spring获取注入Bean方法上的注解
【欢迎关注公众号:冬瓜白】
03-19 981
由于 Bean 可能是代理,使用原始的 Java 反射可能会无法获取注解,所以获取 Bean 方法上的注解可以使用 Spring 提供的工具类:
Spring存取Bean的相关注解
yss233333的博客
03-12 478
Spring存取Bean
Spring AOP 和 拦截器 获取类上与方法上的注解
TechnicianWang的博客
06-17 8011
在做一个跨过目标注解的鉴权功能时,想到了AOP与拦截器两种方式,其 @HasPermission 是我自定义的注解,以下分别为AOP与拦截器获取访问目标类与方法上的注解方法。由于我的系统在拦截器上配置了拦截过则,所以我选的是拦截器的方式,读者可根据自己的需求来。先通过ProceedingJoinPoint对象的 joinPoint.getSignature()方法获取到 Signature 的对象并强制类型转换为一个MethodSignature对象,通过 signature.getClass()方法
Spring启动后获取所有拥有特定注解Bean实例代码
08-28
Spring框架获取所有拥有特定注解Bean实例代码是非常重要的一个功能,特别是在系统参数初始化、获取系统所有接口服务清单等一系列需要在Spring启动后初始化的功能。本文主要介绍了Spring启动后获取所有...
丛林探险之Spring自定义注解加载Bean
12-20
Spring框架自定义注解加载Bean是一种高级特性,允许开发者根据自定义注解来动态地注册和管理Bean。这种机制提供了极大的灵活性,能够帮助我们构建更加模块化和可扩展的应用程序。以下是对这个过程的详细解释:...
Spring在代码获取bean的几种方式详解
08-25
主要介绍了Spring在代码获取bean的几种方式详解,文通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
spring aop获取目标对象的方法对象(包括方法上的注解
sswmjoy的专栏
12-01 2278
最近使用spring aop来做日志,其想获得方法上的注解,找来这篇文章,重点是这篇文章不能只看前半部分,前半部分是问题描述,我犯了错误直接读完前半部分就码代码了,结果发现有问题,原文确实也没提示,这里先提醒大家下spring aop获取目标对象的方法对象(包括方法上的注解
自定义注解
天之大的博客
06-19 215
自定义注解使用
Spring】通过Spring收集自定义注解标识的方法
chenghan_yang的博客
03-31 663
需求:用key找到对应的方法实现。使用注解的形式增量开发。任意时刻都能通过key来进行依赖查找@TestAssert.notNull(myBeanFactory.getMethod("key1"), "key1对应的方法不能为空");声明自己的类注解,并要求被 Spring 收集声明自己的方法注解,确保可以通过反射获取Spring 的能力,容器启动收集bean完成后,把bean列表交给自己,用于自己的收集策略。
Spring存取Bean的注解
weixin_62988630的博客
11-14 1218
就引入了一个问题,在实际开发过程,Tire类的构造函数不能确定,可能有颜色要求,长度要求等,所以一旦减少或增加,就需要改变之前的所有类的代码。IoC:在构造每个类时,都不创建所依赖的对象,改用传递的方式,则只在Tire类·构造对应的函数,达到了解耦的效果,所以如果要减少或者增加属性时,只需要修改Tire类即可。要想实现IoC,就得在相应类注入所需要下一个类的对象,而不在本类new该对象。依赖对象是在构造方法执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化。
Spring获取Bean所加注解的详情
Aal_Lzz_Well的博客
05-10 3988
由于工作的需要,需要获取特定Bean上所加注解的详情,具体情况上图:1.首先自定义一个注解:2.在某个Bean上使用该注解:3.在测试类获取带有MyAnnotation注解bean,并获取注解的value的值:另外:使用SpringBoot:...
Spring获取指定包下,添加了指定注解方法
qq_40104261的博客
03-24 1215
public void getMethod() throws IOException, ClassNotFoundException { ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(res
Spring 存取 Bean 的相关注解
weixin_57190845的博客
11-15 523
SpringBean的存取
spring自定义注解及使用
ZHANGLIZENG的博客
05-31 8902
一文搞懂spring自定义注解及使用场景
写文章

热门文章

  • GitLab的Webhook配置和开发 17447
  • 聊聊数据同步方案 9741
  • 一个简单的Java抽奖程序 6818
  • SkyWalking Agent数据采集和上报原理浅析 6662
  • 图解MESI(缓存一致性协议) 5806

分类专栏

  • Java 34篇
  • 基础 22篇
  • Apollo 3篇
  • Jackson 3篇
  • 数据同步 1篇
  • Canal 1篇
  • MySQL 4篇
  • 微服务 3篇
  • 分布式 3篇
  • 设计模式 2篇
  • API介绍 1篇
  • JVM 1篇
  • Service Mesh 1篇
  • Service
  • RocketMQ 4篇
  • 面试 2篇

最新评论

  • Guava限流器原理浅析

    倜傥村的少年: 写的很棒哦,和redisson的令牌桶限流器不太一样,guava的限流器针对后面的请求速率会固定,倒有点像漏桶了

  • 图解MESI(缓存一致性协议)

    xwqlikepsl: 这尼玛不是小林coding里面的内容吗

  • 【自定义注解使用】增加service层方法访问日志

    qq_57956047: 什么,刚想收藏,看见了这条评论,收集失败表情包

  • SpringBoot中Jackson序列化处理自定义注解

    huangyi211: 大哥,这个为什么会只执行一次,怎么解决

  • 图解MESI(缓存一致性协议)

    我是你BaBa_: 两个字,简明

最新文章

  • Guava限流器原理浅析
  • 【必看!】动态修改SpringBoot定时任务执行周期
  • Go语言 和 Java语言对比理解系列五:锁
2023年7篇
2022年11篇
2021年22篇
2020年17篇
2019年10篇
2018年18篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家玻璃钢电影动漫雕塑多少钱节日商场美陈策划北京玻璃钢雕塑哪里有南京仿铜玻璃钢雕塑价格南昌仿铜玻璃钢雕塑商场美陈评比表格商场美陈的经验玻璃钢创意彩绘鹿雕塑北京湘西玻璃钢人物雕塑遵义玻璃钢花盆厂家浙江通道商场美陈订购台州欧式玻璃钢雕塑优势铜玻璃钢景观雕塑定制互动玻璃钢雕塑东莞玻璃钢大象雕塑浮雕玻璃钢雕塑制作贵阳个性化玻璃钢雕塑哪家便宜吉安玻璃钢卡通雕塑定制济南商场玻璃钢雕塑摆件玻璃钢人像雕塑公司有哪些玻璃钢仿真葡萄雕塑定制玻璃钢雕塑参考价昆明玻璃钢雕塑厂家直供萍乡景区玻璃钢雕塑哪家便宜云南喷泉雕塑玻璃钢昆明玻璃钢人物雕塑深圳季节性商场美陈市场上海玻璃钢广场雕塑价格商场餐饮区美陈杭州玻璃钢雕塑信息推荐香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化