当前位置: 首页 > news >正文

苍穹外卖 - Day03

一、公共字段自动填充:AOP的妙用

在我们的项目中,许多数据表(如 employee, dish, category 等)都有一些共通的字段,比如 create_time(创建时间)、update_time(修改时间)、create_user(创建人ID)、update_user(修改人ID)。每次执行插入或更新操作时,如果都在业务代码中手动为这些字段赋值,不仅繁琐重复,而且容易遗漏。

为了优雅地解决这个问题,“苍穹外卖”项目采用了面向切面编程 (AOP) 的思想,结合自定义注解Java反射机制来实现这些公共字段的自动填充。

1.1 核心技术点:AOP、自定义注解、反射与枚举

在深入具体实现之前,我们先对涉及的核心概念做个提纲挈领的介绍:

  • AOP (Aspect-Oriented Programming - 面向切面编程):

    • 核心思想: 将那些贯穿于多个业务模块中的通用功能(如日志记录、权限校验、事务管理、以及我们这里的公共字段填充)从主业务逻辑中分离出来,形成可重用的“切面”。这样可以降低业务逻辑的复杂度,提高代码的模块化和可维护性。

    (通俗点说:) AOP 就像给你的代码加装了一些“监控探头”和“自动处理装置”。你规定好在哪些地方(切点)装探头,探头发现特定动作(方法执行)时,就自动触发相应的处理(通知/增强逻辑),而不需要在每个地方都手动写一遍这些处理代码。

  • 注解 (Annotation):

    • 核心思想: 注解是附加在代码(类、方法、字段等)上的一种元数据(metadata),它本身不直接影响代码的执行逻辑,但可以被编译器或运行时环境读取,并据此执行某些特定的操作或提供额外信息。

    (打个比方:) 注解就像给代码元素贴上的“标签”。比如,@Override 标签告诉编译器检查这个方法是不是真的覆盖了父类的方法。我们也可以自定义标签,然后写一些代码来识别这些标签并执行特定逻辑。

  • 反射 (Reflection):

    • 核心思想: Java 反射机制允许程序在运行时动态地获取任意一个类的信息(如方法、字段)并能操作它们。

    (简单来说:) 反射就是让Java程序在运行时能“看透”和“操纵”它自己的代码结构,即使在编译时这些结构是未知的或者不可直接访问的(比如私有字段)。

  • 枚举 (Enumeration):

    • 核心思想: 枚举用于定义一组固定的常量集合。它比使用普通的静态final常量更类型安全,也更具可读性。

    (举个例子:) 我们需要表示数据库操作的类型,比如“插入”和“更新”。用枚举 OperationType.INSERTOperationType.UPDATE 就比用整数 01 要清晰得多,也不容易出错。

1.2 “苍穹外卖”项目中的实现方案

基于以上概念,“苍穹外卖”项目通过以下步骤实现公共字段的自动填充:

  1. 定义操作类型枚举 (OperationType.java):

    • 创建一个枚举类,明确标识出数据库操作的类型,例如:

      public enum OperationType {INSERT,UPDATE
      }
  2. 创建自定义注解 (@AutoFill.java):

    • 定义一个注解,比如 @AutoFill,用来标记哪些 Mapper 层的方法在执行时需要进行公共字段的自动填充。

    • 这个注解可以带一个 value 成员变量,其类型就是我们上面定义的 OperationType 枚举,用来指明当前被标记的方法执行的是插入操作还是更新操作。

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;@Target(ElementType.METHOD) // 注解作用于方法上
      @Retention(RetentionPolicy.RUNTIME) // 注解在运行时保留,以便通过反射读取
      public @interface AutoFill {OperationType value(); // 通过value属性指定数据库操作类型是INSERT还是UPDATE
      }
  3. 创建切面类 (AutoFillAspect.java):

    • 这是一个使用 @Aspect 注解标记的类,它包含了我们的横切逻辑(即自动填充公共字段的逻辑)。

    • 定义切点 (Pointcut): 使用切点表达式指定哪些方法会被拦截。这里我们希望拦截所有被 @AutoFill 注解标记的方法。

      • 切点表达式可能类似于:@Pointcut("@annotation(com.sky.annotation.AutoFill)") (假设@AutoFill注解在com.sky.annotation包下)

      • 或者更精确地指定到 Mapper 包下的方法:@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")

    • 定义通知 (Advice): 通常使用 @Before (前置通知),表示在目标方法(被 @AutoFill 标记的Mapper方法)执行之前,执行我们的自动填充逻辑。

      • 获取操作类型: 在通知方法中,可以通过连接点 (JoinPoint) 获取到目标方法上的 @AutoFill 注解,从而得到当前操作是 INSERT 还是 UPDATE

      • 获取实体对象: Mapper 方法的参数通常就是要操作的实体对象(比如 EmployeeDish 等)。通知方法需要从连接点的参数中获取到这个实体对象。

        (要注意的是:) Mapper方法可能有多个参数,实体对象不一定是第一个。需要约定好实体对象在参数列表中的位置,或者通过参数类型来判断。

      • 通过反射为公共字段赋值:

        1. 获取当前时间 (LocalDateTime.now())。

        2. 获取当前登录用户的ID (通常从 ThreadLocal 中获取,例如 BaseContext.getCurrentId())。

        3. 根据操作类型(INSERTUPDATE)和实体对象:

          • 如果是 INSERT 操作:

            • 需要为 createTime, updateTime, createUser, updateUser 这四个字段赋值。

            • 使用反射获取实体对象中名为 setCreateTime, setUpdateTime, setCreateUser, setUpdateUser 的方法。

            • 通过 Method.invoke() 方法调用这些setter方法,将当前时间和当前用户ID设置进去。

          • 如果是 UPDATE 操作:

            • 只需要为 updateTimeupdateUser 这两个字段赋值。

            • 同样使用反射获取并调用 setUpdateTimesetUpdateUser 方法。

        (这里为什么用反射?) 因为这个切面要处理项目中所有可能需要自动填充的实体(Employee, Dish, Category等),这些实体类各不相同,但它们都约定了具有如 setCreateTime 这样的方法。反射使得我们可以在不知道具体实体类型的情况下,动态地查找并调用这些公共的setter方法。

  4. 在 Mapper 接口的方法上使用 @AutoFill 注解:

    • 对于那些执行插入或更新操作的 Mapper 方法,在其声明上添加 @AutoFill 注解,并指明操作类型。

    • 例如,在 EmployeeMapper.java 中:

      @Mapper
      public interface EmployeeMapper {@AutoFill(OperationType.INSERT) // 标记为插入操作,需要自动填充void insert(Employee employee);@AutoFill(OperationType.UPDATE) // 标记为更新操作,需要自动填充void update(Employee employee);
      }

工作流程总结:

  1. 当调用被 @AutoFill 注解标记的 Mapper 方法时(例如 employeeMapper.insert(employee))。

  2. Spring AOP 机制会侦测到这个注解,AutoFillAspect 切面中定义的 @Before 通知会在目标方法执行前被触发。

  3. 通知方法获取到 @AutoFill 注解的值(INSERTUPDATE)和传入的实体对象。

  4. 根据操作类型,通知方法通过 Java 反射机制,动态地找到实体对象中对应的 setCreateTime, setUpdateTime 等方法,并用当前时间或当前用户ID调用它们,完成公共字段的赋值。

  5. 然后,目标 Mapper 方法(如 insert)继续执行,此时实体对象中的公共字段已经被填充好了,可以直接持久化到数据库。

通过这种方式,公共字段的填充逻辑被集中到了一个切面中统一处理,业务代码(Service层和Mapper层)不再需要关心这些字段的设置,使得代码更加简洁、健壮,并且易于维护。

http://www.xdnf.cn/news/532045.html

相关文章:

  • Solana 一键发币 + 自动建池:技术逻辑与挑战解析
  • 《Head First 设计模式》第二章 - 笔记
  • 手动制做一个Transformer
  • C++初阶-vector的使用
  • python-leetcode 67.寻找两个正序数组中的中位数
  • 如何在 Windows 11 或 10 上安装 Fliqlo 时钟屏保
  • CSS attr() 函数详解
  • HJ3 明明的随机数【牛客网】
  • 11.4/Q1,GBD数据库最新文章解读
  • threejs制作上升的小球
  • Kruise Rollout多批次发布
  • 3D 数据交换格式(.3DXML)简介
  • PyTorch Geometric(PyG):基于PyTorch的图神经网络(GNN)开发框架
  • 如何评估开源商城小程序源码的基础防护能力?
  • SCAU18924--二叉树的宽度多解
  • uniapp打包H5,输入网址空白情况
  • 样本复杂性:机器学习的数据效率密码
  • 【Vite】静态资源的动态访问
  • Libero离线IP安装
  • JWT : JSON Web Token
  • Linux 常用命令
  • 华为云Flexus+DeepSeek征文|基于华为云Flexus云服务的云服务器单机部署Dify-LLM应用开发平台
  • 力扣HOT100之二叉树:230. 二叉搜索树中第 K 小的元素
  • 【高德开放平台-注册安全分析报告】
  • LeetCode-滑动窗口-找到字符串中所有字母异位词
  • Swift 二分查找实战:精准定位第一个“Bug版本”(LeetCode 278)
  • 【栈 / 链表板子题】
  • 解决 uv run 时 ModuleNotFoundError: No module named ‘anthropic‘ 的完整指南
  • 【OSS】如何使用OSS提供的图片压缩服务
  • IDEA+AI 深度融合:重构高效开发的未来模式