首页 > 其他分享 >公共字段自动填充-AOP

公共字段自动填充-AOP

时间:2024-10-20 15:19:17浏览次数:10  
标签:填充 AOP UPDATE entity AutoFill 公共 注解 方法

1. 问题描述

1). 在新增数据时, 要将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。

2). 在更新数据时, 要将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。

在所有的新增和更新的业务操作中,都需要对上述字段进行赋值操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

答案是可以的,可以使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

还可以使用拦截器统一对公共字段赋值,下面使用AOP技术解决上述问题。

2. 解决思路

实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

2.1 自定义注解

自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法。

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

其中OperationType定义如下:

/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT
}

解释说明:

(1)自定义注解 @AutoFill

  • 注解作用:标记在某个方法上,用来自动填充一些特定的功能字段,类似于插入或更新操作时自动填充创建时间、修改时间等字段。
  • 注解属性:这个注解有一个属性 value,它表示当前方法执行时是进行什么类型的数据库操作(如 INSERTUPDATE),具体通过 OperationType 枚举值来指定。
  • 注解目标 @Target(ElementType.METHOD)
    • 表示这个注解只能用于方法上。
  • 注解保留策略 @Retention(RetentionPolicy.RUNTIME)
    • 表示这个注解将在运行时保留,也就是说你可以通过反射获取这个注解,并在程序运行时处理它。

(2)枚举类 OperationType

  • 作用:用于定义数据库操作的类型,包括 UPDATEINSERT
  • 枚举常量
    • UPDATE:表示更新操作(例如修改数据库中的某些记录时)。
    • INSERT:表示插入操作(例如向数据库中添加新的记录时)。

2.2 自定义切面类

自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值。

/**
 * 自定义切面类,用于拦截带有 @AutoFill 注解的方法,进行公共字段(如创建时间、创建人、更新时间、更新人)的自动填充
 */
@Aspect // 表示这是一个切面类,专门用于处理某些特定操作的拦截和逻辑执行
@Component // 将该类声明为 Spring 容器中的一个组件,便于自动扫描和管理
@Slf4j // 用于记录日志的注解,简化日志记录操作
public class AutoFillAspect {

    /**
     * 切入点的定义:拦截 com.sky.mapper 包下的所有方法,并且这些方法必须带有 @AutoFill 注解。
     * 也就是说,只有那些需要自动填充公共字段的方法才会被拦截。
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知:在目标方法执行之前进行操作,即在方法被实际执行前,先执行这个逻辑。
     * 在这里,我们会在方法执行前为其自动填充公共字段(如时间和用户 ID)。
     * 
     * @param joinPoint 这是连接点,表示当前拦截到的方法的上下文信息,如方法名、参数等。
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        // 1. 获取当前被拦截的方法上的数据库操作类型(是插入 INSERT 还是更新 UPDATE)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取方法签名,用于获取方法的详细信息
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // 获取当前方法上的 @AutoFill 注解
        OperationType operationType = autoFill.value(); // 从注解中获取数据库操作类型(INSERT 或 UPDATE)

        // 2. 获取当前拦截到的方法的参数列表,通常第一个参数是实体对象(即要保存或更新的数据)
        Object[] args = joinPoint.getArgs(); // 获取方法的参数
        if(args == null || args.length == 0){
            return; // 如果方法没有参数,直接返回,不做任何处理
        }

        Object entity = args[0]; // 获取第一个参数,即实体对象。通常数据库操作的第一个参数就是要处理的数据对象

        // 3. 准备要填充的公共字段数据:当前时间和当前操作用户的 ID
        LocalDateTime now = LocalDateTime.now(); // 获取当前的时间,用于填充创建时间和更新时间
        Long currentId = BaseContext.getCurrentId(); // 获取当前操作用户的 ID,从 ThreadLocal 中获取到当前用户的上下文信息

        // 4. 根据不同的数据库操作类型,选择填充对应的字段
        if(operationType == OperationType.INSERT){
            // 如果是插入操作(INSERT),需要填充四个公共字段:创建时间、创建人、更新时间、更新人
            try {
                // 通过反射获取实体类中的 set 方法,用于动态设置字段值
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); 
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 通过反射调用实体对象的这些 set 方法,为其字段赋值
                setCreateTime.invoke(entity, now); // 为创建时间字段赋值当前时间
                setCreateUser.invoke(entity, currentId); // 为创建人字段赋值当前用户的 ID
                setUpdateTime.invoke(entity, now); // 为更新时间字段赋值当前时间
                setUpdateUser.invoke(entity, currentId); // 为更新人字段赋值当前用户的 ID
            } catch (Exception e) {
                // 如果反射过程中出现异常,打印异常堆栈信息,便于排查问题
                e.printStackTrace();
            }
        } else if(operationType == OperationType.UPDATE){
            // 如果是更新操作(UPDATE),只需要填充两个字段:更新时间、更新人
            try {
                // 通过反射获取实体类中的 set 方法,用于动态设置字段值
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 通过反射调用实体对象的这些 set 方法,为其字段赋值
                setUpdateTime.invoke(entity, now); // 为更新时间字段赋值当前时间
                setUpdateUser.invoke(entity, currentId); // 为更新人字段赋值当前用户的 ID
            } catch (Exception e) {
                // 如果反射过程中出现异常,打印异常堆栈信息,便于排查问题
                e.printStackTrace();
            }
        }
    }
}

2.3 方法上加自定义注解

在 Mapper 的需要公共字段填充的新增和更新的方法上加入自定义注解。

下面以其中一个新增数据的方法为例:

@Mapper
public interface Mapper {
    /**
     * 插入数据
     * @param 
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);
}

3. 总结

使用AOP技术可以实现公共字段自动填充。

在切面类中统一填充公共字段,通过自定义注解的方式定位切点。

标签:填充,AOP,UPDATE,entity,AutoFill,公共,注解,方法
From: https://blog.csdn.net/qq_46637011/article/details/143091924

相关文章

  • 基于Node.js+vue公共台账管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于公共台账管理系统的研究,现有研究主要以企业内部的台账管理为主,专门针对公共事务领域台账管理的研究较少。在国内外的研究现状中,企业台账管理方面已......
  • AOP - 自己写 JDK 动态代理增强 bean
    AOP的原理就是给目标对象创建代理对象,达到增强目标对象方法的目的如果目标对象实现了接口就是用JDK动态代理,如果没实现接口就是用三方的CGLIB代理如果不使用AOP想要增强一个bean可以这样做:@ComponentpublicclassTestimplementsBeanPostProcessor,ApplicationCon......
  • AOP - 切点表达式
    某个特殊的方法:com.example.service.UserService类中所有以find开头的公共方法execution(public*com.example.service.UserService.find*(..))类中的所有方法:com.example.service包下所有类的所有方法execution(*com.example.service.*.*(..))特定参数类型的方法......
  • AOP - Advisor 示例
    定义通知publicclassLoggingAdviceimplementsMethodInterceptor{@OverridepublicObjectinvoke(MethodInvocationinvocation)throwsThrowable{System.out.println("Method"+invocation.getMethod().getName()+"isbeingcalle......
  • AOP - AspectJ 示例
    //自定义注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceLogExecution{}@Aspect//切面类@Order(1000)//数字越小,优先级越高@Component//也要注册到容器publicclassLoggingAspect{//定义切点@Pointcut("ex......
  • 公共自行车借用量的分析与预测算法的实现
    个人名片......
  • SpringAop学习笔记
    SpringAop学习笔记文章目录SpringAop学习笔记1.面向切面编程(AOP)1.1代理模式1.2静态代理1.3动态代理2.AOP概念及相关术语2.1概述2.2相关术语①横切关注点②通知(增强)③切面④目标⑤代理⑥连接点⑦切入点2.3作用3.基于注解的AOP3.1技术说明3.2准备工作3.3创......
  • P1439 【模板】最长公共子序列
    首先考虑动态规划,a[i]==b[j],f[i][j]=f[i-1][j-1]+1,否则f[i][j]=max(f[i-1][j],f[i][j-1]);然后看了一眼数据范围,被卡的实施的,然后考虑优化,看到题目是一个排列于是不要考虑重复的问题,于是只在b里看,如果b[i]在a中的位置,小于我们维护的最长的就不行,否则的话直接加入我们维护的最长......
  • 使用AES 128位加解密,加解密模式采用CBC,填充模式采用PKCS5Padding的Java工具方法示例
    importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjava.util.Base64;publicclassAESUtils{privatestaticfinalStringAES_ALGORITHM="AES/CBC/PKCS5Padding";private......
  • 代码随想录算法训练营day19| 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插
    学习资料:https://programmercarl.com/0235.二叉搜索树的最近公共祖先.html****学习记录:235.二叉搜索树的最近公共祖先(加一个函数traversal)点击查看代码#Definitionforabinarytreenode.#classTreeNode(object):#def__init__(self,x):#self.val=x......