AOP
OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。使用AOP,实际上就是让Spring自动为我们创建一个Proxy,使得调用方能无感知地调用指定方法,但运行期却动态“织入”了其他逻辑,因此,AOP本质上就是一个代理模式。下面举一个使用AOP的例子。
在做工程的时候,插入或修改数据表,需要定义插入时间、修改时间、插入人、修改人等,几乎每张数据表对应的mapper类的方法执行前都需要执行上述操作,因此可以考虑通过Spring给我们创建一个代理方法替我们统一执行这些操作。如何控制对哪些方法统一执行上述操作?利用注解的方式加到mapper类对应需要的方法上。
-
自定义注解 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(); }
-
自定义切面类 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); } } } }
-
在 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进行切面编程,防止在多个业务方法上重复编写代码。