1、AOP概述
AOP:AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
场景:案例部分功能运行较慢,定位执行耗时较长的方法,此时需要统计每一个业务方法的执行耗时。
模板方法:即将计算业务执行的时间的功能抽取为一个模板,这个模版可以计算每一个业务的运行时间。
实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层动态代理机制,对特定的方法进行编程。
2、AOP快速入门
2.1 导入依赖
在pom.xml中导入AOP的依赖
<!--aop相关依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.2 编写AOP程序
针对于特定的方法根据业务需要进行编程,即创建一个相关类,将此类通过注解 @Component 声明为IOC容器的Bean对象,通过 @Aspect 告知项目此类为AOP类。
package com.itheima.tliaswebmanagement.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j //日志注解 @Component //声明为IOC容器的Bean对象 @Aspect //告知项目此类为AOP类 public class TimeAspect { @Around("execution(* com.itheima.tliaswebmanagement.service.*.*(..))" ) //切入点表达式 public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { //1.记录开始时间 long begin = System.currentTimeMillis(); //2.运行原始方法 Object result = joinPoint.proceed(); //3.记录结束时间,计算方法执行耗时 long end = System.currentTimeMillis(); log.info(joinPoint.getSignature() + "方法执行耗时: {}ms",end-begin); return result; } }
2.3 AOP核心概念
连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
通知:Advice,指哪些重复的逻辑,也就是共性功能
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,通知与切入点的对应关系
目标对象:Target,通知所应用的对象
3、AOP进阶
3.1 通知类型
@Around:环绕通知,此注解标注的通知方法在目标前、后都被执行
@Before:前置通知,此注解标注的通知方法在目标前被执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会被执行
@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
(源代码如下)
package com.itheima.tliaswebmanagement.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class MyAspect1 { //解决代码冗余问题 @Pointcut("execution(* com.itheima.tliaswebmanagement.service.impl.DeptServiceImpl.*(..))") private void pt(){} @Before("pt()") public void before(){ log.info("before..."); } @Around("execution(* com.itheima.tliaswebmanagement.service.impl.DeptServiceImpl.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around before ..."); //调用目标对象的原始方式执行 Object result = proceedingJoinPoint.proceed(); log.info("around after ..."); return result; } @After("execution(* com.itheima.tliaswebmanagement.service.impl.DeptServiceImpl.*(..))") public void after(){ log.info("after ..."); } //原始方法正常的执行并且返回 @AfterReturning("execution(* com.itheima.tliaswebmanagement.service.impl.DeptServiceImpl.*(..))") public void afterReturn(){ log.info("afterReturn ..."); } //原始方法返回异常 @AfterThrowing("execution(* com.itheima.tliaswebmanagement.service.impl.DeptServiceImpl.*(..))") public void afterThrowing(){ log.info("afterThrowing ..."); } }
3.2 通知顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行,那么切面的切入点方法执行顺序是怎样的?
答案:
没有注解;首先是根据切面类的类名排序。
有注解@Order(数字);根据注解的数字大小排序。
图3.2-1 注解@Order(数字) |
---|
3.3 切入点表达式
切入点表达式:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
1. execution(...) :根据方法的签名来匹配
2. @annotation(...) :根据注解匹配
3.3.1 切入点表达式-execution
图3.3.1-1 切入点表达式 |
---|
图3.3.1-2 切入点表达式通配符 |
---|
3.3.2 切入点表达式-annotation
图3.3.2-1 切入点表达式 |
---|
3.4 连接点
在Spring中用JoinPoint抽象了连接点,用它可以获取方法执行时的相关信息,例如目标类名、方法名、方法参数等。
1. 对于@Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
2. 对于其他四种通知,获取连接点信息只能使用 JointPoint,它是ProceedingJoinPoint 的父类型
图3.4-1 连接点项目代码 |
---|
上图使用了参数:ProceedingJoinPoint 的各种接口,用来获取目标对象的各种信息,具体的介绍在图中已经做了注解。