目录
🚩Spring AOP概述
Spring框架的第一大核心是IOC(控制反转),接下来我们学习第二大核心——AOP.
拦截器作⽤的维度是URL(⼀次请求和响应), @ControllerAdvice 应⽤场景主要是全局异常处理 (配合⾃定义异常效果更佳), 数据绑定, 数据预处理.
AOP作⽤的维度更加细致(可以根据包、类、⽅法 名、参数等进⾏拦截), 能够实现更加复杂的业务逻辑.
一个项目需要开发很多的业务功能,有一些业务的执行效率是很低的,需要对接口进行优化。第一步就需要定位出执行耗时比较长的业务方法,然后针对该业务方法进行优化。如何定位呢?我们就需要统计当前项目中每一个业务方法的执行耗时,我们需要在每个业务方法运行前和运行后,记录下方法的开始时间和结束时间,两者之差就是这个方法的耗时。但是一个项目中有很多的方法和类,我们需要记录每个方法的执行时长,需要在每个方法中记录,这种执行效率是比较低的,耗时比较长,
这种⽅法是可以解决问题的, 但⼀个项⽬中会包含很多业务模块, 每个业务模块⼜有很多接⼝, ⼀个接⼝ ⼜包含很多⽅法, 如果我们要在每个业务⽅法中都记录⽅法的耗时, 对于程序员⽽⾔, 会增加很多的⼯作量。
🚩Spring AOP快速⼊⻔
需求: 统计图书系统各个接⼝⽅法的执⾏时间
🎓引入AOP依赖
在pom.xml⽂件中添加配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
🎓编写AOP程序
记录Controller中每个⽅法的执⾏时间
@Slf4j
@Component
@Aspect
public class TimeRecordAspect {
/**
* 记录时间
*/
@Around("execution(* com.example.cl.controller.*.*(..))")//对哪些方法生效
//@Around表示侵入程序的时机,表示环绕,目标函数执行前后都会执行
public Object TimeRecord(ProceedingJoinPoint joinPoint) throws Throwable {
//记录开始时间
long start=System.currentTimeMillis();//目标函数执行前
//执行目标方法
Object proceed=joinPoint.proceed();//执行目标函数
//记录结束时间
long end=System.currentTimeMillis();//目标函数执行后
//日志打印耗时时间
log.info("耗时时间:"+(end-start)+" ms");
return proceed;
}
//AOP这种写法作用的维度是 方法
//拦截器 作用的维度是 url 接口
}
在进行用户登录,我们看到该方法耗时时间:234ms.
🚩Spring AOP 详解
🎓Spring AOP核⼼概念
🎓通知类型
上⾯我们讲了什么是通知, 接下来学习通知的类型.
@Around 就是其中⼀种通知类型, 表⽰环绕通知.
@Aspect
@Slf4j
public class AspectDemo {
/**
* 前置通知
*/
@Before("execution(* com.cl.springaop.controller.*.*(..))")
public void doBefore(){
log.info("AspectDemo do before......");
}//是否发生异常,都会执行(再执行方法前)优先级小于around
/**
* 后置通知
*/
@After("execution(* com.cl.springaop.controller.*.*(..))")
public void doAfter(){
log.info("AspectDemo do after......");
}//是否发生异常,都会执行(再执行方法后) 优先级小于around
/**
*添加环绕通知
*/
//around必须有返回值,需要将目标返回值返回,否则会没有返回值
@Around("execution(* com.cl.springaop.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AspectDemo do around before......");
Object result=null;
result=joinPoint.proceed();
log.info("AspectDemo do around after.......");
return result;
}//发生异常就不会返回结果
/**
* 返回后通知
*/
@AfterReturning("execution(* com.cl.springaop.controller.*.*(..))")
public void doAfterReturning(){
log.info("AspectDemo do AfterReturning......");
}//没有异常执行
/**
* 抛出异常后通知
*/
@AfterThrowing("execution(* com.cl.springaop.controller.*.*(..))")
public void doAfterThrowing(){
log.info("AspectDemo do AfterThrowing......");
}//有异常执行
}
写一些测试程序:
@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1(){
log.info("执行t1方法");
return "t1";
}
@RequestMapping("/t2")
public String t2(){
log.info("执行t2方法.......");
int a=10/0;
return "t2";
}
}
🎓@PointCut
上⾯代码存在⼀个问题, 就是存在⼤量重复的
切点表达式 execution(* com.example.demo.controller.*.*(..))
Spring提供了 @PointCut 注解, 把公共的切点表达式提取出来, 需要⽤到时引⽤该切⼊点表达式即可.
@Pointcut("execution(* com.cl.springaop.controller.*.*(..))")
public void pt(){};
@Pointcut("execution(* com.cl.springaop.controller.*.*(..))")
public void pt(){};
/**
* 前置通知
*/
@Before("pt()")
public void doBefore(){
log.info("AspectDemo do before......");
}//是否发生异常,都会执行(再执行方法前)优先级小于around
/**
* 后置通知
*/
@After("pt()")
public void doAfter(){
log.info("AspectDemo do after......");
}//是否发生异常,都会执行(再执行方法后) 优先级小于around
🎓切⾯优先级 @Order
当我们在⼀个项⽬中, 定义了多个切⾯类时, 并且这些切⾯类的多个切⼊点都匹配到了同⼀个⽬标⽅法.当⽬标⽅法运⾏的时候, 这些切⾯类中的通知⽅法都会执⾏, 那么这⼏个通知⽅法的执⾏顺序是什么样的呢?
定义多个切⾯类:
@Order 控制切⾯的优先级, 先执⾏优先级较⾼的切⾯, 再执⾏优先级较低的切⾯, 最终执⾏⽬标⽅法.
🎓切点表达式
📝execution表达式
execution() 是最常⽤的切点表达式, ⽤来匹配⽅法, 语法为:
其中: 访问修饰符和异常可以省略
📝@annotation
execution表达式更适⽤有规则的,
如果我们要匹配多个⽆规则的⽅法呢, ⽐如:TestController中的t1() 和UserController中的u1()这两个⽅法. 这个时候我们使⽤execution这种切点表达式来描述就不是很⽅便了.
我们可以借助⾃定义注解的⽅式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点,来指定方法
UserController
@Component
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
@RequestMapping("/u1")
public String u1(){
log.info("执行u1方法");
return "u1";
}//由于没有引入注解,所以没有
@RequestMapping("/u2")
public String u2(){
log.info("执行u2方法");
return "u2";
}
}
TestController
@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1(){
log.info("执行t1方法");
return "t1";
}
@RequestMapping("/t2")
public String t2(){
log.info("执行t2方法.......");
int a=10/0;
return "t2";
}
}
🎈⾃定义注解 @MyAspect
创建⼀个注解类(和创建Class⽂件⼀样的流程, 选择Annotation就可以了)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAspect {
}
🎈切⾯类
使⽤ @annotation 切点表达式定义切点, 只对 @MyAspect ⽣效
@Aspect
@Component
@Slf4j
public class MyAspectDemo {
@Around("@annotation(com.cl.springaop.config.MyAspect)")
public Object doAround(ProceedingJoinPoint joinPoint) {
log.info("MyAspectDemo do around before.....");
Object result= null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
log.error("发生异常,e:"+e);
}
log.info("MyAspectDemo do around after.....");
return result;
}
}
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object doAround2(ProceedingJoinPoint joinPoint) {
log.info("RequestMapping do around before.....");
Object result= null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
log.error("发生异常,e:"+e);
}
log.info("RequestMapping do around after.....");
return result;
}
🎈添加⾃定义注解
在TestController中的t1()和UserController中的u1()这两个⽅法上添加⾃定义注解 @MyAspect , 其
他⽅法不添加
对testController中的t1测试
对testController中的t2测试
没有打印结果。
🚩Spring AOP的实现⽅式(常⻅⾯试题)