AOP 面向切面编程 是一种编程思想 可以在不惊动原始设计的基础上为其进行功能增强

  • 连接点(JoinPoint) 在 springAOP 中 表示 方法的执行
  • 切入点(Pointcut)匹配连接点的式子

    切入点可以匹配多个连接点

  • 切面:描述通知与切入点的关系
  • 编写通知类 其中包含切面和通知方法

AOP 的工作流程

  1. Spring 容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化 bean,判定 Bean 对应的类中的方法是否匹配到任意切入点
  • 匹配成功:创建目标对象的代理对象(在 spring 初始化 bean 的时候将代理对象存入 IOC 容器中)
  • 匹配失败:创建对象(在 spring 初始化 bean 的时候一般会是创建对象 存入 IOC 容器中)
  1. 获取 bean 执行方法
  • 获取 bean,调用方法并执行(正常执行,相当于没有用到 AOP)
  • 获取的 bean 是代理对象时,根据代理对象的运行模式运行原来方法和增强内容

AOP 的通知类型

  1. 前置通知(@Before 注解 在方法执行前执行增强内容)
  2. 后置通知(@After)
  3. 环绕通知(@Around 方法执行前后都会执行指定增强内容)

    需要一个 ProceedingJoinPoint 类型的参数
    pjp.proceed()表示原代码的执行(这样就可以指定增强的内容在方法执行前后执行了)

    使用环绕通知的时候需要 接收原方法的返回值例如 Object ret = pjp.proceed(); 并 return。(原方法返回值类型是 void 除外)
    否则会报错

  4. 返回后执行 (@AfterReturning) 方法成功执行完后通知

    @AfterReturning 只有方法成功执行了后才通知
    @Around 方法只要执行完就会通知(报错也会)

  5. 抛异常后执行(@AfterThrowing)

AOP 通知获取数据

  • 获取切入点方法的参数
    JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    ProceedJointPoint:适用于环绕通知
    使用getArgs方法即可

    环绕通知时可以接收并更改参数,并传入源代码中


  • 获取切入点方法返回值
    返回后通知
    环绕通知
  • 获取切入点方法运行异常信息
    抛出异常后通知
    环绕通知

AOP 和自定义注解实现公共字段填充

@Component 注解:将这个类定义为 bean 交给 spring 容器管理
@Aspect :告诉 sring 容器 这个 bean 是 AOP

  1. @Pointcut(“execution( com.sky.mapper..*(..))&& @annotation(com.sky.anno.AutofILL)” )
- @Pointcut 注解:切入点用于匹配 连接点
- execution(* com.sky.mapper.*.*(..)) : 连接点的表达式,意思是 在执行 com.sky.mapper下的所有类中的所有方法的时候 ,com前面的*表示方法的返回值可以是所有类型
- @annotation(com.sky.anno.AutofILL) :也是一个练级点的表达式 意思是 添加了com.sky.anno.AutofILL这个注解的方法
  1. 使用@Pointcut 需要一个空的方法 例如 public void pointcut() {}

注解

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutofILL {
OperationType value();
}

AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Aspect
@Component
public class AutoFILL {
@Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.anno.AutofILL)" )
public void pointcut() {}
@Before("pointcut()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//获取当前拦截方法的类型
MethodSignature Signature = (MethodSignature) joinPoint.getSignature();//这行代码是获取方法的签名对象
AutofILL autofILL= Signature.getMethod().getAnnotation(AutofILL.class);// 获得方法上的注解对象
OperationType operationType= autofILL.value();// 获取注解中的值
//获取实体对象
Object[] args = joinPoint.getArgs();// 获取方法的参数
if(args.length==0){
return;
}
Object object = args[0];
//准备复制数据
LocalDateTime now =LocalDateTime.now();
Long id = BaseContext.getCurrentId();
//赋值
Method setCreateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
Method setUpdateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
Method setCreateUesr = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
Method setUpdateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);
/* 这里利用了反射去获取到原参数的set方法 首先object.getClass()获取到了object的类(及参数的实体类)
getDeclaredMethod 是获取当前类的一个方法(需要用参数指定获取方法的方法名,以及该方法需要的参数类型)
最后获取到的是一个set方法 所以返回值类型是 Method */
if (operationType==OperationType.INSERT) {
setCreateTime.invoke(object,now);// 通过反射赋值
setUpdateTime.invoke(object,now);
setCreateUesr.invoke(object,id);
setUpdateUser.invoke(object,id);

}
else if (operationType==OperationType.UPDATE) {
setUpdateUser.invoke(object,id);
setUpdateTime.invoke(object,now);
}

}

}