什么是AOP

AOP (Aspect Oriented Programming),意为: 面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP的编程思想就是把很多类对象中的横切问题点,从业务逻辑中分离出来,从而达到解耦的目的增加代码的重用性,提高开发效率。

传统的日志写法

没学过Aop之前在做日志管理的时候通常是自定义一个Logger类然后在其他模块中调用它,如图所示:

log

但是这会产生一些问题

  • 很多类对象和Logger仍然存在依赖关系
  • 甚至每个类中的所有方法都需要调用Logger
  • 如果修改了方法,需要修改N遍,不能一次修改完成改变

AOP的应用场景

  • 日志记录
  • 权限验证
  • 事务处理
  • 效率检查
  • 异常处理
  • 缓存处理
  • 数据持久化
  • 内容分发

AOP 中主要概念理解

  1. aspect: 切面,切面有切点和通知组成,即包括横切逻辑的定义也包括连接点的定义(是一个逻辑概念,通常是一个切面类)
  2. pointcut: 切点,每个类都拥有多个连接点,可以理解是连接点的集合
  3. joinpoint: 连接点,程序执行的某个特定位置,如某个方法调用前后等
  4. weaving: 织入,将增强添加到目标类的具体连接点的过程
  5. advice: 通知,是织入到目标类连接点上的一段代码,就是增强到什么地方? 增强什么内容?target: 目标对象,通知织入的目标类0
  6. aop Proxy: 代理对象,即增强后产生的对象
    Spring AOP 底层实现,是通过JDK动态代理或CGLib代理在运行时期在对象初始化阶段织入代码的。

advice的类型

  • Before advice
    前置通知,即在目标方法调用之前执行。注意: 即无论方法是否遇到异常都执行
  • After returning advice
    后置通知,在目标方法执行后执行,前提是目标方法没有遇到异常,如果有异常则不执行通知
  • After throwing advice
    异常通知,在目标方法抛出异常时执行,可以获取异常信息
  • After finally advice
    最终通知,在目标方法执行后执行,无论是否是异常执行。
  • Around advice
    环绕通知,最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinPoint.proceed()),可以在目标执行全过程中进行执行。

AOP的基本实现步骤

  1. 定义一个切面类Aspect
    即在声明的类,增加@Component @Aspect 两个注解,Springboot中要引入 spring-boot-stater-aop依赖包
  2. 定义切点 Pointcut
    定义切点,并定义切点在那些地方执行,采用@Pointcut注解完成,如@Pointcut(public * com.xxx.xxx.*.*(..))
    规则: 修饰符(可以不写,但不能用) + 返回类型 + 那些包下的类 + 那些方法 + 方法参数 `代表不限,..`两个点表示参数不限
  3. 定义Advice 通知
    利用通知的5种类型注解@Before、 @After、@AfterReturning、 @AfterThrowing、@Around来完成在某些切点的增强动作,如@Before(“myPointcut”),myPointcut为第二步骤定义的切点

具体案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
@Component
public class LoggingAdvice {
private Loggerlogger = LoggerFactory.getLogger(LoggingAdvice.class);
/*
切点名称: myPointcut,在返回类型不限的com.quanfon.sprint.aop.controller包下的所有类,所有方法,并且参数不限
*/
@Pointcut( value ="execution(* com.guanfon.sprint.aop.controller.*.*(..))")
public void myPointcut() {
}
//注意: ProceedingJointPoint只能用于环绕通知,因为ProceedingJointPoint暴露了proceed方法
@Around("myPointcut()")
public Object logAround(ProcedingJointPoint pjp) throws Throwable{
String methodName = pjp.getSignature()getName();
String className = pjp.getTarget().getClass().toString();
ObjectMapper m = new ObjectMapper();
Object[] array = pjp.getArgs();
logger.info("调用前:"+className+":"+methodName+"args="+m.writeValueAsString(array))
Object obj=proceedingJoinPoint.proceed();
logger.info("调用后:"+className+":"+methodName+"返回"+m.writeValueAsString(obj));
return obj;
}
}