一、简介
1.1 概念
- 是什么
- AOP(Aspect Oriented Programming)面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序统一添加功能的一种技术。
- AOP 的编程思想就是把很多类对象中的横切问题点从业务逻辑中分离出来,从而达到解耦的目的、增加代码的重用性、提高开发效率。
- OOP 很多类对象和 Logger 仍然存在依赖关系,如果修改了方法需要对应修改所有的依赖。
- 应用场景
- 日志记录
- 权限验证
- 事务处理
- 效率检查
- 异常处理
- 缓存处理
- 数据持久化
- 内容分发
1.2 切面
Spring AOP 底层实现是通过 JDK 动态代理或 CGLib 代理在运行时期在对象初始化阶段织入代码的;JDK 动态代理基于接口实现,CGLib 是基于类的继承实现。
-
aspect
:切面,由切点和通知组成,既包括横切逻辑的定义也包括连接点的定义 -
pointcut
:切点,每个类都拥有多个连接点,可以理解是连接点的集合 -
joinpoint
:连接点,程序执行的某个特定位置,如某个方法调用前后 -
weaving
:织入,将增强添加到目标类的具体连接点的过程 -
advice
:通知,是织入到目标类连接点上的一段代码,就是增强到什么地方?增强什么内容? -
target
:目标对象,通知织入的目标类 -
aop proxy
:代理对象,即增强后产生的对象
1.3 通知
-
Before Advice
:前置通知,即在目标方法调用之前执行。注意:即无论方法是否异常都执行 -
After returning advice
:后置通知,在目标方法执行后执行,前提是目标方法没有遇到异常 -
After throwing advice
:异常通知,在目标方法抛出异常时执行,可以获取异常信息 -
After finnally advice
:最终通知,在目标方法执行后执行,无论是否发生异常 -
Around Advice
:环绕通知,最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinPoint.proceed()
),可以在目标执行的全过程中进行执行。
二、Hello AOP
- 思路
- 实现一个切面类 Aspect:即在声明的类上增加 @Component、@Aspect 两个注解(Spring Boot 中要引入 spring-boot-starter-aop 依赖)
- 定义切点 Pointcut:定义切点并定义切点在哪些地方执行,采用 @Pointcut 注解完成,如
@Pointcut("public * com.xxx.xxx.*.*(..)")
;规则:修饰符(可以不写,但不能用*
)+ 返回类型(可以用*
)+ 包 + 类(可以用*
)+ 方法(可以用*
)+ 参数(可以用..
) - 定义通知 Advice:利用通知的 5 种注解类型 @Before、@After、@AfterReturning、@AfterThrowing、@Around 来完成在某些切点的增强动作。如
@Before("myPointcut()")
,其中 myPointcut 为步骤 2 中定义的切点
- 步骤
- 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 定义目标类
package com.simwor.aop.controller;
@RestController
public class AopController {
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return "Hello, " + name;
}
@GetMapping("/hi")
public String hi(@RequestParam String name, @RequestParam int age) {
return "hi " + name + ", age = " + age;
}
}
- 实现切面
@Aspect
@Component
@Slf4j
public class MyAspect {
@Pointcut(value = "execution( * com.simwor.aop.controller.*.*(..))")
public void myPointCut() { }
@Around("myPointCut()")
public Object myLogger(ProceedingJoinPoint pjp) throws Throwable {
String className = pjp.getTarget().getClass().toString();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
ObjectMapper objectMapper = new ObjectMapper();
// 相当于 @Before
log.info("AOP : before invoking classname = " + className + ", method name = " + methodName + ", args = " + objectMapper.writeValueAsString(args));
Object obj = pjp.proceed();
// 相当于 @After
log.info("AOP : after invoking classname = " + className + ", method name = " + methodName + ", return = " + objectMapper.writeValueAsString(obj));
return obj;
}
}
- 运行观察效果
... com.simwor.aop.MyAspect : AOP : before invoking classname = class com.simwor.aop.controller.AopController, method name = hello, args = ["rayslee"]
... com.simwor.aop.MyAspect : AOP : after invoking classname = class com.simwor.aop.controller.AopController, method name = hello, return = "Hello, rayslee"
... com.simwor.aop.MyAspect : AOP : before invoking classname = class com.simwor.aop.controller.AopController, method name = hi, args = ["rayslee",18]
... com.simwor.aop.MyAspect : AOP : after invoking classname = class com.simwor.aop.controller.AopController, method name = hi, return = "hi rayslee, age = 18"