首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java反射与AOP实战:公共字段自动填充

Java反射与AOP实战:公共字段自动填充

作者头像
北极的代码
发布2026-04-22 17:16:56
发布2026-04-22 17:16:56
1160
举报
前言:前面我们完成了员工管理(新增员工和分页查询)和员工编辑等业务功能,同时因为业务逻辑一样,对于菜品的分类,我们直接导入了代码,下面我们主要实现的是公共字段的填充和新增菜品操作,以及难点和原理。

公共字段: 这里指的是在不同的业务表中,有相同的字段,如:创建时间,创建人,更新时间.......如果我们按照不同的业务逻辑去挨个实现,这就造成了代码的冗余繁杂,由此我们想到了一些解决办法。

实现思路——面向切面编程

首先我们需要知道在什么时机我们需要修改这些字段,以便对这些字段进行归类,如创建人和创建按时间都是在执行insert语句时进行修改的,更新时间是在执行insert和update语句时修改的。这时我们可以通过设置切面来统一的拦截mapper这一层,统一的为公共字段来统一的赋值。同时持久层mapper有许多字段,并不是每一个字段都需要被拦截,我们需要通过设置注解的方式来进行筛选(自定义注解AutoFill,用于表示需要进行公共字段填充的方法),之后我们使用自定义切面类AutoFillAspect来统一的拦截加入了AutoFill的方法,,通过反射为公共字段赋值。

实现步骤:

1.我们先创建一个自定义注解类,然后在里面指定数据库操作的类型,实现我们已经定义了数据库操作的类型,create,update。

OperationType value();这行代码的作用就是:

  1. 定义注解的参数:让注解可以接收一个OperationType类型的值
  2. 传递重要信息:告诉AOP这次操作是INSERT还是UPDATE
  3. 实现灵活配置:同一个注解可以通过不同参数实现不同行为

就像我们设计一张表格,上面有一个必填的选项框,让使用者勾选“新增”还是“修改”。AOP切面看到这张表格,就能根据勾选的选项,决定该填哪些字段。

这就是自定义注解中参数定义的标准写法,也是让注解变得灵活实用的关键所在!

2.创建一个切面类

并在里面设置切入点和通知

代码语言:javascript
复制
@Aspect @Component @Slf4j public class AutoFillAspect { /** * 切入点 */ @Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)") public void autoFilePointCut(){} /** * 设置通知 */ @Before("autoFilePointCut()") public void autoFill(JoinPoint joinPoint){ log.info("开始进行公共字段填充"); } }
具体操作完善:

代码语言:javascript
复制
//获取当前被拦截方法的数据库操作类型
 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
OperationType value = annotation.value();//获取操作类型

//获取到当前被拦截的方法参数---实体对象
Object[] args = joinPoint.getArgs();
if (args==null||args.length==0){
    return;
}
Object entity=args[0];//将实体放在第一位,用object接收

//准备赋值的数据
LocalDate now = LocalDate.now();
Long currentId = BaseContext.getCurrentId();

//根据不同的操作类型,为对应的属性进行赋值
if (value== OperationType.INSERT){
    //为四个公共字段进行赋值
    try {
        Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDate.class);
        Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDate.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
    //通过反射为对象赋值
        setCreateTime.invoke(entity,now);
        setCreateUser.invoke(entity,currentId);
        setUpdateTime.invoke(entity,now);
        setUpdateUser.invoke( entity,currentId);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}else if (value==OperationType.UPDATE){
    //为两个公共字段进行赋值
    try {
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDate.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //通过反射为对象赋值
        setUpdateTime.invoke(entity,now);
        setUpdateUser.invoke( entity,currentId);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
反射调用的拆解:

代码部分

含义

比喻

entity

当前要操作的对象

一张具体的表格(可能是菜品卡/员工卡)

.getClass()

获取这个对象的类信息

看看这张表格是什么类型(菜品卡模板/员工卡模板)

.getDeclaredMethod()

获取这个类中声明的方法

在这个模板上找到叫某个名字的功能

AutoFillConstant.SET_CREATE_TIME

要找的方法名("setCreateTime")

要找的功能叫"填写创建时间"

LocalDate.class

这个方法需要的参数类型

这个功能需要传入"日期"类型的数据

反射与常规调用的对比:

方法

作用

区别

getMethod()

获取public方法(包括父类的)

只能拿public的

getDeclaredMethod()

获取本类声明的所有方法

可以拿private的,但不包括父类

代码详解:

在我们的代码中用getDeclaredMethod()是合适的,因为setter方法都在本类中定义。 解释: execution(* com.sky.mapper.*.*(..)) 匹配 com.sky.mapper 包下所有类、所有方法 • && @annotation(com.sky.annotation.AutoFill) 并且 方法上标了 @AutoFill 这个注解 最终意思:只有mapper里加了注解的方法,才会被增强。 @Before:在 目标方法执行之前 执行 • JoinPoint:可以拿到当前执行的方法、参数(实体对象) 这里真正要做的是: 1. 拿到方法参数里的 实体对象 2. 反射给它设置:createTime、updateUser 等字段

为什么要用 @Before?(最重要)

因为我们要做的是:在执行 SQL 之前,把字段值设置好 流程是这样的: 1. Controller 调用 Service 2. Service 调用 Mapper 3. Mapper 准备执行 SQL 公共字段必须在 SQL 执行之前就填进去 @Before = 在 mapper 方法执行之前填充字段,这样 SQL 才能拿到正确的值插入/更新 • @After:方法执行完了才填充 → 没用,数据库已经写完了 • @Around:可以,但没必要,@Before 更简单清晰

具体分析流程(以Dish为例):

代码语言:javascript
复制
// 1. 有一个对象,但不知道具体类型
Object entity = new Dish();  // 实际上是Dish对象

// 2. 获取它的类信息
Class<?> clazz = entity.getClass();  // clazz = Dish.class

// 3. 在Dish.class中查找名为"setCreateTime"的方法
//    这个方法需要一个LocalDate类型的参数
Method setCreateTime = clazz.getDeclaredMethod("setCreateTime", LocalDate.class);

// 4. 找到了!setCreateTime变量现在代表dish.setCreateTime()这个方法

// 5. 调用这个方法,把now传进去
setCreateTime.invoke(entity, now);  // 相当于 dish.setCreateTime(now)
代码语言:javascript
复制
【编译时】(你写的代码)
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDate.class);

【运行时】(JVM实际做的)
1. entity对象指向 → [堆内存中的Dish对象: id=1, name="宫保鸡丁"]
2. entity.getClass() → 找到Dish类的元数据 [方法区中的Dish.class]
3. getDeclaredMethod() → 在Dish.class的方法表中搜索:
   ✓ setCreateTime(LocalDate) → 找到了!内存地址0x7F3A2B
   ✗ setCreateTime(String) → 没有
   ✗ setCreateTime(Date) → 没有
4. 返回Method对象,它内部保存了0x7F3A2B这个地址

【调用时】
setCreateTime.invoke(entity, now)
→ JVM通过Method对象保存的地址0x7F3A2B,直接跳转到setCreateTime方法的代码位置执行
→ 执行时,entity作为this对象,now作为参数传入
3.在mapper方法上加上@AutoFill注解

通过注解@AutoFill(value=OperationType.UPDATE).

图解:

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────────┐
│                     员工登录系统                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│              ThreadLocal记录当前用户ID                        │
│              BaseContext.setCurrentId(1001)                  │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│      Mapper方法执行(如insert(dish) 或 update(employee))      │
│      方法上有 @AutoFill(INSERT/UPDATE) 注解                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    AOP切面拦截                                │
│      1. 获取注解类型:INSERT 或 UPDATE                        │
│      2. 获取实体对象:dish 或 employee                        │
│      3. 从ThreadLocal获取当前用户ID                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                 根据操作类型执行填充                           │
│                                                              │
│  如果是INSERT:                      如果是UPDATE:           │
│  ┌─────────────────┐                ┌─────────────────┐    │
│  │setCreateTime(now)│                │setUpdateTime(now)│    │
│  │setCreateUser(1001)│                │setUpdateUser(1001)│    │
│  │setUpdateTime(now)│                └─────────────────┘    │
│  │setUpdateUser(1001)│                                       │
│  └─────────────────┘                                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   反射执行setter方法                          │
│   dish.setCreateTime(now)  →  Dish对象被赋值                 │
│   employee.setUpdateUser(1001) → Employee对象被赋值          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                执行原Mapper方法                              │
│         insert(dish) 或 update(employee) → 入库             │

结语:感谢大家观看到最后,如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现思路——面向切面编程
  • 实现步骤:
    • 1.我们先创建一个自定义注解类,然后在里面指定数据库操作的类型,实现我们已经定义了数据库操作的类型,create,update。
    • 2.创建一个切面类
      • 具体操作完善:
    • 反射调用的拆解:
    • 反射与常规调用的对比:
    • 代码详解:
      • 为什么要用 @Before?(最重要)
    • 具体分析流程(以Dish为例):
    • 3.在mapper方法上加上@AutoFill注解
  • 图解:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档