Spring AOP

本文介绍了AOP(面向切面编程)与OOP的区别,如何利用Spring创建代理实现自动填充功能,并举例说明如何在Mapper方法中使用自定义注解和切面类进行前后置通知。AOP有助于模块化分散的业务逻辑,减少代码重复。
摘要由CSDN通过智能技术生成

AOP

OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。使用AOP,实际上就是让Spring自动为我们创建一个Proxy,使得调用方能无感知地调用指定方法,但运行期却动态“织入”了其他逻辑,因此,AOP本质上就是一个代理模式。下面举一个使用AOP的例子。

在做工程的时候,插入或修改数据表,需要定义插入时间、修改时间、插入人、修改人等,几乎每张数据表对应的mapper类的方法执行前都需要执行上述操作,因此可以考虑通过Spring给我们创建一个代理方法替我们统一执行这些操作。如何控制对哪些方法统一执行上述操作?利用注解的方式加到mapper类对应需要的方法上。

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

    package com.sky.annotation;
    
    import com.sky.enumeration.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();
    }
    
    
  2. 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

    package com.sky.aspect;
    
    import com.sky.annotation.AutoFill;
    import com.sky.constant.AutoFillConstant;
    import com.sky.context.BaseContext;
    import com.sky.enumeration.OperationType;
    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.Method;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    
    /**
     * @projectName: sky-take-out
     * @package: com.sky.aspect
     * @className: AutoFillAspect
     * @author: fangjiayueyuan
     * @description: 切面
     * @date: 2023/12/7 23:33
     * @version: 1.0
     */
    @Aspect
    @Component
    @Slf4j
    public class AutoFillAspect {
        /**
         * @author jiayueyuanfang
         * @description 切入点定义:拦截com.sky.mapper包下所有类的所有方法,并且这些方法上有@AutoFill注解的
         * @date 2023/12/9 10:02
         */
        @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
        public void autoFillPointCut(){}
    
        /**
         * @description 前置通知,在通知中进行公共字段的赋值
         * @date 2023/12/9 10:05
         */
        @Before("autoFillPointCut()")
        public void autoFill(JoinPoint joinPoint){
            log.info("开始执行前置通知");
            // 获取当前被拦截的方法上的数据库的操作类型(反射)
            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){
                return;
            }
            Object entity = args[0];
            // 准备赋值的数据
            LocalDateTime now = LocalDateTime.now();
            Long currentId = BaseContext.getCurrentId();
            // 根据当前不同的操作类型,为对应的属性通过反射来赋值
            if(operationType == OperationType.INSERT){
                try {
                    // getDeclaredMethod("setCreateTime", LocalDateTime.class):在entity对象的类类型中查找名为setCreateTime且参数类型为LocalDateTime的方法
                    Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                    setCreateTime.invoke(entity, now);
    
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    setUpdateTime.invoke(entity, now);
    
                    Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                    setCreateUser.invoke(entity, currentId);
    
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                    setUpdateUser.invoke(entity, currentId);
    
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }else{
                try {
                    // getDeclaredMethod("setCreateTime", LocalDateTime.class):在entity对象的类类型中查找名为setCreateTime且参数类型为LocalDateTime的方法
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    setUpdateTime.invoke(entity, now);
    
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                    setUpdateUser.invoke(entity, currentId);
    
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
    
            }
        }
    }
    
    
  3. 在 Mapper 的方法上加入 AutoFill 注解

        @Insert("insert into employee" +
                "(name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)" +
                "VALUES " +
                "(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, " +
                "#{status}, #{createTime},#{updateTime},#{createUser}, #{updateUser})")
        @AutoFill(value = OperationType.INSERT) // 增加注解
        void insert(Employee employee);
    

总结

除了上面举的例子,一些诸如安全检查、日志、事务等代码,也会重复出现在每个业务方法中。单纯使用OOP,我们很难将这些四处分散的代码模块化,这些功能实际上“横跨”多个业务方法,因此使用AOP进行切面编程,防止在多个业务方法上重复编写代码。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值