AOP:面向切面的编程。
AOP并不是Spring特有的技术,只是Spring框架很好的支持了AOP。
AOP解决了横切关注的问题,所谓的横切关注,就是执行多个不同的方法时,都需要执行相同的代码片段。
例如,当前项目中的数据处理流程为:
登 录: 客户端 ---请求---> Controller ------> Sevice ------> Mapper
添加相册:客户端 ---请求---> Controller ------> Sevice ------> Mapper
修改品牌:客户端 ---请求---> Controller ------> Sevice ------> Mapper
假设,存在需求:统计各Service方法的执行耗时。
要实现以上需求,首先,在项目中添加spring-boot-starter-aop
依赖项:
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
并编写切面代码:
@Slf4j
@Aspect // 将当前类标记为“切面”类
@Component // 将当前类标记为“组件”类
public class TimerAspect {
public TimerAspect() {
log.debug("创建切面对象:TimerAspect");
}
// 【AOP的核心概念】
// 连接点(JoinPoint):数据处理过程中的某个节点,可能是调用了方法,或抛出了异常
// 切入点(PointCut):选择1个或多个连接点的表达式
// ---------------------------------------------------------
// 【通知注解】
// @Before:表示“在……之前”,方法应该是无参数的
// @After:表示“在……之后”,无论顺利执行结束,还是抛出异常,都会执行,方法应该是无参数的
// @AfterReturning:表示“在返回结果之后”,方法的参数是JoinPoint和返回值对象
// @AfterThrowing:表示“在抛出异常之后”,方法的参数是JoinPoint和异常对象
// @Around:表示“包裹”,也称之“环绕”,使用此注解时,方法的参数必须是ProceedingJoinPoint类型的
// ---------------------------------------------------------
// @Around开始
// try {
// @Before
// 连接点方法
// @AfterReturning
// } catch (Throwable e) {
// @AfterThrowing
// } finally {
// @After
// }
// @Around结束
// ---------------------------------------------------------
// 注解中的execution内部配置表达式,以匹配需要在哪些方法上执行切面代码
// 在表达式中,星号(*)是通配符,可以匹配1次任意内容
// 例如,在返回值位置使用星号,表示此方法可以是任意返回值,包括:void / int / String ...
// 在表达式中,连续的2个小数点(..)也是通配符,可以匹配0~n次任意内容,仅能用于包名和参数列表部分
// 例如,在方法的参数列表使用2个小数点,表示此方法的参数列表中可以是0个参数,也可以是n个参数
@Around("execution(* cn.tedu.csmall.product.service.*.*(..))")
// ↑ 此星号表示方法的返回值类型
// ↑----------- 根包 ------------↑
// ↑ 类名
// ↑ 方法名
// ↑↑ 参数列表
// 提示:在“返回值类型”的左侧,还可以配置修饰符,例如方法的注解等,修饰符是可选的配置
public Object timer(ProceedingJoinPoint pjp) throws Throwable {
log.debug("执行了TimerAspect中的方法……");
log.debug("【{}】类型的对象调用了【{}】方法,传入的参数为【{}】",
pjp.getTarget().getClass().getName(),
pjp.getSignature().getName(),
pjp.getArgs());
long start = System.currentTimeMillis();
// 调用ProceedingJoinPoint的proceed()方法,本质就是执行了连接点对应的方法
// 注意:调用的proceed()方法会抛出Throwable,此时必须抛出,不可以使用try...catch捕获并处理
// 注意:调用proceed()方法时必须获取返回值,此返回值就是连接点对应的方法的返回值,必须作为当前切面方法的返回值
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.debug("执行耗时:{}ms", end - start);
return result;
}
}