mybatis注册一个自定义拦截器,拦截器用于自动填充字段

ops/2025/3/17 15:25:29/

MetaObject 通过反射机制访问 parameter 对象的类对象(Class 对象),为什么要修改类对象的属性值,类对象里都没有属性值,属性值在实例对象里呀,实例对象的属性值操作不需要反射呀?

场景:mybatisplus有自动生成主键的雪花算法策略,在实体类上,但是前提是数据库操作要走mybatisplus提供的BaseMapper里的insetr或update,

@GeneratedValue是jpa的注解,这里设置了自增,在生表阶段,不会设置id为雪花算法

将id赋值为雪花id是在哪个阶段呢,是在sql执行阶段,很好,那就是对应dao层框架了,他们封装了对于数据库的操作呀,尤其是mybatis,但很遗憾,如果你走的是mybatis的i定义接口和接口映射文件这一套,那就不能触发雪花id的生成策略了,因为mybatis实现操作数据库靠的是:依靠接口动态生成实现类,不要怀疑,真的有class对象和实例对象生成,再根据接口和实现类搞一个代理实现类的代理对象,这个代理对象里就是jdbc操作了,具体操作就看mapper.xml解析出来的内容了,所以mybatisse会调用代理对象的方法,所以,压根和baseMApepr没关系,

所以我们只能搞拦截器了,

@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class CustomIdInterceptor implements Interceptor, InnerInterceptor {private static final Logger logger = LoggerFactory.getLogger(CustomIdInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];//        如果是插入操作if ("insert".equalsIgnoreCase(mappedStatement.getSqlCommandType().toString())) {//        如果parameter是个List集合,那就是批量插入if (parameter instanceof java.util.List) {
//            遍历for (Object obj : (java.util.List) parameter) {MetaObject metaObject = SystemMetaObject.forObject(obj);
//                处理idif (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);}
//                处理deleted字段if (metaObject.hasGetter("deleted")) {metaObject.setValue("deleted", false);}//                处理创建时间if (metaObject.hasGetter("createdTime")) {metaObject.setValue("createdTime", java.time.LocalDateTime.now());}//                处理创建人if (metaObject.hasGetter("createdBy")) {metaObject.setValue("createdBy", UserUtil.getCurUser().getId());}}}else{
//            如果不是集合//                获取参数的mataObjiectMetaObject metaObject = SystemMetaObject.forObject(parameter);
//                处理idif (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);}
//                处理deleted字段if (metaObject.hasGetter("deleted")) {metaObject.setValue("deleted", false);}//                处理创建时间if (metaObject.hasGetter("createdTime")) {metaObject.setValue("createdTime", java.time.LocalDateTime.now());}//                处理创建人if (metaObject.hasGetter("createdBy")) {metaObject.setValue("createdBy", UserUtil.getCurUser().getId());}}}//            如果是更新操作if ("update".equalsIgnoreCase(mappedStatement.getSqlCommandType().toString())) {//            获取参数的mataObjiectMetaObject metaObject = SystemMetaObject.forObject(parameter);
//            处理更新时间if (metaObject.hasGetter("updatedTime")) {metaObject.setValue("updatedTime", java.time.LocalDateTime.now());}
//            处理更新人if(metaObject.hasGetter("updatedBy")){metaObject.setValue("updatedBy", UserUtil.getCurUser().getId());}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {return InnerInterceptor.super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);}@Overridepublic void setProperties(Properties properties) {// 可以在这里设置拦截器的属性}
}

看这小拦截器,其中的mateObject和Excuter,MappedStatementd都是mybatise的核心对象,但是,看了调试上的实例对象的地址,我发现

这个参数其实是你的实体类,

MetaObject 通过反射机制访问 parameter 对象的类对象(Class 对象),为什么要修改类对象的属性值,类对象里都没有属性值,属性值在实例对象里呀,实例对象的属性值操作不需要反射呀?

记住我的神之回答

获取 类对象 不需要知道实例对象的类型,

1. 实例对象与类对象的区别

在 Java 中:

  • 类对象(Class Object)

    • 是类的运行时表示形式,存储在方法区(Metaspace)中。

    • 包含类的元数据,例如字段、方法、构造函数等的定义。

    • 不包含实例字段的值,因为实例字段的值是属于具体实例对象的。

  • 实例对象(Instance Object)

    • 是类的具体实例,存储在堆(Heap)中。

    • 包含实例字段的实际值。

    • 每个实例对象都有自己的字段值,这些值是独立的。


2. 为什么需要反射?

在你的代码中,MetaObject 通过反射机制访问和修改实例对象的字段值。这是因为:

  1. 动态性

    • 在运行时,你可能不知道 parameter 对象的具体类型。它可能是任意实现了接口的类的实例

    • 反射机制允许你在运行时动态地访问和修改对象的字段,而无需在编译时知道具体类型。

  2. 封装性

    • Java 的封装性使得字段通常是私有的(private),无法直接访问。

    • 反射机制允许绕过封装性,直接访问和修改私有字段的值。

      • 你用getter和setter每个方法都不一样,怎解,就问你怎解。。。。

      • ​​​​​​​就算是通过反射,类对象里只有字段,没有字段值,所以修改字段值必定是操作了实例对象,这个没跑,就看是怎么操作的了,

        • 这是先将字段属性设置成外部可直接访问的,再对实例对象进行修改,改玩在设置成不可访问。。。。。。

  3. 通用性

    • MetaObject 提供了一种通用的方式来操作对象的字段,而无需针对每个类编写特定的代码。

    • 这使得拦截器可以处理任意类型的对象,只要这些对象有相应的字段。


3. 实例对象的属性值操作为什么需要反射?

虽然实例对象的属性值存储在实例对象中,但直接操作这些属性值需要知道对象的具体类型。例如:

java复制

MyEntity entity = (MyEntity) parameter;
entity.setId(IdUtil.getSnowflake().nextId());

这种方式的问题是:

  • 类型硬编码:你需要知道 parameter 的具体类型(如 MyEntity),并且在代码中显式地进行类型转换。

  • 不通用:如果 parameter 是其他类型的对象,这段代码就会失败。

使用反射的优势

通过反射,你可以动态地访问和修改字段值,而无需知道具体类型。例如:

java复制

MetaObject metaObject = SystemMetaObject.forObject(parameter);
if (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);
}

这种方式的优点是:

  • 通用性:可以处理任意类型的对象,只要这些对象有 id 字段。

  • 动态性:在运行时检查字段是否存在,并动态地设置值。


4. MetaObject 的实现原理

MetaObject 是 MyBatis 提供的一个工具类,它封装了反射操作,使得字段访问和修改更加方便。它的实现基于以下机制:

  1. 反射机制

    • 使用 Class 对象获取字段的定义。

    • 使用 Field 对象访问和修改字段值。

  2. 动态代理

    • MetaObject 提供了一种代理机制,使得你可以通过统一的接口访问和修改字段值,而无需直接使用反射 API。

  3. 缓存机制

    • 为了提高性能,MetaObject 会缓存类的元数据,避免重复的反射操作。


5. 示例代码解析

在你的代码中:

java复制

MetaObject metaObject = SystemMetaObject.forObject(parameter);
if (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);
}
  • SystemMetaObject.forObject(parameter) 创建了一个 MetaObject 实例,封装了对 parameter 对象的反射操作。

  • hasGetter("id") 检查 parameter 对象的类是否有 id 字段。

  • setValue("id", id) 动态地为 parameter 对象的 id 字段设置值。


6. 总结

  • 类对象(Class Object):存储类的元数据,不包含实例字段的值。

  • 实例对象(Instance Object):存储实例字段的实际值。

  • 反射机制:允许在运行时动态地访问和修改实例对象的字段值,而无需知道具体类型。

  • MetaObject:封装了反射操作,提供了一种通用的方式来操作实例对象的字段值。

通过反射和 MetaObject,你的拦截器可以动态地为任意类型的对象设置字段值,而无需在编译时知道具体类型。这种机制使得拦截器具有很高的通用性和灵活性。


http://www.ppmy.cn/ops/166531.html

相关文章

JVM 的不同组成部分分别有什么作用?

JVM(Java Virtual Machine)主要由以下几个核心组成部分构成: 1. 类加载器子系统 (Class Loader Subsystem): 作用: 加载类: 负责查找并加载 Java 类文件(.class 文件)到 JVM 中。 类文件可以来…

Git 常用命令指南

本文档旨在提供 Git 的常用命令及其使用示例&#xff0c;涵盖全局参数配置、获取本地仓库、基本概念、本地仓库操作、远程仓库操作和分支操作等内容。 1. 全局参数配置 Git 允许用户配置全局参数&#xff0c;以便在所有的仓库中共享这些设置。 <BASH> # 设置用户名 gi…

Ollama+DeepSeek+NatCross内网穿透本地部署外网访问教程

目录 一、Ollama 简介 二、下载Ollama 三、下载并运行 DeepSeek 模型 四、运行 DeepSeek 模型 五、NatCross免费内网穿透 六、配置 ChatBox 连接 Ollama 七、外网使用ChatBox体验 一、Ollama 简介 Ollama 是一个开源的本地大模型部署工具&#xff0c;旨在让用户能够在个…

第五章-动态规划

第五章-动态规划 写在前面&#xff1a; 本笔记是根据acwing网站:算法基础课进行制作的&#xff0c;欢迎大家支持y总&#xff0c;听过y总的课&#xff0c;你绝对会对于算法产生更深的理解和更浓厚的兴趣&#xff01; 本笔记可能会有部分视频的截图&#xff0c;我不知道是不是会造…

CF 230B. T-primes

题目 time limit per test&#xff1a;2 seconds&#xff1b;memory limit per test&#xff1a;256 megabytes We know that prime numbers are positive integers that have exactly two distinct positive divisors. Similarly, well call a positive integer t Т-prime,…

自动驾驶与车路协同

自动驾驶与车路协同 核心进展 2024年自动驾驶技术聚焦“城市NOA”&#xff08;城市领航辅助驾驶&#xff09;和纯视觉方案。比亚迪“天神之眼”系统通过多传感器融合&#xff08;5R12V12U&#xff09;实现无图城市领航&#xff0c;测试中高速路段零接管1012。特斯拉的端到端模…

微服务分层架构技术解析:从 API 到数据访问的全方位探秘

微服务分层架构技术解析&#xff1a;从 API 到数据访问的全方位探秘 前言 在当今复杂多变的软件开发领域&#xff0c;微服务架构已成为构建大型分布式系统的核心范式。它通过将系统分解为一组小型、独立且高度内聚的服务&#xff0c;实现了模块化开发、独立部署与扩展&#x…

前端vue3 setup,后端fastapi

介绍 在 Web 开发中&#xff0c;视频播放是一个常见的需求。HLS&#xff08;HTTP Live Streaming&#xff09;和 FLV&#xff08;Flash Video&#xff09;是两种常见的视频流媒体传输协议。以下是它们的详细介绍和实现方法。 FLV&#xff08;Flash Video&#xff09; 简介 F…