首页
关于
Search
1
湖南工商大学校园网优化指北2
127 阅读
2
湖南工商大学校园网优化指北
36 阅读
3
GKD:搞快点,李跳跳的最好替代
24 阅读
4
记一次服务器宕机
14 阅读
5
梦开始的地方-力扣001 两数之和
11 阅读
默认分类
杂谈
学习
极客
分享
登录
Search
标签搜索
LeetCode
服务器
校园网
路由器
手机
Java
Zezin
累计撰写
11
篇文章
累计收到
7
条评论
首页
栏目
默认分类
杂谈
学习
极客
分享
页面
关于
搜索到
1
篇与
的结果
2026-01-16
CGLIB 代理对象属性赋值不一致问题分析与解决方案
前言在使用 Spring 等框架进行 Java 开发时,我们经常会遇到动态代理相关的问题。本文将详细分析一个由 CGLIB 代理机制引起的属性赋值不一致问题,并提供解决方案和深入的原理解释。问题描述在使用 IDEA 调试工具时,发现了一个奇怪的现象:问题 1:通过 pageDTO.setCreateUserNo(employeeNo) 赋值后,值被存储到了 $cglib_prop_createUserNo 字段中,而不是直接存储到 createUserNo 字段问题 2:通过 pageDTO.setBaseCodes(codes.get("baseCode")) 赋值时,值被存储到了 baseCodes 字段,但没有赋值到 $cglib_prop_baseCodes最终结果:后续代码通过 getCreateUserNo() 可以正常获取值,但通过 getBaseCodes() 却获取不到赋值内容相关代码如下:EqmsEqRepairOrderPageDTO pageDTO = (EqmsEqRepairOrderPageDTO) PageUtils.handleQuery(params.getModel()); // 设置用户编号 - 可以正常获取 pageDTO.setCreateUserNo(employeeNo); // 设置基地代码 - 无法获取 pageDTO.setBaseCodes(codes.get("baseCode")); // 后续调用 baseMapper.selectPageList(pageDTO); // 此时 baseCodes 为 nullDTO 类定义:@Data @EqualsAndHashCode(callSuper = false) @Builder @ApiModel(value = "EqmsEqRepairOrderPageDTO", description = "设备维修单") public class EqmsEqRepairOrderPageDTO extends BaseDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "单据状态") private String orderStatus; private List<String> baseCodes; private List<String> factoryCodes; private List<String> workshopCodes; }问题根源分析CGLIB 动态代理机制要理解这个问题,首先需要了解 CGLIB 动态代理的工作原理:代理对象的创建:CGLIB 通过在运行时动态生成被代理类的子类来创建代理对象方法拦截:通过重写父类的方法来实现拦截,在方法执行前后加入自定义逻辑属性管理:CGLIB 代理对象内部会维护一套自己的属性存储机制(如 $cglib_prop_xxx)继承层次导致的行为差异在本案例中,问题的关键在于:createUserNo:定义在父类 BaseDto 中CGLIB 会正确重写父类的 setCreateUserNo() 和 getCreateUserNo() 方法赋值和取值都通过代理对象的拦截器,行为一致baseCodes:定义在子类 EqmsEqRepairOrderPageDTO 中CGLIB 可能不会自动重写仅在子类中新增的方法导致 setter 和 getter 的代理行为不一致赋值与取值的"错位"赋值过程: pageDTO.setBaseCodes(codes) ↓ 未被代理拦截(直接调用原始方法) ↓ 值存储到真实对象的 baseCodes 字段 取值过程: pageDTO.getBaseCodes() ↓ 被代理拦截 ↓ 尝试从 $cglib_prop_baseCodes 中取值 ↓ 返回 null(因为这个字段从未被赋值)解决方案通过在子类中显式重写 getter 和 setter 方法,强制 CGLIB 将这些方法纳入代理拦截范围:@Data @EqualsAndHashCode(callSuper = false) @Builder @ApiModel(value = "EqmsEqRepairOrderPageDTO", description = "设备维修单") public class EqmsEqRepairOrderPageDTO extends BaseDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "单据状态") private String orderStatus; private List<String> baseCodes; private List<String> factoryCodes; private List<String> workshopCodes; // 显式重写 getter 和 setter @Override public List<String> getBaseCodes() { return baseCodes; } @Override public void setBaseCodes(List<String> baseCodes) { this.baseCodes = baseCodes; } }为什么这样能解决问题?当您显式重写这些方法时:Java 编译器和 CGLIB 会识别到这些方法在当前类中被明确声明CGLIB 在生成代理子类时,必须重写这些方法以实现拦截赋值和取值操作都会被代理一致地处理确保数据在代理对象内部能够被一致地存取深入理解:CGLIB 代理的工作流程正常情况(父类属性)实际对象:EqmsEqRepairOrderPageDTO (extends BaseDto) ↓ CGLIB 生成:EqmsEqRepairOrderPageDTO$$EnhancerByCGLIB$$xxxxx ↓ 重写父类方法:setCreateUserNo(), getCreateUserNo() ↓ 调用时都经过代理拦截器 ↓ 数据一致性 ✓问题情况(子类新增属性,未重写)实际对象:EqmsEqRepairOrderPageDTO ↓ CGLIB 生成:EqmsEqRepairOrderPageDTO$$EnhancerByCGLIB$$xxxxx ↓ 未重写子类新增的方法(取决于具体实现) ↓ setter 可能不被拦截 / getter 可能被拦截 ↓ 数据不一致 ✗解决后(显式重写)实际对象:EqmsEqRepairOrderPageDTO (显式声明 @Override) ↓ CGLIB 生成:EqmsEqRepairOrderPageDTO$$EnhancerByCGLIB$$xxxxx ↓ 强制重写:setBaseCodes(), getBaseCodes() ↓ 调用时都经过代理拦截器 ↓ 数据一致性 ✓最佳实践建议了解框架的代理机制:在使用 Spring、Mybatis 等框架时,要意识到对象可能被代理保持一致性:如果父类属性可能被代理,子类新增的属性也应该采用相同的处理方式显式声明:对于可能被代理的 DTO 类,建议在子类中显式重写 getter/setter 方法,即使看起来"多余"调试技巧:使用 IDEA 的"Evaluate Expression"功能查看对象的实际类型观察是否有 $$EnhancerByCGLIB$$ 等特征检查字段中是否存在 $cglib_prop_xxx 这样的内部字段避免直接访问字段:在可能存在代理的情况下,始终通过 getter/setter 访问属性总结CGLIB 代理对父类和子类方法的处理方式可能存在差异。当遇到代理对象属性赋值/取值不一致的问题时,通过在子类中显式重写相关的 getter 和 setter 方法,可以强制 CGLIB 将这些方法纳入代理拦截范围,从而解决数据存取不一致的问题。Lombok在该问题中扮演着重要角色,抛开其节省大量重复的代码不谈,Lombok是导致问题“隐藏”得更深的原因。从源码层面看:在您的 EqmsEqRepairOrderPageDTO.java 文件中,你看不到 getBaseCodes 和 setBaseCodes 方法。这会给人一种错觉,即这些方法和父类 BaseDto 中的方法(如 getCreateUserNo)在继承和重写方面没有区别。从 CGLIB 代理层面看:CGLIB 在创建代理子类时,需要决定重写哪些方法来实现拦截。它的分析机制很可能依赖于类在源码级别的继承和方法重写(@Override)关系。它能识别出 createUserNo 的 getter/setter 是继承自 BaseDto 的。但对于 baseCodes,由于在 EqmsEqRepairOrderPageDTO 的源码中没有显式的方法声明或 @Override 注解,CGLIB 可能认为这些方法只是普通的新增方法,从而采取了不同的代理策略,最终导致了赋值和取值行为的不一致。Lombok隐藏了方法实现的细节,使得在与 AOP、动态代理等底层技术结合时,可能会出现这种难以发现的“黑盒”问题。因为代理工具看到的是源码结构,而 Lombok 的魔法发生在编译期。参考资料CGLIB 官方文档Spring AOP 代理机制Java 动态代理技术详解
2026年01月16日
10 阅读
0 评论
3 点赞