java SpringBoot实现公共字段的填充

本文介绍如何在前后端分离项目中,通过SpringAOP和自定义注解避免数据库字段重复赋值,减少代码冗余,提高开发效率。作者演示了如何创建自定义注解、切面类以及在Mapper层应用这些技术来简化代码编写。
摘要由CSDN通过智能技术生成

问题背景:在我们做前后端分离项目,或者是其他项目的时候,代码的简洁成为了我们必不可少的一部分,在数据库设计的时候,有时候同样的代码或字段需要重复的去编写,这大大提高了代码的冗余度。

例如:在我们的用户表user和我们的类似于发布动态的表 难免会出现相同的字段,例如:更新时间,修改时间,创建时间等。一系列字段,需要我们每次做update或者insert时,都会重复的编写对应的代码进行赋值,这样我们的代码冗余就会体现出来。

解决方案:为此我们可以使用spring中的AOP面向切面编程对其进行简化,并且使用自定义注解进行实现这种重复赋值的情况。

项目初始化的版本

  1. spring版本:3.0.2
  2. maven版本:3.8.8
  3. jdk版本:java8及以上,我这里是jdk17
  4. Idea版本这个不是很重要:我这里是2023.x

项目依赖:

主要的就是需要引入自定义切面的一些包

<!--        自定义切面类Aspect-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

代码编写:

创建一个自定义注解类 AutoFill

package com.sxy.recordnetwork.annotation;

import com.sxy.recordnetwork.enumeration.OperationType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用于标识某个方法需要进行普通自动填充,例如修改用户信息需要涉及到id我们不能直接一个个得到,id我们只能通过注解模式
 * @author Administrator
 */
// 指定该注解只能加载方法上method -> METHOD
@Target(ElementType.METHOD)
/*
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    // 指定属性,指定数据库操作的类型 定义枚举类UPDATE
    OperationType value();
}

我自定义注解的类型是一个枚举类OperationType,我们定义一个枚举类用来存放UPDATEINSERT操作

package com.sxy.recordnetwork.enumeration;

/**
 * 定义更新的操作枚举类
 */
public enum OperationType {
    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 新增操作
     */
    INSERT
}

接着我们创建一个自定义的切面类AutoFillAspect用于配置AOP切面的切入点方法

package com.sxy.recordnetwork.aspect;


import com.sxy.recordnetwork.Utils.BaseContext;
import com.sxy.recordnetwork.annotation.AutoFill;
import com.sxy.recordnetwork.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 自定义切面类,实现公共字段的填充,例如修改用户信息使用到where条件是用户的id
 */
@Component
@Slf4j
@Aspect
public class AutoFillAspect {

    /*
      切入点 对哪些类的哪些方法来进行拦截进行定义
     */
    // execute(返回值是所有的* 在com.sky.mapper包下 所有的类 所有的方法 (匹配所有的参数类型 ) && 满足这个方法上加入了AutoFill的注解)
    @Pointcut("execution(* com.sxy.recordnetwork.mapper.*.*(..)) && @annotation(com.sxy.recordnetwork.annotation.AutoFill)")
    public void autoFillPointCut() {
    }
    /* 前置通知 因为我们要在insert 和update语句执行之前做相关操作
      在通知中进行公共字段的赋值
      Before("切入点,当匹配表达式的时候执行的方法")
   */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段自动填充。。。");
        // 1、获取到当前被拦截的方法上的数据库操作类型是INSERT 还是 UPDATE
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 方法签名对象
        AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class); // 获得方法上的注解对象
        OperationType operationType = annotation.value(); // 获得数据库操作类型

        // 2、获取到当前被拦截的方法的参数 -- 实体对象
        Object[] args = joinPoint.getArgs();
        // 是否为空指针
        if (args == null || args.length == 0) {
            return;
        }
        // 通过object接受,因为实体对象不确定
        Object UserUpdateDTO = args[0];

        // 3、准备赋值的数据
        Long currentId = BaseContext.getCurrentId(); // 当前用户id 这个ID来自于当前线程中的用户id
        if (operationType == OperationType.UPDATE) { // update操作
            // 为一个公共字段进行填充id
            try{
                Method setUpdateUser = UserUpdateDTO.getClass().getDeclaredMethod("setUserNo", Long.class);
                setUpdateUser.invoke(UserUpdateDTO, currentId);
            }catch(Exception e){
                e.printStackTrace();
            }
        }else{ //  insert操作

        }
    }
}

整体的代码我在这里解读一下:

我们这个类中有两个方法分别为:

  1. autoFillPointCut
    1. 定义切入点
    2. 参数列表为空
    3. 注解:@Pointcut:定义切入点匹配类型
      1. 匹配规则为:
        1. 返回值为所有的* 在com.sxy.recordnetwork.mapper包下.所有的类.所有的方法(匹配所有的参数类型) &&(并且) 满足这个方法上添加了AutoFill的注解)
  2. autoFill
    1. 逻辑处理代码
    2. 参数列表为JoinPoint类  用于获取拦截到的方法上的数据类型
    3. 注解:@Before:前置通知,在方法执行之前进行拦截通知  注解中的参数为切入点,这里的切入点就是上面的方法autoFillPointCut,满足autoFillPointCut这个方法上的切入点Pointcut注解的匹配规则进行拦截

autoFill方法内部代码解读:

@Before:前置通知,因为需要在insert和update语句执行之前做操作,在通知中进行公共字段的赋值

方法参数:JoinPoint  用于获取拦截到的方法上的数据类型是INSER还是UPDATE等或其他

首先:我们就是用这个JoinPoint 来获取方法的签名对象

接着:获得方法上的注解对象

然后:获取数据库的操作类型

紧接着:获取到当前被拦截的方法的参数 -- 实体对象,是一个Object类型数组,因为我们不知道具体的类型是什么,并且被拦截的方法列表上可能由多个参数,所以我们获取到Object类型数组

是否为空:我们判断一下是否为空指针,方法空指针错误

紧跟着:通过object类型接收,因为实体对象不确定,我们拿到object类型数组的第一个参数,我们在这里规定执行修改或添加操作时,第一个参数必须是一个实体对象,所以我们就获取到object类型数组中的第一个数据

然后:准备需要赋值的数据。我这里只是对修改的时候获取当前用户线程中的id,做的一个演示,拿到这个数据,甚至你可能还需要获取当前时间。

跟着:用上面获取到方法上注解的参数是否和我定义的枚举类OperationType.UPDATE是相等的,如果是UPDATE就执行update相关操作,否则就执行insert操作

最后:我们通过拿到实体类中的set属性方法,对应的数据类型为Long.class,然后对其赋值即可完成操作

Mapper层代码:

/**
 * 修改用户信息的方法
 * @param userUpdateDTO
 */
@AutoFill(OperationType.UPDATE)
void saveUser(UserUpdateDTO userUpdateDTO);

我们只需要在mapper层的某个修改方法或者新增方法上添加这个注解指定类型参数即可

总结:以上就是我们为了防止重复代码会造成一个代码冗余的方法,对其进行一个解决问题的方法,通过自定义注解和自定义AOP切面类实现的一个公共字段的填充。

如果有什么问题欢迎在评论区下面留言,各位博友们,如果觉得这篇文章写得好对你有帮助,麻烦给个评论 + 关注 + 点赞 + 收藏呗。😊😊😊

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

树若逝花若殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值