Spring好坑!为什么代理对象的属性没有值?

news2024/9/21 16:28:19

先看代码:

@Service
@Transactional
public class ZhouyuService {

    private String name = "zhouyu";

    public final void test() {
        System.out.println(name);
    }
}

关键点:

  1. 加了@Transactional,所以ZhouyuService会生成代理对象作为Bean对象
  2. name属性有默认值“zhouyu”
  3. test()方法为final

现在,通过Spring容器获取ZhouyuService的Bean对象,并执行test方法打印name属性:

ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class, args);

ZhouyuService zhouyuService = applicationContext.getBean(ZhouyuService.class);
zhouyuService.test();

问题来了,test()方法打印出来的name属性值为:null !

是不是不敢相信,不信你可以自己在电脑上试试,一开始我也不信,name属性有默认值啊,怎么会为null呢?

熟悉AOP底层原理的同学应该会想到,代理对象执行方法时,逻辑是这样的:

  1. 代理对象先执行自己的test()方法,从而执行切面逻辑
  2. 然后执行被代理对象的test()方法,从而执行原本逻辑

代理对象对应的是代理类,是ZhouyuService$EnhancerBySpringCGLIB$$f4bc73d9类
被代理对象对应的是被代理类,就是ZhouyuService类

一般情况下:

  1. 代理类的父类是被代理类
  2. 代理类会重写父类里面被代理的方法,比如test()方法
  3. 代理类会在自己的test()方法中,执行切面逻辑,并执行被代理对象的test()方法,被代理对象就是一个ZhouyuService对象

因此,当代理对象执行test()方法时,最终仍然会执行被代理对象的test()方法,从而打印被代理对象的name属性

如果是以上流程,那么打印出来的name应该是有值的。

但是,上面的代码中,test()方法前面加了final,表示不能被子类重写,因此代理类中是没有test()方法的,代理对象执行的test()方法,并不是自己的test()方法,也就是不会执行切面逻辑,也就是事务会失效。

但是,自己没有test()方法,父类有啊,所以,代理对象实际上执行的是ZhouyuService类里的test()方法,从而打印name属性,但是打印的是代理对象的name属性,再由于ZhouyuService中的name属性为private,因此代理类中也没有继承该属性,因此代理对象中name属性为null,这是正常的。

以上的分析没有问题,可是,如果我把name属性改成public呢?那代理类就可以继承name属性了吧,那应该就能打印出来值了吧?

震惊的地方就在这里,打印出来的仍然是:null !

不理解了吧,子类继承父类里面的public属性,这不是天经地义的吗?

这里面的魔鬼在于Objenesis,第一次听说这个技术?让GPT来解析一下这个技术:
image.png

假如,我们用Objenesis来创建一个对象,并打印name属性:

Objenesis objenesis = new ObjenesisStd();
ZhouyuService zhouyuService = objenesis.newInstance(ZhouyuService.class);
System.out.println(zhouyuService.name);

结果为null,因为使用Objenesis创建对象根本就没有走属性初始化这一步。

而Spring AOP里默认就会用这个技术,对应的类为ObjenesisCglibAopProxy,关键代码为:
image.png

通过上面的Spring AOP源码,发现其实可以通过开关来关闭使用Objenesis,这个开关是-Dspring.objenesis.ignore=true,设置为true,Spring AOP就不会使用Objenesis来创建代理对象了。

因此,我们把这个开关加上,重新回到上面让我们震惊的场景中进行测试,就能发现name属性有值了。

因此,我们上面分析的代理对象执行方法的流程并没有问题,代理类肯定会继承父类的name属性,只是代理对象在创建时默认使用的是Objenesis,创建出来的对象根本就没有对属性做初始化,所以最终name属性为null,不使用Objenesis就正常了。

好了,分析到这里文章其实可以结束了,但是,再给大家一个彩蛋。

我们把刚刚的Objenesis开关再去掉,也就是还是让Spring使用Objenesis,只不过,我们把name属性改为final。

你会发现,最终打印出来的name属性还是有值的,并不是null,这又是为啥?不是说用Objenesis创建的对象不会初始化属性吗?难道会初始化final的属性?

没有这种说法,没有说只初始化final的属性,而不初始化非final的属性,我们不妨看看现在的ZhouyuService:

@Service
@Transactional
public class ZhouyuService {

    public final String name = "zhouyu";

    public final void test() {
        System.out.println(name);
    }
}

仔细看看,不知道大家能不能分析出原因?如果分析出来了,记得给文章点个赞之后,就可以离开了。

如果没分析出来,那就看看编译后的ZhouyuService:

@Service
@Transactional
public class ZhouyuService {
    public final String name = "zhouyu";

    public ZhouyuService() {
    }

    public final void test() {
        System.out.println("zhouyu");
    }
}

明白了吗?点赞了吗?

甚至,你现在debug去看ZhouyuService代理对象,会发现debug会显示name属性为null,但是最终test()方法却能打印出来“zhouyu”。

因为,ZhouyuService代理对象的name属性确实没有值,没有值的原因就是Objenesis,test()方法之所以能打印出来值,是因为编译优化,直接将name属性的值内联到test()方法中了。

分析了这么多,是不是有点晕了,最后,我再来给大家梳理一下:

  1. Spring会用cglib来创建代理类,会用Objenesis来创建代理对象,因此不会初始化代理对象中的属性,这是可以理解的,因为代理对象的作用是去代理方法,而不是代理属性,所以代理对象不关心属性,使用Objenesis可以更快的创建代理对象,但是会导致代理对象中的属性为null
  2. 如果方法加了final,那么就不能被代理到,导致打印的是代理对象的name属性,如果不是final,就被代理到了,导致打印的是被代理对象的name属性
  3. final的属性很有可能会被编译内联到方法中

以上,正式结束,非常感谢我的一位学员,是他发现并一起解决了这个问题。

关注我,我是大都督周瑜,我的公众号:IT周瑜。

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

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

相关文章

HAProxy理论+实验

目录 一、基于cookie的会话保持 1、配置选项 2、配置示例 3、验证cookie信息 二、IP透传 1、layer4 与 layer7 (1)四层:IPPORT转发 (2)七层:协议内容交换 三、haproxy的ACL应用 1、ACL配置选项 (1&#xf…

应用案例:劳易测传感器助力宝马集团莱比锡工厂锂电池生产

位于德国萨克森州的宝马集团莱比锡工厂,是全球领先的汽车制造基地之一,不仅生产燃油车,也致力于电动汽车的生产。随着电动汽车及混合动力车辆的普及,锂电池的需求日益增长,宝马集团在莱比锡工厂内部设立了锂电池生产线…

Python之简单了解pylab绘图工具和汇编语言

《Python入门经典以解决计算问题为导向的Python编程实践》89-93页的笔记。 用pylab对数据绘图最小的通用计算 用pylab对数据绘图 PyLab是Matplotlib面向对象绘图库的过程界面。Matplotlib是整个软件包; matplotlib.pyplot是Matplotlib中的一个模块;而P…

能够清理浮毛的宠物空气净化器哪家好用?希喂、安德迈测评分享

虽然已经立秋了,但是现在这个天气还是很热,尤其是还处在南方城市就更加了,天气热空气中的水含量还高,这就代表着即使下雨天能降温但身体还是会有黏黏的感觉。家里养有猫和狗,大汗淋漓的到家,一进门就被我家…

17位著名妈妈和女儿在电影中合作 包括斯特里普、黛米摩尔、安吉丽娜朱莉等

好莱坞母女二人组正在占领大银幕。如今,你不会只在头条新闻中看到她们的名字。这些强大的女性正在联手,创造电影奇迹,并为她们家喻户晓的名字增添更多的明星影响力。 虽然像戈尔迪霍恩和凯特哈德森这样的母女组合更喜欢分开工作,…

Python进阶之3D图形

Python进阶之3D图形 在数据可视化中,2D图形通常可以满足大多数需求。然而,对于一些复杂的数据或分析,3D图形可以提供更多的视角和洞察。在Python中,使用 Matplotlib 和 Plotly 等库可以轻松创建各种3D图形。本文将介绍如何使用这…

C++第一讲:开篇

C第一讲:开篇 1.C历史背景1.1C创世主--本贾尼1.2C版本更新1.3C的重要性1.4C书籍推荐 2.C的第一个程序3.命名空间3.1namespace是什么3.2namespace的使用3.3namespace使用注意事项3.4命名空间的使用 4.C输入和输出5.缺省参数6.函数重载7.引用7.1什么是引用7.2引用的定…

点餐系统软件源码入门教程:从零开始构建你的餐饮系统

随着餐饮行业的数字化转型,点餐系统已经成为餐厅运营不可或缺的一部分。无论是新手开发者还是有经验的程序员,学习如何从零开始构建一个点餐系统,都是一项具有挑战性但又非常有意义的任务。本文将带你逐步了解如何使用基本的技术和代码&#…

E. Lucky Queries

https://codeforces.com/contest/145/problem/E 元素值只有4,7转换成01序列,操作一区间反转,操作二询问类LIS 我们先考虑操作二 应该维护什么量呢 线段树维护量,是通过左子树和右子树的信息合并来维护的 大致有两种情况 可以发现可以通过Leftcnt0Righ…

45.跳跃游戏

:双层for。复杂度n*n n class Solution {public int jump(int[] nums) {// 找到所有的条约方法,返回其中的最小次数// 从后向前,依次记录到最后的次数int n nums.length;if(n 1) return 0;// int[] temp new int[n];// temp[n-1] 0;fo…

Redis远程字典服务器(5) —— hash类型详解

目录 一,hash基本情况 二,hash常用命令详解 2.1 hset,hget,hexists,hdel 2.2 hexists,hdel 2.3 hkeys,hvals 2.4 hgetall,hmget 2.5 hlen,hsetnx 2.6 hincrby&am…

Android逆向题解 攻防世界难度4- Android2.0

Jeb打开apk 关键代码在Native函数getResult IDA 打开 so 发现代码比较简单,可以直接静态分析。 输出字符串也就是flag 长度是15,然后分成三段,第一段是可以整除3,第二段是除3取余1,第三段是除3取余等于2&#xff1…

【Redis进阶】缓存设计模式

目录 Cache Aside(旁路缓存)模式 概念 读操作流程如上图所示 写操作流程如上图所示 代码示例 总结 Read-Through 模式 概念 操作流程: 优点: Write-Through 模式 概念 操作流程: 优点: Writ…

【摄影后期技巧】连拍多张图像中快速找到最清晰的图像——Python代码实现

手持相机高速连拍过程,当快门速度不够高时不可避免出现模糊帧,通过肉眼去从多张连拍图像中找到最清晰的帧是比较费事的,可通过代码自动去计算最清晰的图像,省去挑选图像的麻烦事,同时也可以将模糊图像剔除掉&#xff0…

【Python学习-UI界面】PyQt5 小部件11-Dialog Button Box 确认与取消框

样式如下: 一个预配置的对话框,带有一个文本字段和两个按钮,OK和取消。在用户单击OK按钮或按下Enter键后,父窗口会在文本框中收集输入。 用户输入可以是数字、字符串或列表中的项。还会显示一个提示用户应该做什么的标签。 常用方法如下&…

详细介绍 Vue3 的 watch 和 watchEffect

在 Vue 3 中,watch 和 watchEffect 都是用于响应式地监听数据变化的工具,但它们有不同的使用场景和工作机制。 1. watch 1、概念 watch 是 Vue 3 提供的一个用于观察响应式数据变化并在数据发生变化时执行特定操作的工具。它通常用于执行副作用&#…

LeetCode 热题 HOT 100 (023/100)【宇宙最简单版】

【技巧】No. 0647 回文子串【中等】👉力扣对应题目指路 希望对你有帮助呀!!💜💜 如有更好理解的思路,欢迎大家留言补充 ~ 一起加油叭 💦 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xff…

基于spring boot的疫情信息管理系统

TOC springboot255基于spring boot的疫情信息管理系统 绪论 1.1研究背景与意义 信息化管理模式是将行业中的工作流程由人工服务,逐渐转换为使用计算机技术的信息化管理服务。这种管理模式发展迅速,使用起来非常简单容易,用户甚至不用掌握…

CSC5812C 同步降压5V2.5A DC/DC车载充电方案

CSC5812C 是一款同步降压型的 DC/DC 变换器 IC ,CSC5812C输入电压为 8~30V ,可取得 2.5A恒定输出电流,开关频率 120kHz 左右,具有良好的瞬态响应和环路稳定性。CSC5812C 外围元器件极少,具有线补、过流保护和热保护功能…

Android全面解析之context机制(二): 从源码角度分析context创建流程(上)

前言 这篇文章从源码角度分析context创建流程。 在上一篇Android全面解析之Context机制(一) :初识context一文中讲解了context的相关实现类。经过前面的讨论,读者对于context在心中有了一定的理解。但始终觉得少点什么:activity是什么时候被创建的&…