一个关于IntroductionAdvisor的bug

一个关于IntroductionAdvisor的bug


public class TestMain {public static void main(String[] args) {// 1. 准备被代理的目标对象People peo = new People();// 2. 准备代理工厂ProxyFactory pf = new ProxyFactory();// 3. 准备introduction advice,advice 持有需要额外添加的接口Developer和Developer接口的实现类DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor((Developer) () -> System.out.println("编码"));// 4. 添加advice和代理对象需要继承的接口pf.addAdvice(dii);// 5. 设置被代理对象pf.setTarget(peo);// 6. 这里强制类型转换会失败,因为代理对象采用JDK进行动态代理,只实现了Developer接口和Spring AOP内部接口//  这里按理应该采用Cglib代理才对 !!!peo = (People) pf.getProxy();peo.drink();peo.eat();// 7. 强制转换为Developer接口,实际方法调用会被introduction advice拦截,调用请求转发给了advice内部持有的Developer接口实现类Developer developer = (Developer) peo;developer.code();}public static class People {void eat() {System.out.println("eat");}void drink() {System.out.println("drink");}}public interface Developer {void code();}
}

运行结果:

Exception in thread "main" java.lang.ClassCastException: class com.sun.proxy.$Proxy0 cannot be cast to class com.spring.TestMain$People (com.sun.proxy.$Proxy0 and com.spring.TestMain$People are in unnamed module of loader 'app')at com.spring.TestMain.main(TestMain.java:20)

这里原本是期望代理对象能够采用Cglib进行代理的,因为目标对象没有实现任何接口,但是却因为ProxyFactory特殊处理了类型为IntroductionAdvisor的切面,将IntroductionAdvisor提供的接口都加入到了AdvisedSupport的interfaces接口集合中;导致DefaultAopProxyFactory最终执行代理时,选择采用jdk而非cglib。

所以我们得到的代理对象实际采用jdk实现动态代理,实现了Spring AOP模块内部相关接口和Developer接口,当我们强制将代理对象转换为People类型时,会抛出类型转换异常。


Spring AOP 模块版本为: 5.3.9

在这里插入图片描述

原因:

AdvisedSupport 在添加advice的时候会特殊处理IntroductionInfo类型的Advice , 将其额外实现的接口添加到interfaces接口集合中去 :

	@Overridepublic void addAdvice(Advice advice) throws AopConfigException {int pos = this.advisors.size();addAdvice(pos, advice);}@Overridepublic void addAdvice(int pos, Advice advice) throws AopConfigException {Assert.notNull(advice, "Advice must not be null");if (advice instanceof IntroductionInfo) {// We don't need an IntroductionAdvisor for this kind of introduction:// It's fully self-describing.addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));}...}@Overridepublic void addAdvisor(int pos, Advisor advisor) throws AopConfigException {if (advisor instanceof IntroductionAdvisor) {validateIntroductionAdvisor((IntroductionAdvisor) advisor);}addAdvisorInternal(pos, advisor);}private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {advisor.validateInterfaces();// If the advisor passed validation, we can make the change.Class<?>[] ifcs = advisor.getInterfaces();for (Class<?> ifc : ifcs) {addInterface(ifc);}}

​ 此时即便目标对象没有实现接口,interfaces集合也不会为空:

	private List<Class<?>> interfaces = new ArrayList<>();

这会导致DefaultAopProxyFactory选择是采用jdk还是cglib进行动态代理时,错误的选择JDK而非cglib进行动态代理,因此最终得到的代理对象不能够强制转换为目标对象类型,这与我们预期目标不符合:

	@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!NativeDetector.inNativeImage() &&(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}// interfaces集合此时不为空,所以会采用jdk进行动态代理private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {Class<?>[] ifcs = config.getProxiedInterfaces();return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));}

我不确定这边是否算是一个bug , 如果可以的话, 我更期望这边能够单独处理一下IntroductionAdvisor额外提供的接口列表,避免在目标对象没有实现接口的前提下,还是选择采用JDK动态代理。


笔者目前不太确定这是否算做一个bug,目前已将该问题反馈给Spring官方团队,Issue链接如下:

  • A bug related to IntroductionAdvisor

关于IntroductionAdvisor的用法,可以参考我之前写的这篇文章进行学习:

  • Seata 源码篇之AT模式启动流程 - 上 - 02

2023-09-26 Spring官方回复

在这里插入图片描述
简而言之就是确实存在这个bug,但是目前只能临时性强制采用cglib动态代理解决,后期会改进。

各位小伙伴使用IntroductionAdvisor的时候可以注意一下,不要踩了这个坑。

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

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

相关文章

影刀自动化采集底层逻辑

hello,大家好&#xff0c;这里是【玩数据的诡途】 接上回 <我的影刀故事> 今天给大家介绍一下整个采集的底层逻辑&#xff0c;包括业务流程自动化也是基于这一套基础逻辑进行展开的&#xff0c;顺便带大家熟悉一下影刀&#xff0c;既然叫影刀系列了&#xff0c;那后续一些…

13基于PCA的人脸识别,程序已调通,可将自己的数据替换进行识别,得到识别准确率结果,MATLAB平台。

基于PCA的人脸识别&#xff0c;程序已调通&#xff0c;可将自己的数据替换进行识别&#xff0c;得到识别准确率结果&#xff0c;MATLAB平台。

xorm数据库操作之Join、Union

golang的数据库操作xorm使用起来非常方便&#xff0c;不用再自己写SQl语句&#xff0c;而且xorm自己给我们做了SQL防注入等操作&#xff0c;用起来既方便又安全。此次文章我不会记录xorm的基本操作&#xff0c;我值记录一些特殊用法问题&#xff0c;包括动态创建表单、基于xorm…

CSS详细基础(二)文本样式

插播一条CSS的工作原理&#xff1a; CSS是一种定义样式结构如字体、颜色、位置等的语言&#xff0c;被用于描述网页上的信息格式化和显示的方式。CSS样式可以直接存储于HTML网页或者单独的样式单文件。无论哪一种方式&#xff0c;样式单包含将样式应用到指定类型的元素的规则。…

【node】发送邮件及附件简要使用说明

Nodemailer是一个用于Node.js应用程序的模块&#xff0c;可以轻松发送电子邮件。该项目始于2010年&#xff0c;当时没有合理的选项来发送电子邮件消息&#xff0c;如今它是大多数Node.js用户默认选择的解决方案。 一、环境配置 安装模块&#xff1a;nodemailer npm i nodema…

接口测试——接口协议抓包分析与mock_L2

目录&#xff1a; 抓包工具charles抓包工具fiddler抓包工具证书配置app抓包实战练习接口测试实战练习 1.抓包工具charles 工具介绍 支持 SSL 代理支持流量控制支持重发网络请求&#xff0c;方便后端调试支持修改网络请求参数支持网络请求的截获并动态修改可以自动将 json 或…

电子电子架构——AUTOSAR信息安全机制有哪些?(上)

电子电子架构——AUTOSAR信息安全机制有哪些&#xff08;上&#xff09; 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 人们会在生活中不断攻击你。他们的主要…

Mysql 子查询,最值查询

1.leetcode-184&#xff1a;查找部门内最高的薪水 首先是一个子查询&#xff0c;找出一个部门里最高的那个工资 随后查找最终需要的值&#xff0c;并且部门编号以及薪水应该包含在这个子查询中 最终答案&#xff1a; # Write your MySQL query statement below SELECT Depar…

爬虫获取一个网站内所有子页面的内容

上一篇介绍了如何爬取一个页面内的所有指定内容&#xff0c;本篇讲的是爬去这个网站下所有子页面的所有指定的内容。 可能有人会说需要的内容复制粘贴&#xff0c;或者直接f12获取需要的文件下载地址一个一个下载就行了&#xff0c;但是如下图十几个一级几十个二级一百多个疾病…

网工内推 | 网络工程师,软考证书优先,六险一金,包吃

01 科力信息 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责蚌埠项目的设备安装及调试&#xff1b; 2、对边界网络运行中的监控、故障排除、问题处理。 任职要求&#xff1a; 1、2年及以上网络相关工作经验&#xff0c;有交通管理网络运维经验优先&#xff1b…

重新认识Java中的死锁问题

一、定义 多个进程因抢夺系统资源而产生相互等待的现象。 二、场景模拟 package com.gui.practise.thread.deadlock;public class DeadLock {private final Object resource1 new Object();//资源 1 private final Object resource2 new Object();//资源 2 public v…

前端架构师之路03_移动端规范兼容处理

1 移动端页面制作规范 1.1 计量单位的使用 CSS 的计量单位选择 px&#xff1a;固定的像素值em&#xff1a;相对父级元素的 font-size 设置来作为当前元素 1em 所代表的像素值&#xff0c;如父节点的 font-size:10px&#xff0c;当前节点的 font-size:1.2em&#xff0c;则当前节…

什么才是物联网领域最好的开发语言?

什么才是物联网领域最好的开发语言&#xff1f; 最好&#xff01;运行最快&#xff1f;开发最高效&#xff1f;最容易学习&#xff1f; 各有特点&#xff01; 采用C/C语言&#xff0c;运行最快&#xff0c;一般采用厂家提供的底层驱动支持包BSP&#xff0c;所有MCU都支持。如…

2009-2018年各省涉农贷款数据(wind)

2009-2018年各省涉农贷款数据&#xff08;wind&#xff09; 1、时间&#xff1a;:209-2018年 2、范围&#xff1a;31省 3、来源&#xff1a;wind 4、指标&#xff1a;涉农贷款 指标解释 &#xff1a;在涉农贷款的分类上&#xff0c;按照城乡地域将涉农贷款分为农村贷款和城…

Django的设计模式及模板层

Django的设计模式及模板层 设计模式MVC和MVT MVC 代表 Model-View-Controller(模型-视图-控制器)模式。 M 模型层(Model),主要用于对数据库层的封装 V 视图层(View),用于向用户展示结果 (WHAT HOW) C 控制(Controller&#xff0c;用于处理请求、获取数据、返回结果(重要) 作…

记录使用iText7查找PDF内容关键字坐标,加盖电子签名、印章

一、前言 项目以前签字都是由C端那边进行合成操作&#xff0c;最近项目要求把那块功能&#xff0c;由后端进行实现&#xff0c;其中包含坐标、关键字、任意位置进行签字操作&#xff0c;坐标是最容易实现的&#xff0c;曾经也写过类似的功能在&#xff08;添加图片印章到PDF&a…

Spring学习笔记3 Spring对IOC的实现

Spring学习笔记2 Spring的入门程序_biubiubiu0706的博客-CSDN博客 控制反转是一种思想 控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP(开闭原则)原则,达到DIP(依赖倒置原则)原则 控制反转,反转的是什么? 1.将对象的创建权力交出去,交给第三方容器负责 2.对象与对象…

【Vue3 源码解析】nextTick

nextTick 是 Vue 3 中用于异步执行回调函数的函数&#xff0c;它会将回调函数延迟到下一个微任务队列中执行。其中&#xff0c;Vue 更新 DOM 是异步的。下面是对 nextTick 函数的详细解释&#xff1a; export function nextTick<T void, R void>(this: T,fn?: (this:…

【C#】Redis在net core下使用教程

系列文章 文章目录 系列文章前言一、Redis 简介1.1 Redis 优势1.2 Redis与其他key-value存储有什么不同&#xff1f; 二、Redis安装步骤2.1 下载链接2.2 安装测试 三、Redis修改帐户密码四、Redis写成Windows服务五、.net core - 使用CSRedisCore操作redis 前言 官方教程&…

25814-2010 三聚氯氰 阅读笔记

声明 本文是学习GB-T 25814-2010 三聚氯氰. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了三聚氯氰的要求、采样、试验方法、检验规则以及标志、标签、包装、运输、贮存、安全、 安全技术说明书。 本标准适用于三聚氯氰的产品…