AOP

概念 : AOP为Aspect Oriented Programming的缩写, 意思为切面编程。

他是一种可以在不修改原来核心代码的情况下给程序动态统一进增强的一种技术

springAOP : 批量对spring容器中的bean的方法做增强,并且这种增强不会与原来方法中的代码耦合

依赖

1
2
3
4
5
6
<dependency>
<groupId>org.aspect</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

快速入门实现

定义切面类

1
2
3
4
@Component 	//将类交给容器去管理
@Aspect //作用 : 告诉Spring容器,这是一个切面类
public class MyAspect{
}

切入点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component 	//将类交给容器去管理
@Aspect //作用 : 告诉Spring容器,这是一个切面类
public class MyAspect{

//定义切入点
@Pointcut("execution("需要增强的路径地址")")
public void pt(){}

//在被调用的方法之前执行
@Before("pt()") //增强是针对哪些方法
public void methodbefore(){

}

}

aop核心概念

连接点 : 【joinpoint】所谓的连接点就是能够被增强到的点,在spring中 只支持方法类型的连接点

切入点 : 【pointcut】所谓的切入点,就是指被增强的连接点

**通知/增强 :【Advice】 **所谓的通知是指jurisdiction增强的代码

**目标对象 : 【Target】 **被增强的对象就是目标对象

切面: 【Aspect】: 是指切入点 和通知(引介) 的 结合

**代理 : 【Proxy】 **一个类被aop增强后,所产生一个结果的代理类

切入点

  1. **切入点表达式: **

image-20230320191539415

举例:

image-20230320191636878

  1. 切点函数(用的不多)

我们可以在要增强的方法上加上注解。然后使用@annotation来表示对加了什么注解的方法进行增强。

image-20230320192100758

1
2
3
4
5
6
7
8
9
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {

String businessName();
}
  1. 在自定义注解上需要加

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

  1. 在需要被注解增强的相关方法上加上我们自定义的注解的注解名就可以实现增强
  2. 在切面类上进行添加切入点等信息

image-20230320192458549

通知

几种通知的分类

image-20230320192755168

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package tech.pdai.springframework.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
* @author pdai
*/
public class LogAspect {

/**
* 环绕通知.
*
* @param pjp pjp
* @return obj
* @throws Throwable exception
*/
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法");
return o;
}

/**
* 前置通知.
*/
public void doBefore() {
System.out.println("前置通知");
}

/**
* 后置通知.
*
* @param result return val
*/
public void doAfterReturning(String result) {
System.out.println("后置通知, 返回值: " + result);
}

/**
* 异常通知.
*
* @param e exception
*/
public void doAfterThrowing(Exception e) {
System.out.println("异常通知, 异常: " + e.getMessage());
}

/**
* 最终通知.
*/
public void doAfter() {
System.out.println("最终通知");
}

}

环绕通知

如何使用 ?

1
2
3
4
5
6
7
8
9
@Around("pt()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法前");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法后");
return o;
}

注意点 : 如果目标方法有返回值 ,那么对应的切面类中的环绕通知的方法也必须有返回值

AspectJ注解方式

基于XML的声明式AspectJ存在一些不足,需要在Spring配置文件配置大量的代码信息,为了解决这个问题,Spring 使用了@AspectJ框架为AOP的实现提供了一套注解。

注解名称 解释
@Aspect 用来定义一个切面。
@pointcut 用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。
@Before 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。
@AfterReturning 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。
@Around 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
@After-Throwing 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。
@After 用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。


获取被增强方法相关信息

我们实际对方法进行增强时往往还需要获取到被增强方法的相关信息,比如 : 方法名,参数,返回值,异常信息….

我们除了在环绕通知外的所有通知方法中增加一个JoinPoint类型的参数。这个参数封装了被增强方法的相关信息 。我们还可以通过这个参数获取到除了异常对象 和返回值 之外的所有信息。

image-20230320194616436

image-20230320195321351

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


//对于controller层的方法增强
private void handleBefore(ProceedingJoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//获取被增强方法上的注解对象
SystemLog systemLog = getSystemLog(joinPoint);
log.info("============Start============");
// 打印请求 URL
log.info("URL : {}",request.getRequestURL());
// 打印描述信息
log.info("BusinessName : {}",systemLog.businessName());
// 打印 Http method
log.info("HTTP Method : {}",request.getMethod());
// 打印调用 controller 的全路径以及执行方法
log.info("Class Method : {}.{}",joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
// 打印请求的 IP
log.info("IP : {}",request.getRemoteHost());
// 打印请求入参
log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs()) );

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//对于service层的方法增强
private void handleBefore(ProceedingJoinPoint joinPoint) {
String className = joinPoint.getSignature().getDeclaringTypeName();
System.out.println("获取 类名: " + className);

String methodName = joinPoint.getSignature().getName();
System.out.println("获取方法名 : " + methodName);

Object[] args = joinPoint.getArgs();
System.out.println("获取参数;" + Arrays.toString(args));
System.out.println("获取参数(转为json格式);" + JSON.toJSONString(args));


}

异常

image-20230320202111363

多切面增强顺序

通过@Order的大小来规定多个切面的顺序

image-20230320203705453

参考学习说明:

视频学习 : 三更springAOP

相关资料参考 :https://pdai.tech/md/spring/spring-x-framework-aop.html