一、AOP(面向切面编程)
切面是指某一个特定的问题,AOP就是面向特定的方法编程,例如登录拦截器,统一数据返回,统一异常处理,都是使用AOP思想。AOP是Spring框架的第二大核心。
二、Spring AOP也是AOP的一种实现
编写AOP程序,记录Controller每个方法执行的时间
切点(Pointcut):
切点就是一组规则告诉程序哪些方法进行功能加强
切点表达式(例如代码中):![]()
连接点:
满足切点表达式的方法就是连接点
路径下的方法都是连接点
通知( Advice)
通知就是具体要做的工作,即共同重复的逻辑,例如记录多个方法的耗时
切面:(切点+通知)
即:
切面类:
切面所在的类,即被@Aspect修饰的类
通知类型
1. @Before 前置通知
执行时机:目标方法执行之前运行
2. @AfterReturning 后置返回通知
执行时机:目标方法正常执行完毕、成功返回结果后,异常不执行
3. @AfterThrowing 异常通知
执行时机:目标方法抛出异常时执行
4. @After 最终通知
执行时机:无论目标方法是正常结束还是抛出异常,该通知都会在最后执行。
5. @Around 环绕通知(功能最强)
执行时机:包裹目标方法,手动控制方法执行
package com.bite.springaopdemo.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Slf4j public class AspectDemo1 { //添加环绕通知 @Around("execution(* com.bite.springaopdemo.controller.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp)throws Throwable{//pjp指的是连接点对象 //记录方法开始时间 long begin=System.currentTimeMillis(); log.info("Around方法开始执行"); //执行原始方法 Object result=pjp.proceed(); //记录方法执行结束时间 long end=System.currentTimeMillis(); log.info(pjp.getSignature()+"执行耗时{}ms",end-begin);//getSignature()被拦截对象的类名方法名 log.info("Around方法执行结束"); return result; } //前置通知 @Before("execution(* com.bite.springaopdemo.controller.*.*(..))") public void doBefore(){ log.info("执行before方法"); } //后置通知 @After("execution(* com.bite.springaopdemo.controller.*.*(..))") public void doAfter(){ log.info("执行after方法"); } // //返回后通知 @AfterReturning("execution(* com.bite.springaopdemo.controller.*.*(..))") public void doAfterReturning(){ log.info("执行afterReturning方法"); } //抛出异常后通知 @AfterThrowing("execution(* com.bite.springaopdemo.controller.*.*(..))") public void doafterThrowing(){ log.info("执行AfterThrowing方法"); } }测试接口:
package com.bite.springaopdemo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public Integer t1(){ System.out.println("执行方法"); return 1; } }出现异常时候:
@afterReturning标识方法不会执行,@AfterThrowing标识的通知执行
@Around环绕方法有异常的时候,环绕后的代码不会在执行了
@PointCut将公共代码提取出来
没有规定切面类的优先级的时候:
1、@Before(目标方法执行之前)
多个切面的前置通知,按照切面类名称字典顺序执行: Demo1 排在前面 → Demo2 → Demo3,越靠后的切面,Before 越晚执行。
2、@After(目标方法执行之后,最终通知)
Spring AOP 采用栈结构执行:先进后出。 想象成套盒子: 先套上 Demo1 盒子 → 再套 Demo2 盒子 → 最后套 Demo3 盒子; 执行完目标方法之后,拆盒子要从最外层开始拆: 先拆掉最外面的 Demo3 → 再拆 Demo2 → 最后拆 Demo1。
所以: Before 入栈顺序:1 → 2 → 3 After 出栈顺序:3 → 2 → 1
可以使用注解:
@Order(优先级数值)数值越小,优先级越高,执行越早。
切点表达式(具体)
1.execution
execution(访问修饰符? 返回值类型 包路径.类名.方法名(参数列表) 异常抛出?)
@Pointcut("execution(* com.bite.springaopdemo.controller.*.*(..))")
*匹配任意单个内容..匹配多层包 / 任意个参数
2.annotation
1.创建有一个注解类:
2.切面类:(定义切点只对@MyAspect生效)
3.添加自定义注解
4.测试结果
Spring AOP实现方式:
1.基于注解@Aspect
2.基于自定义注解
3.基于Spring API(通过xml配置的方式)
4.基于代理实现
三、Spring AOP原理:
基于动态代理实现的,分为
1.代理模式
提供一个代理类,让我们调用目标方法的时候可以通过目标方法简介调用
根据代理创建的时期分为静态代理和动态代理:
静态代理:程序运行前,代理类的.class文件就已经存在
动态代理:不需要为每个对象目标创建一个代理对象,而是把代理对象的工作推迟到程序运行时,由JVM实现,即”业务来了再创建“
java对动态代理进行了实现,常见实现方式时JDK动态代理CGLIB动态代理
CGLIB和JDK区别:
1.CGLIB能够代理接口和类,JDK只能代理接口
2.据说CGLIB的性能更高
StringAOP 使用的是 JDK 还是 CGlib 什么时候使用 JDK 什么时候使用 CGlib
proxyTargetClass是 Spring AOP 的一个配置项
proxyTargetClass 值 目标类情况 使用代理方式 false 实现了接口 JDK 代理 false 没有实现接口(只有类) CGLIB 代理 true 实现了接口 CGLIB 代理 true 没有实现接口 CGLIB 代理
proxyTargetClass=false(默认旧版本规则):优先 JDK 代理(类实现接口才用 JDK,没接口自动用 CGLIB);proxyTargetClass=true:强制全部使用 CGLIB 代理,不管这个类有没有实现接口。三、版本差异重点(SpringBoot2.x 和 3.x 不一样)
- Spring Framework 原生框架:
proxyTargetClass=false默认值;- SpringBoot 2.x 版本:默认也是
false,优先 JDK 代理;- SpringBoot 3.x 默认值改成 trueSpringBoot3 全局默认强制使用 CGLIB 代理,不管你的 Bean 有没有实现接口,统一 CGLIB。
这就是为什么你用 SpringBoot3 做 AOP 的时候,不需要写接口也能正常切面拦截,SpringBoot2 不写接口就会报错。