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

news2024/11/24 3:36:42

文章目录

  • 背景描述
  • 场景复现
  • 问题追踪
  • 解决方案
  • 扩展思考

背景描述

项目中需要扫描出来所有 标注了自定义注解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生成的代理,所以无需将自定义注解放在接口上,毕竟放在实现类上才是常规操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring】核心与设计思想

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 谈起Java 圈子里的框架&#xff0c;最年长最耀眼的莫过于 Spring 框架啦&#xff0c;如今已成为最流行、最广泛使用的Java开发框架之一。不知道大家有没有在使用 Spring 框架的时候思考过这…

11111111111

def cosine_similarity(vector_a, vector_b): “”" 计算两个向量之间的余弦相似度 :param vector_a: 向量 a :param vector_b: 向量 b :return: distance “”" vector_a np.mat(vector_a) vector_b np.mat(vector_b) num float(vector_a * vector_b.T) denom n…

华为OD机试真题 Java 实现【预定酒店】【2022Q4 100分】

一、题目描述 放暑假了,小明决定到某旅游景点游玩,他在网上搜索到了各种价位的酒店(长度为n的 数组A),他的心理价位是x元,请帮他筛选出k个最接近x元的酒店 (n>=k>0) ,并由低到高打印酒店的价格。 二、输入描述 第一行: n,k,x 第二行: A[o] A[1] A[2]…A[n-1] 三…

djiango orm简单实现增删改查

目录 一、配置数据库1.1 在settings.py文件中找到DATABASES &#xff0c;配置数据库连接&#xff0c;这里用的是mysql 二、切换操作数据库的模块三、 创建一个app并注册3.1创建一个app3.2 注册app 三、在app1定义模型类四、迁移数据库&#xff0c;使用以下命令&#xff0c;生成…

Android修改aar并重新打包

目录 一.修改 aar 需要用到的工具&#xff08;就一个工具&#xff0c;使用方式非常简单&#xff0c;别担心&#xff09; 二.修改 aar 代码层业务逻辑 三.修改 aar layout 布局文件 四.附上recyclerview aar修改工程源码 一.修改 aar 需要用到的工具&#xff08;就一个工具&…

MKS SERVO4257D 闭环步进电机_系列8 CAN通讯示例

第1部分 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口和RS485/CAN串行接口&#xff0c;支持MODBUS-RTU通讯协议&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&a…

AD19操作注意事项及信息

直接在PCB编辑界面添加差分对&#xff08;差分布线&#xff09; 1.PCB边界界面Panels菜单调出PCB界面 2.选择框中信息&#xff0c;点击添加差分对即可&#xff0c;然后利用交互式差分对布线命令进行布线操作。&#xff08;前提设置好差分布线规则&#xff09; 过孔&#xff1a…

7年经验之谈 —— 如何进行渗透测试以提高软件安全性?

对于各种规模的企业和组织来说&#xff0c;软件安全是一个至关重要的问题。随着网络攻击越来越复杂&#xff0c;软件中的漏洞越来越多&#xff0c;确保你的软件安全比以往任何时候都更重要。提高软件安全性的一个有效方法是渗透测试&#xff08;penetration testing&#xff09…

6月9号软件资讯更新合集....

Vivaldi 6.1 发布&#xff0c;可绕过微软限制使用 Bing Chat 最新版本的 Vivaldi 可在桌面端伪装成 Edge&#xff0c;使其用户受益&#xff0c;并为工作空间和标签增加了更多的功能。 支持微软 Bing Chat Vivaldi 是建立在 Chromium 开源项目之上的。它与 Edge 和 Chrome 使用…

Android kotlin序列化之Parcelable详解与使用(二)

一、介绍 注解序列化篇&#xff1a;Android kotlin序列化之Parcelize详解与使用_蜗牛、Z的博客-CSDN博客 通过上一篇注解序列化&#xff0c;我们已了解的kotlin的序列化比Java复杂了很多。而且有好多问题&#xff0c;注解虽好&#xff0c;但是存在一些问题。 一般在大型商业…

【Flutter】如何更改 Flutter 应用的启动图标

文章目录 一、前言二、什么是启动图标三、为什么我们需要更改启动图标四、如何更改启动图标五、注意事项六、总结 一、前言 欢迎来到 Flutter 的世界&#xff01;在这篇文章中&#xff0c;我们将探索 Flutter 的一些基础知识。但是&#xff0c;你知道吗&#xff1f;这只是冰山…

爬虫一般怎么解决加密问题?

① 对于网页端来说通常加密的算法是写在 js 代码里的&#xff0c;所以首先你要对 js 语言有所了解。 至少知道 js 基础的内容&#xff0c;其次找到对应 js 加密代码&#xff0c;然后找出关键的函数。 把 js 代码在 node.js 环境进行调试&#xff0c;最后在 Python 环境下利用…

Goby 漏洞发布|maxView Storage Manager 系统 dynamiccontent.properties.xhtml 远程代码执行漏洞

漏洞名称&#xff1a;maxView Storage Manager 系统 dynamiccontent.properties.xhtml 远程代码执行漏洞 English Name&#xff1a;maxView Storage Manager dynamiccontent.properties.xhtml RCE CVSS core: 9.8 影响资产数&#xff1a;1465 漏洞描述&#xff1a; maxVie…

C++debug-centos-ubuntu-vscode

1.centos下安装VSCODE 在linux系统(centOS7)中安装VSCode(Visual Studio Code)_centos vscode安装_沈醉不知的博客-CSDN博客 pacman -S code2.ubuntu下安装VSCODE 与windows下一样。 3.windows 调试 下载安装vscode cmake https://cmake.org/download GDB:UNIX及UNIX-…

php7.4生产环境压力测试CPU占用100%解决方案

最近开发了一个项目&#xff0c;客户要求压力测试&#xff0c;测试时发现并发量大时php-fpm占用cpu一直100%,调整了php的最大进程数pm.max_children&#xff0c;优化了程序效果不明显。后面使用了opcache&#xff0c;cpu使用率一下降到20%左右。 什么是opcache&#xff1f;下面…

网络渗透攻击与加固

目录 一、内网渗透模拟攻击 1.1 综合扫描工具 1.1.1 X-scan 1.1.2 Zenmap 1.2 内网渗透模拟流程 二、Net命令 2.1 net user 2.1.1 功能 2.1.2 用法 2.2 net localgroup 2.2.1 功能 2.2.2 用法 2.3 net share 2.3.1 功能 2.3.2 用法 2.4 net use 2.4.1 功能 2…

Lecture 20 Topic Modelling

目录 Topic ModellingA Brief History of Topic ModelsLDAEvaluationConclusion Topic Modelling makeingsense of text English Wikipedia: 6M articlesTwitter: 500M tweets per dayNew York Times: 15M articlesarXiv: 1M articlesWhat can we do if we want to learn somet…

8寸Windows 10/Android 4.4系统三防平板电脑

8寸Windows 10/Android 4.4系统三防平板电脑是一款功能强大的工业平板电脑&#xff0c;能够在恶劣的工业环境中工作&#xff0c;并能够满足各种生产应用需求。该平板电脑采用了三防设计&#xff0c;能够防护尘土、水等物质的侵入&#xff0c;同时也能够抵抗震动&#xff0c;保证…

openGauss社区五月运作报告

前言 五月&#xff0c;openGauss社区在北京举办了一年一度的开发者大会&#xff0c;汇报社区最新的技术创新进展、生态进展与商业实践成果&#xff0c;同期社区开展了多个闭门会议&#xff0c;SIG工作组会议&#xff0c;规划未来社区工作治理方向&#xff0c;社区工作事项分…

实战案例|黑灰产肆虐,腾讯ACE一键打造清朗游戏世界

随着游戏行业的快速发展&#xff0c;相关黑色产业链的问题日益严重&#xff0c;各种外挂、违规内容、非法交易现象的出现破坏着游戏的生态&#xff0c;为行业带来诸多安全挑战&#xff0c;也影响着玩家们的游戏体验。越来越多游戏厂商开始重视游戏安全问题&#xff0c;并探索全…