一、创建一个自定义注解
AutoFill
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();
}
@Target(ElementType.METHOD)当前注解加在的位置(方法上)
@Retention(RetentionPolicy.RUNTIME)保留时间,生命周期
OperationType
public enum OperationType {
UPDATE,
INSERT
}
二、创建一个切面类
AutoFillAspect
//自定义切面,实习公共字段自动填充处理逻辑
@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("开始进行公共字段的自动填充...");
//
}
}
@annotation(com.sky.annotation.AutoFill)")
定义了一个切入点,指明了哪些方法会被拦截。这里表示所有在com.sky.mapper
包下的方法,并且这些方法需要有 AutoFill
注解。
@Before("autoFillPointCut()")前置通知
三、在mapper方法上加上注解
@AutoFill(value = OperationType.INSERT)
@AutoFill(value = OperationType.UPDATE)
测试发现已经拦截到方法了,接下来完善代码
AutoFillAspect
@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)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){
//为4个公共字段赋值
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,now);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType==OperationType.UPDATE) {
//2个
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();
}
}
}
}
-
获取被拦截方法的签名。MethodSignature signature = (MethodSignature)joinPoint.getSignature();
-
获取被拦截方法上的AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
AutoFill
注解。 -
获取数据库操作类型。OperationType operationType = autoFill.value();
-
获取被拦截方法的参数。Object[] args = joinPoint.getArgs();
到这里功能完成,我们就可以把更新之类的方法公共字段注释起来
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
//employee.setUpdateTime(LocalDateTime.now());
//employee.setCreateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
测试后程序正常运行
最后我查找了AOP 切面的相关资料,在此记录
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于处理横切关注点(cross-cutting concerns)。横切关注点是指那些跨越多个模块或组件的公共行为或功能,例如日志记录、事务管理、安全控制等。在传统的面向对象编程中,这些横切关注点通常需要在各个业务逻辑中重复编写,这不仅增加了代码的复杂性,还可能导致代码的维护困难。
AOP 的核心思想是将这些横切关注点从业务逻辑中分离出来,封装成独立的模块,称为“切面”(Aspect)。这样,就可以在不修改业务逻辑的情况下添加或修改这些横切关注点的行为。
AOP 的主要组成部分包括:
- 切面(Aspect):
- 切面是横切关注点的模块化实现。它可以包含方法、类或属性,以及对这些元素的增强逻辑。
- 连接点(Joinpoint):
- 连接点是在程序执行过程中某个特定的点,如方法调用、异常抛出或属性修改等。
- 通知(Advice):
- 通知是切面在特定连接点上执行的动作。通知可以是在方法调用前后执行的动作、异常抛出时的动作等。
- 切入点(Pointcut):
- 切入点是一组连接点的集合。它是通知所应用的位置,即定义了哪些连接点会触发通知的执行。
- 引入(Introduction):
- 引入允许在不修改类定义的情况下添加新的方法或属性。
- 织入(Weaving):
- 织入是指将切面与目标对象组合在一起的过程。这可以在编译时、加载时或运行时完成。
AOP 在 Java 中的应用:
Spring 框架支持 AOP,并提供了一种声明式的方式来进行 AOP 编程。在 Spring 中,可以通过以下方式定义切面:
- 使用
@Aspect
注解标记切面类。 - 使用
@Pointcut
注解定义切入点表达式。 - 使用
@Before
,@After
,@AfterReturning
,@Around
,@AfterThrowing
等注解来定义通知类型。
示例代码解析:
在代码中,AutoFillAspect
类是一个切面类,它使用了 AOP 来实现自动填充的功能。下面逐项解释:
- 切面类(Aspect Class):
@Aspect
和@Component
注解表明AutoFillAspect
类是一个切面类,它将被 Spring 管理。
- 切入点(Pointcut):
@Pointcut
注解定义了一个名为autoFillPointCut
的切入点表达式,该表达式匹配所有位于com.sky.mapper
包下的方法,并且这些方法必须标注有@AutoFill
注解。
- 通知(Advice):
@Before
注解定义了一个前置通知autoFill
,该通知在匹配的切入点方法执行之前触发。
- 连接点(Joinpoint):
JoinPoint
对象joinPoint
代表了切入点方法执行时的连接点,可以通过它获取方法签名、参数等信息。
在 autoFill
方法中,通过 joinPoint.getArgs()
获取被拦截方法的参数,然后根据 @AutoFill
注解中的 value
属性确定操作类型,并使用反射机制来设置实体对象的公共字段(如创建时间、更新时间等)。
总结:
AOP 切面是 AOP 编程中的一个重要概念,它封装了横切关注点的逻辑,使得这些逻辑可以从主要业务逻辑中分离出来,便于管理和重用。在您的代码示例中,AutoFillAspect
类就是一个切面类,它负责自动填充实体对象的公共字段。