今天在敲苍穹外卖的时候用到了 SpringBoot 中的 AOP,这里简单记录下使用过程。
背景
目前的CreateTime、CreateUser、UpdateTime、UpdateUser等字段都是在插入和更新操作时手动设置, 每次都要手动操作太麻烦,可以把这几个操作放到一块包装一下,可以用SpringBoot中的面向切面编程做到。
什么是AOP
AOP为Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
常用注解
- @Aspect: 把当前类标识为一个切面供容器读取
- @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
- @Around:环绕增强,相当于MethodInterceptor。
- @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行。
- @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有。
- @AfterThrowing:异常抛出增强,相当于ThrowsAdvice。
- @After: final增强,不管是抛出异常或者正常退出都会执行
这里盗用一下铿然架构的图。
使用
在这里为实现公共字段填充功能大体可以分为三步,即自定义注解、自定义切面类、在Mapper层方法上加上自定义注解。
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作,INSERT or UPDATE
OperationType value();
}
其中OperationType为枚举类,代码如下:
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
自定义切面类
写完自定义注解后,需要再写切面类
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* @Description ToDo 定义切点,指定需要拦截的方法
*/
// 这里execution中第一个 * 是返回类型,指返回值是所有的类型,这里要给mapper包下且带AutoFill自定义注解的方法加入切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill) ")
public void autoFillPointCut(){}
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("AutoFillAspect自动填充数据");
// 1.获取操作类型
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();
// 2.获取被拦截的方法的参数
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0){
return;
}
Object entity = args[0];
// 3.准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
// 4.根据不同的操作类型进行不同的赋值
switch (operationType){
case 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 (Exception e) {
e.printStackTrace();
}
break;
case 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 (Exception e) {
e.printStackTrace();
}
break;
default:
return;
}
}
}
在Mapper层添加方法
以为只有在执行 INSERT 和 UPDATE 操作时才需要填充字段,所以这里只需要给 insert 和 update 方法加入 @AutoFill 注解就可以。
@Mapper
public interface EmployeeMapper {
/**
* 根据用户名查询员工
* @param username
* @return
*/
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
/**
* 根据用户名查询员工
* @param employee
* @return
*/
@Insert("insert into employee(name,username,password,phone,sex,id_number,create_time,update_time,create_user,update_user,status)" +
"values" +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);
/**
* 员工信息分页查询
* @param employeePageQueryDTO
* @return PageResult
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
/**
* 员工信息修改
* @param employee
* @return PageResult
*/
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);
/**
* 根据id查询员工信息
* @param id
* @return PageResult
*/
@Select("select * from employee where id = #{id}")
Employee getById(Integer id);
}