网站制作服务合同,网站建设基础流程,北京网络安全公司,如何查找网站所有页面目录 AOP概述
什么是AOP#xff1f;
什么是Spring AOP ?
Spring AOP 快速入门
1.引⼊ AOP 依赖
2.编写AOP程序
Spring AOP 核心概念
1.切点
2.连接点
3.通知
4.切面
通知类型
注意事项:
PointCut#xff08;定义切点#xff09;
切面优先级 Order
切点表达…目录 AOP概述
什么是AOP
什么是Spring AOP ?
Spring AOP 快速入门
1.引⼊ AOP 依赖
2.编写AOP程序
Spring AOP 核心概念
1.切点
2.连接点
3.通知
4.切面
通知类型
注意事项:
PointCut定义切点
切面优先级 Order
切点表达式
execution表达式
切点表达式示例
annotation
1. 编写自定义注解
2. 使⽤ annotation 表达式来描述切点 AOP概述 AOP 是 Spring 框架的第⼆⼤核⼼(第⼀⼤核⼼是 IoC 推荐看Spring IoC DI 使⽤)
什么是AOP Aspect Oriented Programming⾯向切⾯编程 什么是⾯向切⾯编程呢? 切⾯就是指某⼀类特定问题,所以 AOP 也可以理解为⾯向特定⽅法编程. 什么是⾯向特定⽅法编程呢?⽐如登录校验,就是⼀类特定问题.登录校验拦截器,就 是对登录校验这类问题的统⼀处理推荐看拦截器使用详解.所以,拦截器也是 AOP 的⼀种应⽤. AOP 是⼀种思想,拦截器是 AOP 思想的⼀种实现. Spring 框架实现了这种思想, 提供了拦截器技术的相关接⼝. 同样的,统⼀数据返回格式和统⼀异常处理推荐看Spring Boot 统⼀数据返回格式, 也是 AOP 思想的⼀种实现. 简单来说: AOP是⼀种思想,是对某⼀类事情的集中处理. AOP的作⽤可以在不修改源代码的基础上对已有⽅法进⾏增强⽆侵⼊性:解耦
什么是Spring AOP ? AOP 是⼀种思想,它的实现⽅法有很多,有 Spring AOP也有AspectJ、CGLIB等. Spring AOP是其中的⼀种实现⽅式.
Spring AOP 快速入门
1.引⼊ AOP 依赖 在pom.xml ⽂件中添加配置
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency2.编写AOP程序
Aspect //标识这是一个切面类
Component //将 AspectDemo 的对象交给 Spring 进行管理切面类中的通知方法才能对指定的目标方法起作用
Slf4j
public class AspectDemo {//环绕通知Around//通知方法作用于目标方法执行前和执行后Around(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))// point 参数表示目标方法public Object around_AOP(ProceedingJoinPoint point) throws Throwable {log.info(执行 around_AOP 方法,目标方法前);//执行目标方法,并且要用一个变量接收目标方法的值然后再让通知方法返回Object resultpoint.proceed();log.info(执行 around_AOP 方法,目标方法后);return result;}
} 对于上述程序进行简单讲解 Aspect标识这是⼀个切⾯类 ComponentIoC 核心思想五大注解其中之一表示将 AspectDemo 这个切面类的对象交给 Spring 进行管理要加上这个注解切面类中的通知方法才会对指定的目标方法起作用 Slf4jlombok 工具类提供的注解为我们创建了一个日志对象 log 用于打印日志的 Around环绕通知,在⽬标⽅法的前后都会被执⾏.后⾯的表达式表⽰对哪些⽅法进⾏增强.指定目标方法 ProceedingJoinPoint 类型的对象 point 代表目标方法point.proceed() 代表执行目标方法
Spring AOP 核心概念
1.切点 切点(Pointcut), 也称之为切⼊点 Pointcut 的作⽤就是提供⼀组规则,告诉程序对哪些⽅法来进⾏功能增强简单来说切点就是通过表达式指定对哪些目标方法进行功能增强上⾯的表达式execution(* com.yulin.spring_aop_demo.Controller.*.*(..)) 就是切点表达式.
2.连接点 满⾜切点表达式规则的⽅法,就是连接点.简单来说每一个需要进行功能增强的目标方法都是连接点在上述代码中 com.yulin.spring_aop_demo.Controller 路径下的所有目标方法都是连接点 切点和连接点的关系连接点是满⾜切点表达式的元素.切点可以看做是保存了众多连接点的⼀个集合简单来说切点表示所有要进行功能增强的目标方法每个目标方法都是一个连接点
3.通知 通知就是具体要做的⼯作,指那些重复的逻辑也就是共性功能(最终体现为⼀个⽅法)简单来说我们想要为目标方法添加的新功能就是通知新功能的代码写在一个方法中这个方法叫通知方法
4.切面 切⾯(Aspect) 切点 (Pointcut) 通知(Advice) 通过切⾯就能够描述当前 AOP 程序需要针对于哪些⽅法,添加哪些新的功能如下图一个完整的通知方法就是一个切面通知方法所在的类就叫切面类 通知类型 上⾯我们讲了什么是通知,接下来学习通知的类型.Around 就是其中⼀种通知类型,表⽰环绕通知. Spring AOP的通知类型有以下⼏种: • Around:环绕通知,此注解标注的通知⽅法在⽬标⽅法前,后都被执⾏ • Before:前置通知,此注解标注的通知⽅法在⽬标⽅法前被执⾏ • After: 后置通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,⽆论是否有异常都会执⾏ • AfterReturning: 返回后通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,有异常不会执⾏ • AfterThrowing: 异常后通知, 此注解标注的通知⽅法在目标方法发⽣异常后执⾏ 我们可以通过如下的代码来熟悉这几种通知类型
Aspect //标识这是一个切面类
Component //将 AspectDemo 的对象交给 Spring 进行管理切面类中的通知方法才能对指定的目标方法起作用
Slf4j
public class AspectDemo {//前置通知Before//通知方法作用于目标方法执行前Before(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))public void before_AOP(){log.info(执行 before_AOP 方法);}//后置通知After//通知方法作用于目标方法执行后After(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))public void after_AOP(){log.info(执行 after_AOP 方法);}//返回后通知AfterReturning//通知方法作用于目标方法“正确”返回后AfterReturning(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))public void afterReturning_AOP(){log.info(执行 afterReturning_AOP 方法);}//抛出异常后通知(AfterThrowing)//通知方法作用于目标方法抛出异常后AfterThrowing(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))public void afterThrowing_AOP(){log.info(执行 afterThrowing_AOP 方法);}//环绕通知Around//通知方法作用于目标方法执行前和执行后Around(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))// point 参数表示目标方法public Object around_AOP(ProceedingJoinPoint point) throws Throwable {log.info(执行 around_AOP 方法,目标方法前);//执行目标方法,并且要用一个变量接收目标方法的值然后再让通知方法返回Object resultpoint.proceed();log.info(执行 around_AOP 方法,目标方法后);return result;}
} 1.当 com.yulin.spring_aop_demo.Controller 路径下的一个目标方法正常执行时 程序正常运⾏的情况下, AfterThrowing 标识的通知⽅法不会执⾏ 从上图也可以看出来, Around 标识的通知⽅法包含两部分,⼀个前置逻辑,⼀个后置逻辑.其 中前置逻辑会先于 Before 标识的通知⽅法执⾏,后置逻辑会晚于 After 标识的通知⽅法执行 2.当 com.yulin.spring_aop_demo.Controller 路径下的一个目标方法执行出现异常时 · AfterReturning 标识的通知⽅法不会执⾏ AfterThrowing 标识的通知⽅法执⾏了
Around 环绕通知的后置逻辑没有执行因为目标方法没有正常返回
注意事项: 1.Around 环绕通知需要调⽤ ProceedingJoinPoint.proceed() 来让原始⽅法执⾏,其他 通知不需要考虑⽬标⽅法执⾏.在该代码前的代码就是环绕通知的前置逻辑该代码后的代码就是环绕通知的后置逻辑 2.Around 环绕通知⽅法的返回值,必须指定为 Object ,来接收目标⽅法的返回值,否则目标⽅法执⾏完毕,是获取不到返回值的.
PointCut定义切点 上面的代码有一个问题就是存在⼤量重复的切点表达式 execution(* com.yulin.spring_aop_demo.Controller.*.*(..)) Spring提供了 PointCut 注解,把公共的切点表达式提取出来,需要⽤到时引⽤该切⼊点表达式即可 上面的代码可以修改为
Aspect //标识这是一个切面类
Component //将 AspectDemo 的对象交给 Spring 进行管理切面类中的通知方法才能对指定的目标方法起作用
Slf4j
public class AspectDemo {//定义切点Pointcut(execution(* com.yulin.spring_aop_demo.Controller.*.*(..)))private void pt(){}//前置通知Before//通知方法作用于目标方法执行前Before(pt())public void before_AOP(){log.info(执行 before_AOP 方法);}//后置通知After//通知方法作用于目标方法执行后After(pt())public void after_AOP(){log.info(执行 after_AOP 方法);}//返回后通知AfterReturning//通知方法作用于目标方法“正确”返回后AfterReturning(pt())public void afterReturning_AOP(){log.info(执行 afterReturning_AOP 方法);}//抛出异常后通知(AfterThrowing)//通知方法作用于目标方法抛出异常后AfterThrowing(pt())public void afterThrowing_AOP(){log.info(执行 afterThrowing_AOP 方法);}//环绕通知Around//通知方法作用于目标方法执行前和执行后Around(pt())// point 参数表示目标方法public Object around_AOP(ProceedingJoinPoint point) throws Throwable {log.info(执行 around_AOP 方法,目标方法前);//执行目标方法,并且要用一个变量接收目标方法的值然后再让通知方法返回Object resultpoint.proceed();log.info(执行 around_AOP 方法,目标方法后);return result;}
}注意当切点定义使⽤private修饰时,仅能在当前切⾯类中使⽤,当其他切⾯类也要使⽤当前切点定义时,就需要把private改为public.引⽤⽅式为:全限定类名.⽅法名 ()
切面优先级 Order 当我们在⼀个项⽬中,定义了多个切⾯类时,并且这些切⾯类的切入点都匹配到了同⼀个⽬标⽅法.当⽬标⽅法运⾏的时候,这些切⾯类中的通知⽅法都会执⾏,那么这⼏个通知⽅法的执⾏顺序是什么样的呢? 我们通过程序来求证: 定义多个切⾯类: AspectDemo1 和 AspectDemo2为简单化,只写了 Before 和 After 两个通知
AspectDemo1
Aspect
Component
Slf4j
public class AspectDemo1 {Before(com.yulin.spring_aop_demo.Aspect.AspectDemo.pt())public void before_AOP(){log.info(执行 AspectDemo1 -》 before_AOP 方法);}After(com.yulin.spring_aop_demo.Aspect.AspectDemo.pt())public void after_AOP(){log.info(执行 AspectDemo1 -》 after_AOP 方法);}}
AspectDemo2
Aspect
Component
Slf4j
public class AspectDemo2 {Before(com.yulin.spring_aop_demo.Aspect.AspectDemo.pt())public void before_AOP(){log.info(执行 AspectDemo2 -》 before_AOP 方法);}After(com.yulin.spring_aop_demo.Aspect.AspectDemo.pt())public void after_AOP(){log.info(执行 AspectDemo2 -》 after_AOP 方法);}} 上述两个切面类中的通知方法都作用于同一批目标方法我们现在来观察目标方法执行后通知方法的执行顺序 2023-12-04 11:55:07.539 INFO 28864 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo1 : 执行 AspectDemo1 -》 before_AOP 方法
2023-12-04 11:55:07.539 INFO 28864 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo2 : 执行 AspectDemo2 -》 before_AOP 方法
2023-12-04 11:55:07.545 INFO 28864 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo2 : 执行 AspectDemo2 -》 after_AOP 方法
2023-12-04 11:55:07.545 INFO 28864 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo1 : 执行 AspectDemo1 -》 after_AOP 方法如上的日志可以看出 存在多个切⾯类时,默认按照切⾯类的类名字⺟排序 • Before 通知字⺟排名靠前的先执⾏ • After 通知字⺟排名靠前的后执⾏ 但这种⽅式不⽅便管理我们的类名更多还是具备⼀定含义的这种方式不可控Spring 给我们提供了⼀个新的注解, 来控制这些切⾯通知的执⾏顺序: Order
使⽤⽅式如下:
Aspect
Component
Order(2)
public class AspectDemo1 {//...代码省略
}Aspect
Component
Order(1)
public class AspectDemo2 {//...代码省略
}设置优先级以后重新访问目标方法带动执行通知方法得到的日志为
2023-12-04 12:07:38.540 INFO 34560 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo2 : 执行 AspectDemo2 -》 before_AOP 方法
2023-12-04 12:07:38.541 INFO 34560 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo1 : 执行 AspectDemo1 -》 before_AOP 方法
2023-12-04 12:07:38.546 INFO 34560 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo1 : 执行 AspectDemo1 -》 after_AOP 方法
2023-12-04 12:07:38.546 INFO 34560 --- [nio-8080-exec-1] c.y.spring_aop_demo.Aspect.AspectDemo2 : 执行 AspectDemo2 -》 after_AOP 方法 当我们设置 AspectDemo2 的优先级为1AspectDemo1 的优先级为 2 后AspectDemo2 的优先级就比 AspectDemo1 的高所以 AspectDemo2 的 before 通知会先执行after 通知会后执行 通过上述程序的运⾏结果,得出结论: Order 注解标识的切⾯类,执⾏顺序如下: • Before 通知数字越⼩优先级越高先执⾏ • After 通知数字越⼤优先级越小先执⾏
切点表达式 上⾯的代码中,我们⼀直在使⽤切点表达式来描述切点描述目标方法所在的位置.下⾯我们来介绍⼀下切点表达式的语法. 切点表达式常⻅有两种表达⽅式
1. execution(......) 根据⽅法的签名来匹配
2.annotation(......) 根据注解匹配
execution表达式 execution() 是最常⽤的切点表达式, ⽤来匹配目标⽅法,语法为:
execution(访问修饰符 返回类型 包名.类名.⽅法(⽅法参数) 异常),其中:访问修饰符和异常可以省略 访问修饰符为 private public protected匹配目标方法的访问修饰符
切点表达式⽀持通配符表达:
1. * 匹配任意字符只匹配⼀个元素(返回类型,包,类名,⽅法或者⽅法参数) a. 包名使⽤* 表⽰任意包(⼀层包使⽤⼀个*) b. 类名使⽤* 表⽰任意类 c. 返回值使⽤* 表⽰任意返回值类型 d. ⽅法名使⽤* 表⽰任意⽅法 e. 参数使⽤* 表⽰⼀个任意类型的参数
.. 匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数 a. 使⽤ .. 配置包名标识此包以及此包下的所有⼦包 b. 可以使⽤ .. 配置参数匹配任意个任意类型的参数
切点表达式示例
TestController类下的 public修饰,返回类型为String⽅法名为t1,⽆参⽅法
execution(public String com.example.demo.controller.TestController.t1())匹配 controller 包下所有的类的所有⽅法
execution(* com.example.demo.controller.*.*(..))匹配所有包下⾯的 TestController 类中的所有方法
execution(* com..TestController.*(..))匹配com.example.demo包下,⼦孙包下的所有类的所有⽅法
execution(* com.example.demo..*(..))annotation execution 表达式更适⽤有规则的,如果我们要匹配多个⽆规则的⽅法呢,⽐如TestController中的 t1() 和 UserController 中的 u1() 这两个⽅法. 这个时候我们使⽤ execution 这种切点表达式来描述就不是很⽅便了.我们可以借助⾃定义注解的⽅式以及另⼀种切点表达式 annotation 来描述这⼀类的切点 实现步骤 1. 编写⾃定义注解 2. 使⽤ annotation 表达式来描述切点 3. 在连接点的⽅法上添加⾃定义注解
1. 编写自定义注解 创建⼀个注解类(和创建 Class ⽂件⼀样的流程,选择Annotation就可以了)
Target(ElementType.METHOD) //表示自定义注解的作用范围即该注解可以⽤在什么地⽅.
Retention(RetentionPolicy.RUNTIME) //表示自定义注解的生命周期即注解被保留的时间⻓短
public interface MyAspectAnnotation { } Target 注解用于设置自定义注解的作用范围即该注解可以⽤在什么地⽅.上述代码表示注解用于方法上 常⽤取值: ElementType.TYPE:⽤于描述类、接⼝(包括注解类型)或 enum 声明 ElementType.METHOD:描述⽅法 ElementType.PARAMETER:描述参数 ElementType.TYPE_USE:可以标注任意类型 Retention 注解设置自定义注解的生命周期即注解被保留的时间⻓短上述代码表示注解在程序运行时也一直存在 常⽤取值: 1. RetentionPolicy.SOURCE表⽰注解仅存在于源代码中,编译成字节码后会被丢弃.这意味着 在运⾏时⽆法获取到该注解的信息,只能在编译时使⽤.⽐如 SuppressWarnings ,以及 lombok提供的注解 Data ,Slf4j 2. RetentionPolicy.CLASS编译时注解.表⽰注解存在于源代码和字节码中,但在运⾏时会被丢弃.这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运⾏时⽆法获取.通常⽤于⼀些框架和⼯具的注解. 3. RetentionPolicy.RUNTIME运⾏时注解.表⽰注解存在于源代码,字节码和运⾏时中.这意味 着在编译时,字节码中和实际运⾏时都可以通过反射获取到该注解的信息.通常⽤于⼀些需要在运⾏时处理的注解,如Spring的 Controller ResponseBody
2. 使⽤ annotation 表达式来描述切点 我更喜欢这样理解通过 annotation 注解把切面类中的通知方法依附于注解上在目标方法上使用该注解在运行目标方法时便会执行依附于注解上的通知方法
Aspect
Component
Slf4j
public class MyAspectDemo {// annotation 注解中的参数是通知方法要绑定的注解位置//如下代码就将 beforeAOP通知方法绑定到了 MyAspectAnnotation 注解上Before(annotation(com.yulin.spring_aop_demo.Annotation.MyAspectAnnotation))public void beforeAOP(){log.info(执行 MyAspectDemo -》 beforeAOP 通知方法);}After(annotation(com.yulin.spring_aop_demo.Annotation.MyAspectAnnotation))public void afterAOP(){log.info(执行 MyAspectDemo -》 beforeAOP 通知方法);}
} 我们定义一个接口类来使用定义好的注解 MyAspectAnnotation
RequestMapping(/userController)
RestController
Slf4j
public class userController {RequestMapping(/getUserName)public String getUserName(){log.info(进入接口 getUserName );return 小三;}MyAspectAnnotationRequestMapping(/getPassword)public String getPassword(){log.info(进入接口 getPassword );return 123;}
} 当我们访问 ”/userController/getUserName“ 路径得到如下的日志
2023-12-04 16:33:01.873 INFO 18364 --- [nio-8080-exec-1] c.y.s.Controller.userController : 进入接口 getUserName 通过日志我们可以看出由于 getUserName方法上没有添加 MyAspectAnnotation 注解所以访问该接口后不会执行通知方法 当我们访问 ”/userController/getPassword“ 路径得到如下的日志
2023-12-04 16:35:47.123 INFO 18364 --- [nio-8080-exec-4] c.y.spring_aop_demo.Aspect.MyAspectDemo : 执行 MyAspectDemo -》 beforeAOP 通知方法
2023-12-04 16:35:47.123 INFO 18364 --- [nio-8080-exec-4] c.y.s.Controller.userController : 进入接口 getPassword
2023-12-04 16:35:47.123 INFO 18364 --- [nio-8080-exec-4] c.y.spring_aop_demo.Aspect.MyAspectDemo : 执行 MyAspectDemo -》 beforeAOP 通知方法 通过日志我们可以看出由于 getPassword方法上添加了 MyAspectAnnotation 注解所以访问该接口后会执行依附于该注解的通知方法 Spring AOP的实现⽅式(常⻅⾯试题) 1. 基于注解 Aspect (参考上述内容) 2. 基于⾃定义注解(参考⾃定义注解 annotation 部分的内容)