AOP
1、AOP的概念和简介
1.1、AOP简介
- AOP面向切面编程,一种编程范式,指导开发者如何组织程序结构
- oop面向对象编程
- 作用:在不惊动原始设计的基础上为其进行进行能增强
- Spring理念:无入式侵入/无侵入式
1.2、AOP核心概念
- 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点:匹配连接点的式子
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法:com.anle.dao包下的BookDao接口中的无形参无返回值的sho方法
- 匹配多个方法:所有的sho方法,所有的get开头的方法,所有Dao接口结尾的接口中的任意方法,所有带一个参数的方法
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知:在切入点执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面:描述通知与切入点的对应关系
2、AOP入门案例
-
第一步:导坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> </dependency>
-
第二步:定义dao接口与实现类
/** * @Author: AnLe * @Date: 2023/04/01/10:58 */ public interface BookDao { public void sho(); public void update(); }
/** * @Author: AnLe * @Date: 2023/04/01/10:59 */ @Repository("bookDao") public class BookDaoImpl implements BookDao { @Override public void sho() { System.out.println(System.currentTimeMillis()); System.out.println("book,,,dao,,,,sho"); } @Override public void update() { System.out.println("book...dao...update"); } }
-
第三步:定义通知类,制作通知
public class MyAdvice { public void method(){ System.out.println(System.currentTimeMillis()); } }
-
定义切入点
public class MyAdvice { //定义切入点 @Pointcut("execution(void com.anle.aop.dao.BookDao.update())") public void qrd(){ } }
- 说明:切入点定义依托一个不具有实际意义的方法进行,既无参数,无返回值,方法体无实际逻辑
-
第五步:绑定切入点与通知关系,并指定通知到原始连接点的具体执行位置
/**
* @Author: AnLe
* @Date: 2023/04/01/16:21
*/
public class MyAdvice {
//定义切入点
@Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
public void qrd(){
}
@Before("qrd()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 第六步:定义通知类受spring容器管理,并定义当前类为切面类
/**
* @Author: AnLe
* @Date: 2023/04/01/16:21
*/
@Component
@Aspect
public class MyAdvice {
//定义切入点
@Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
public void qrd(){
}
@Before("qrd()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 第七步:开启spring对AOP注解驱动支持
/**
* @Author: AnLe
* @Date: 2023/04/01/11:02
*/
@Configuration
@ComponentScan("com.anle")
@EnableAspectJAutoProxy
public class SpringConfig {
}
3、AOP的工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 初始化失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean的执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,更具代理对象的运行模式运行原始方法与增强的能容,完成操作。
3.1、AOP核心概念
- 目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象是无法完成最终工作的
- 代理:目标对象无法直接完成对应的工作,需要对其进行功能进行回填,通过原始对象的代理对象实现
4、AOP切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述方法
4.1、语法格式
-
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
@Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
- 动作关键字:描述切入点的动作,列如execution表示执行到指定切入点
- 访问修饰符:public , private等,可以省略
- 返回值
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出异常,可以省略
4.2、通配符
-
可以使用通配符描述切入点,可以快速描述
-
*:单个独立的任意字符,可以独立出现,也可以作为前缀或者后缀的匹配符出现
@Pointcut("execution(public * com.anle.*.UserService.u*(*))")
匹配com.anle包下的任意包中的UserService类接口中所有名称以u开头的方法
-
… :表示多个连接的任意符号,可以独立出现,常用于简化包名与参数的书写
@Pointcut("execution(public User com..UserService.update(..))")
匹配com包下的任意包中的UserService类接口中所有名称以update的方法
-
+:专用于匹配子类类型
@Pointcut("execution(* *..Service+.*(..))")
-
4.3、书写技巧
- 书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发采用:public描述(可以省略控制访问修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配类型快速描述
- 包名书写尽量不使用 … 匹配 ,效率过低,常用* 作为单包匹配,或者精准匹配
- 接口名/类名书写名称与模块相关的采用*匹配,类如:UserService书写成 * Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用*匹配,类如:getById书写成getBy * ,selectAll写成selectAll
- 参数规则较为复杂,根业务方法进行调整
- 通常不使用异常作为匹配规则
5、AOP通知类型
5.1、AOP通知类型
- AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要加入到合理的位置
- AOP通知共分为5种类型
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回后通知(了解)
- 抛出异常后通知(了解)
5.2、类型介绍
-
名称:@Before
-
类型:方法注解
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
-
范例:
//前置通知 @Before("pt()") public void before(){ System.out.println("before....advice"); }
-
名称:@Around(重点,常用)
-
类型:方法注解
-
位置:通知方法定义与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
-
范例:
//环绕通知 @Around("pt()") public void around(ProceedingJoinPoint point) throws Throwable { System.out.println("before....advice"); //表示对原始数据的操作 Object proceed = point.proceed(); System.out.println("after....advice"); }
-
@Around注意事项
- 环绕通知必须依靠形参ProceedingJoinPoint才能实习对原始方法的调用,进而实现原始方法调用前后同时增加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
- 原始方法的调用可以不接受返回值,通知方法设置成void即可,如果接收返回值,必须设定为object类型
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成object
- 由于无法预知原始方法运行后台是否会抛出异常,因此环绕通知方法必须抛出 Throwable对象
-
名称:@AfterReturning(了解)
-
类型:方法注解
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当通知方法在原始切入点方法政策执行完毕后运行
-
范例:
@AfterReturning("pt2()") public void afterReturning(){ System.out.println("afterReturning....advice"); }
-
相关属性:value(默认) :切入点方法名 , 格式为类名.方法名()
-
名称:@AfterThrowing(了解)
-
类型:方法注解
-
位置:通知方法定义上方
-
作用:设置通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
-
范例:
//抛出异常后在执行 @AfterThrowing("pt2()") public void afterThrowing(){ System.out.println("afterThrowing....advice"); }
-
相关属性:value(默认):切入点方法名,格式为类名.方法名()
6、AOP通知获取数据
6.1、获取参数
-
获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedJoinPoint:适用于环绕通知
-
JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()") public void before(JoinPoint jp){ Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("before......."); }
-
ProceedJoinPoint是JoinPoint的子类
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); Object ref = pjp.proceed(args); return ref; }
6.2、获取返回值
-
获取切入点的返回值
- 返回后通知
- 环绕通知
-
抛出异常后通知可获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret){
System.out.println("afterReturning....advice");
}
-
环绕通知可手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ref = pjp.proceed(args); return ref; }
6.3、获取异常
-
获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
-
抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value = "pt()",throwing = "t") public void afterThrowing(Throwable t){ System.out.println("afterThrowing....advice"); }
-
抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ref = null;
try {
ref = pjp.proceed(args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return ref;
}
owable t){
System.out.println(“afterThrowing…advice”);
}
- 抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收运行时抛出的异常对象
```Java
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ref = null;
try {
ref = pjp.proceed(args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return ref;
}