利用Spring AOP实现数据库表单公共字段填充 | JoinPoint的应用

目录

1、AOP 相关概念

2、公共字段填充

2.1、为什么需要

2.2、自定义注解

2.2.1、元注解

2.2.2、AutoFill.java

2.3、Aspect切面

2.4、完整代码

3、JoinPoint


🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎Python人工智能开发和前端开发。
🦅主页:@逐梦苍穹

📕您的一键三连,是我创作的最大动力🌹

1、AOP 相关概念

本文利用AOP实现数据库表单公共字段填充

Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:

  1. Target(目标对象):代理的目标对象
  2. Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
  3. Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  4. Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
  5. Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
  6. Aspect(切面):是切入点和通知(引介)的结合
  7. Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

2、公共字段填充

2.1、为什么需要

在一个项目中,会涉及到多个表单,而这些表单当中,可能会存在着一些通用的字段。

(比如表单中某条数据的创建时间/修改时间、数据的创建者/修改者等)

在这个例子中,用到的是黑马苍穹外卖项目中的员工表单进行举例说明。

在这个表单中,存在着create_time、update_time、create_user、update_user这四个公共字段。

这类公共字段的数据填充,往往是用着相同的代码,这部分代码没必要重复写,如:

所以这里采用Spring结合AOP动态完成这四个字段的填充

2.2、自定义注解

2.2.1、元注解

在编写自定义注解代码之前,需要先复习一下元注解的内容:

元注解:注解注解的注解。

元注解有两个:

        ① @Target: 约束自定义注解只能在哪些地方使用

        ② @Retention:申明注解的生命周期

@Target中可使用的值定义在ElementType枚举类中,常用值如下:
        TYPE,类,接口

        FIELD, 成员变量

        METHOD, 成员方法

        PARAMETER, 方法参数

        CONSTRUCTOR, 构造器

        LOCAL_VARIABLE, 局部变量

@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如:

        SOURCE: 注解只作用在源码阶段,生成的字节码文件中不存在

        CLASS: 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.

        RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

2.2.2、AutoFill.java

这里需要定义一个自动填充的注解AutoFill.java(接口类型):

这个注解的作用是声明在对应的mapper上,表示这个mapper调用的时候需要自动填充对应的字段。

自动填充的注解的代码AutoFill.java如下:

操作类型OperationType.java如下:

解释:

这段代码定义了一个自定义注解 AutoFill,用于标记在方法上。让我们逐一解释注解的各个部分:

  1. @Target(ElementType.METHOD): 这是一个元注解,它用于标注自定义注解可以使用的地方。在这里,AutoFill 注解只能用于方法上。也就是说,你只能在方法上使用 @AutoFill 注解。
  2. @Retention(RetentionPolicy.RUNTIME): 这是另一个元注解,它用于指定自定义注解的保留策略。RetentionPolicy.RUNTIME 表示注解会在运行时保留,因此可以通过反射在运行时获取到注解的信息。
  3. public @interface AutoFill: 这定义了一个注解类型。关键字 interface 表示定义一个注解。注解名为 AutoFill
  4. OperationType value(): 这是注解的成员,被称为注解属性。在这里,AutoFill 注解有一个名为 value 的属性,其类型是 OperationType。注解属性的类型可以是任何基本数据类型、字符串、枚举、注解或上述类型的数组。

总体而言,AutoFill 注解用于标记方法,并且具有一个属性 value,该属性用于指定数据库操作类型。在使用 @AutoFill 注解时,你需要为 value 属性提供一个 OperationType 的值。

2.3、Aspect切面

有了自定义注解来标记哪个方法被调用时需要自动填充之后,就需要来实现自动填充功能了。

这里需要用到AOP思想中的切面,把切入点和通知结合起来。

通俗来说:

切入点:需要拦截的方法;

通知:拦截后需要干的事情

下面开始编写切面类AutoFillAspect.java:

①创建类,加上@Component(IOC控制反转)、@Aspect(定义切面)和@Slf4j(日志)注解

②抽取切点表达式

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")

解释说明:

这个切点表达式由两部分组成,使用了逻辑运算符 && 连接起来,表示同时满足两个条件:

  1. execution(* com.sky.mapper.*.*(..)) 这部分表示切点的方法执行匹配规则。具体来说,它匹配了 com.sky.mapper 包下的任意类的任意方法(*.*(..)表示任意方法,而 * 表示任意类名)。
  2. @annotation(com.sky.annotation.AutoFill) 这部分表示切点的注解匹配规则。它要求被切点选中的方法上必须有 com.sky.annotation.AutoFill 注解。

综合起来,autoFillPointCut 切点选择了 com.sky.mapper 包下的所有带有 @AutoFill 注解的方法。这样定义的切点通常用于在带有特定注解的方法上执行额外的逻辑,例如在方法执行前后进行日志记录、权限校验等操作。

③定义前置通知

这里的前置通知引用了表达式方法,等价于:

@Before("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")

④完善前置通知

这个部分总共分为以下几步:

  • 获取方法对象签名

  • 获得方法上的注解对象

  • 获得数据库操作类型

  • 获取到当前被拦截的方法的参数

  • 通过反射为对象属性赋值

2.4、完整代码

package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import com.sky.exception.AutoFillDatabasePublicFieldException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面类,实现公共字段自动填充逻辑
 *
 * @author 逐梦苍穹
 * @date 2023/10/8 22:06
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 指定切点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {
    }

    /**
     * 前置通知
     * 在通知中进行公共字段赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段的自动填充");
        //获取当前被拦截方法上的数据库操作类型
        /*
         * 获取方法签名对象,类型为MethodSignature,是Signature的子接口
         */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        /*
         * 获得方法上的注解对象
         */
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        /*
         * 获得数据库操作类型
         */
        OperationType operationType = autoFill.value();

        //获取到当前被拦截的方法的参数->实体对象
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0){
            throw new AutoFillDatabasePublicFieldException(MessageConstant.AUTO_FILL_ARGS_EXCEPTION);
        }
        /*
         * 约定参数第一个为实体对象
         */
        Object entity = args[0];
        //准备好赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //根据当前拦截到注解配置的不同的操作类型,为对应的属性通过反射机制来完成赋值
        if (operationType == OperationType.INSERT){
            /*
             * 为四个公共字段赋值
             */
            try {
                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);
                /*
                 * 通过反射为对象属性赋值
                 */
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        if (operationType == OperationType.UPDATE){
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

}

3、JoinPoint

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。

常用api:

方法名

功能

Signature getSignature();

获取封装了署名信息的对象,

在该对象中可以获取到目标方法名,所属类的Class等信息

Object[] getArgs();

获取传入目标方法的参数对象

Object getTarget();

获取被代理的对象

Object getThis();

获取代理对象

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Spring AOP(面向切面编程)是Spring框架中的一个模块,可以实现在代码执行期间的横切关注点的处理。表单验证是一个常见的需求,在接收到用户提交的表单数据后,需要对这些数据进行验证,确保数据的合法性。使用Spring AOP可以将这个验证过程从业务逻辑代码中剥离出来,使得代码更加清晰和可维护。 首先,我们需要定义一个切点(Join Point),它表示在哪些方法执行之前或之后我们要进行表单验证。例如,我们可以定义一个切点在所有的Controller层的方法执行之前进行表单验证。 接下来,我们需要定义一个切面(Aspect),它包含了在切点上要执行的逻辑。在这个切面中,我们可以定义各种验证规则,比如必填项验证、长度验证、正则表达式验证等。 然后,我们需要配置Spring AOP,将切面和切点关联起来。通过配置,Spring框架会在切点匹配的方法执行之前或之后,自动调用切面中的验证逻辑。 最后,我们只需要在业务方法中使用相应的注解,将需要进行表单验证的方法标识出来。比如,我们可以在Controller类的方法上加上@Validated注解。 使用Spring AOP实现表单验证的好处是可以将验证逻辑独立出来,不会和业务逻辑代码混在一起,使得代码更加易读和易维护。同时,由于AOP的动态代理机制,我们可以在不修改原有代码的情况下,添加、修改或删除验证规则。这种灵活性使得我们能够方便地对表单验证进行扩展和修改。 综上所述,通过使用Spring AOP,我们可以实现表单验证的功能,并将其与业务逻辑代码解耦,使代码更加清晰和可维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逐梦苍穹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值