当前位置: 首页 > 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层)不再需要关心这些字段的设置,使得代码更加简洁、健壮,并且易于维护。

相关文章:

  • 【AI News | 20250519】每日AI进展
  • 深度强化学习 | 基于SAC算法的移动机器人路径跟踪(附Pytorch实现)
  • Manus AI 突破多语言手写识别技术壁垒:创新架构、算法与应用解析
  • 9万字67道Java集合经典面试题(2025修订版)
  • 安全强化的Linux
  • 云原生时代的系统可观测性:理念变革与实践体系
  • 枪机定焦系统的自动控制装置
  • 1.3.3 数据共享、汇聚和使用中的安全目标
  • spring中的EnvironmentPostProcessor接口详解
  • 虚拟币制度钱包开发:功能设计与成本全解析
  • c#基础01(.Net介绍)
  • 每日c/c++题 备战蓝桥杯(洛谷P1440 求m区间内的最小值 详解(单调队列优化))
  • RHCE 练习三:架设一台 NFS 服务器
  • 02-前端Web开发(JS+Vue+Ajax)
  • Oracle 数据库的默认隔离级别
  • Playwright 多语言一体化——Python_Java_.NET 全栈采集实战
  • 【Python 算法零基础 4.排序 ② 冒泡排序】
  • 5个yyds的.Net商城开源项目
  • 交易所开发:构建功能完备的金融基础设施全流程指南
  • .NET外挂系列:2. 了解强大的 harmony 注解特性
  • 大语言模型在线辩论说服力比人类辩手高出64%
  • 以色列媒体:以总理称将接管整个加沙
  • 证监会副主席李明:支持符合条件的外资机构申请新业务、设立新产品
  • 视觉周刊|走进变革中的博物馆
  • 以色列称“将立即允许恢复”人道主义物资进入加沙
  • 人民日报评论员:党政机关要带头过紧日子