目录
一 .AOP概述
二. Spring AOP 使用
2.1 引入AOP依赖
2.2 编写AOP程序
三. Spring AOP详情
3.1 切点(Pointcut)
3.2 连接点(Join Point)
3.3通知(Advice)
3.4切面(Aspect)
3.5通知
3.6 @PointCut (公共切点)
3.7 切面类的优先级 @Order
3.8 切点表达式
3.8.1 execution表达式
3.8.2@annotation
四 代理模式
总结:
一 .AOP概述
Spring 两大核心思想:
- IoC :IoC概述(详情)
- AOP:Aspect Oriented Programming (面向切面编程)
什么是切面编程呢? 切面编程指的是某一类特定问题 所以AOP也可以理解为面向特定方法编程。
什么是面向特定方法编程呢?统一功能处理之拦截器、统一数据返回格式、统一异常处理 这类的问题的统一处理, 所以拦截器也是AOP的一种应用, AOP是一种思想, 拦截器就是AOP思想的一种实现,. Spring框架实现了这种思想, 提供了拦截器技术的相关接⼝。
同样统一数据返回格式、统一异常处理 都是AOP思想的一种实现。
可以这么理解 AOP是一种思想 , 是对某一类事物的集中处理
例如上面的 统一功能处理之拦截器、统一数据返回格式、统一异常处理
二. Spring AOP 使用
2.1 引入AOP依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
2.2 编写AOP程序
创建编写切面类:
@Slf4j
@Component
@Aspect
public class AspectDemo {//定义切点(公共的切点表达式)@Pointcut("execution(* com.example.springaop.controller.*.*(..))")public void pointcut(){};@Around("pointcut()")public Object test2(ProceedingJoinPoint joinPoint) throws Throwable {log.info("环绕通知 方法前执行....");Object result = joinPoint.proceed();log.info("环绕通知 方法后执行....");return result;}/*** @param joinPoint* @return 针对使用@MyAspect注解的方法* @throws Throwable*/@Around("@annotation(com.example.springaop.config.MyAspect)")public Object test(ProceedingJoinPoint joinPoint) throws Throwable {log.info("环绕通知 方法前执行....");Object result = joinPoint.proceed();log.info("环绕通知 方法后执行....");return result;}/**** @param joinPoint* @return 针对使用@RequestMapping注解的方法* @throws Throwable*/@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")public Object test1(ProceedingJoinPoint joinPoint) throws Throwable {log.info("环绕通知 方法前执行....");Object result = joinPoint.proceed();log.info("环绕通知 方法后执行....");return result;}
}
启动类conterllor 类代码 如下:
@RequestMapping("/user")
@RestController
@Slf4j
public class UserController {@MyAspect@RequestMapping("/user")public void text01(){log.info("我是text01");}@RequestMapping("/getUser1")public boolean text02(){int a = 10/0;return true;}
}
对程序进⾏简单的讲解:@Aspect:标识这是个切面类@Around:环绕通知, 在方法前后都会执行, 后面参数表示对那些方法生效@ProceedingJoinPoint.proceed() 让原始⽅法执⾏
AOP 面向切面编程优点:
- 代码无侵入:不用修改原始方法, 就可以对方法进行增强或者功能的变更
- 减少重复代码
- 提高开发效率
- 维护方便
三. Spring AOP详情
3.1 切点(Pointcut)
Pointcutz作用:告诉程序对 哪些⽅法来进⾏功能增强.也称:公共切点表达式!
3.2 连接点(Join Point)
切点和连接点的关系连接点是满⾜切点表达式的元素. 切点可以看做是保存了众多连接点的⼀个集合.
3.3通知(Advice)
3.4切面(Aspect)
切⾯(Aspect) = 切点(Pointcut) + 通知(Advice)
切⾯所在的类, 我们⼀般称为切⾯类(被@Aspect注解标识的类)
3.5通知
Spring中AOP的通知类型有以下几种:
- @Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
@Slf4j
@Aspect
@Component
public class AspectDemo {//前置通知@Before("execution(* com.example.springaop.controller.*.*(..))")public void doBefore() {log.info("执⾏ Before ⽅法");}//后置通知@After("execution(* com.example.springaop.controller.*.*(..))")public void doAfter() {log.info("执⾏ After ⽅法");}//返回后通知@AfterReturning("execution(* com.example.springaop.controller.*.*(..))")public void doAfterReturning() {log.info("执⾏ AfterReturning ⽅法");}//抛出异常后通知@AfterThrowing("execution(* com.example.springaop.controller.*.*(..))")public void doAfterThrowing() {log.info("执⾏ doAfterThrowing ⽅法");}//添加环绕通知@Around("execution(* com.example.springaop.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ⽅法开始执⾏");Object result = joinPoint.proceed();log.info("Around ⽅法结束执⾏");return result;}
}
测试启动类:
@RequestMapping("/user")
@RestController
public class UserController {private static final Logger log = LoggerFactory.getLogger(UserController.class);@MyAspect@RequestMapping("/user")public void text01(){log.info("我是text01");}@RequestMapping("/getUser1")public boolean text02(){int a = 10/0;return true;}
}
测试运行正常的结果:
程序正常运⾏的情况下, @AfterThrowing 标识的通知⽅法不会执⾏从上图也可以看出来, @Around 标识的通知⽅法包含两部分, ⼀个"前置逻辑", ⼀个"后置逻辑".其 中"前置逻辑" 会先于 @Before 标识的通知⽅法执⾏, "后置逻辑" 会晚于 @After 标识的通知⽅法执⾏
测试运行报错的异常的情况:
- @AfterReturning 标识的通知⽅法不会执⾏, @AfterThrowing 标识的通知⽅法执⾏了
- @Around 环绕通知中原始⽅法调⽤时有异常,通知中的环绕后的代码逻辑也不会在执⾏了(因为 原始⽅法调⽤出异常了)
测试结果流程图:
注意 :
- @Around 环绕通知需要调⽤ ProceedingJoinPoint.proceed() 来让原始⽅法执⾏, 其他 通知不需要考虑⽬标⽅法执⾏.
- @Around 环绕通知⽅法的返回值, 必须指定为Object, 来接收原始⽅法的返回值, 否则原始⽅法执 ⾏完毕, 是获取不到返回值的.
- ⼀个切⾯类可以有多个切点.
3.6 @PointCut (公共切点)
//公共切点@Pointcut("execution(* com.example.springaop.controller.*.*(..))")public void pointCut() {};//前置通知@Before("pointCut()")public void doBefore() {log.info("执⾏ Before ⽅法");}
3.7 切面类的优先级 @Order
没有使用 @Order时 多个切面类同时启动的结果:
- @Before 通知:字⺟排名靠前的先执⾏
- @After 通知:字⺟排名靠前的后执⾏
使用 @Order时 多个切面类同时启动的结果:
通过上述程序的运⾏结果, 得出结论:@Order 注解标识的切⾯类, 执⾏顺序如下:
- @Before 通知:数字越⼩先执⾏
- @After 通知:数字越⼤先执⾏
3.8 切点表达式
- execution(...):根据⽅法的签名来匹配
- @annotation(....) :根据注解匹配
3.8.1 execution表达式
匹配语法为:
execution ( <访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
3.8.2@annotation
execution表达式更适⽤有规则的, 如果我们要匹配多个⽆规则的⽅法呢,
问:如果我们 匹配两个不同类的一个方法,怎么操作呢?
我们可以借助⾃定义注解的⽅式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点
实现步骤:
- 编写自定义类
- 使用@annotation 表达式来描述切点
- 在连接点的方法上添加自定义注解
第一步:首先创建一个自定义类 MyAspect
具体代码
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAspect {}
注解解释:
一.@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅. 常⽤取值:
- ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型) 或enum声明
- ElementType.METHOD: 描述⽅法
- ElementType.PARAMETER: 描述参数
- ElementType.TYPE_USE: 可以标注任意类型
二. @Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期,@Retention 的取值有三种:
- RetentionPolicy.SOURCE:表⽰注解仅存在于源代码中, 编译成字节码后会被丢弃. 这意味着在运⾏时⽆法获取到该注解的信息, 只能在编译时使⽤. ⽐如 @SuppressWarnings , 以及 lombok提供的注解 @Data , @Slf4j
- RetentionPolicy.CLASS:编译时注解. 表⽰注解存在于源代码和字节码中, 但在运⾏时会被丢弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息, 但在实际运⾏时⽆法获 取. 通常⽤于⼀些框架和⼯具的注解.
- RetentionPolicy.RUNTIME:运⾏时注解. 表⽰注解存在于源代码, 字节码和运⾏时中. 这意味着在编译时, 字节码中和实际运⾏时都可以通过反射获取到该注解的信息. 通常⽤于⼀些需要 在运⾏时处理的注解, 如Spring的 @Controller @ResponseBody
第二步:在切面类中使用@annotation
@Slf4j
@Aspect
@Component
@Order(3)
public class ApectDemo2 {@After("@annotation(com.example.springaop.config.MyAspect)")public void doAfter() {log.info("ApectDemo2 执⾏ After ⽅法");}
}
@annotationh后面的连接点就是 自定义注解类的路径
第三步:在 需要执行的方法上面加上 自定义注解 @MyAspect:
@RequestMapping("/user")
@RestController
@Slf4j
public class UserController {@MyAspect@RequestMapping("/user")public void text01(){log.info("我是text01");}
}
Spring AOP 的实现方式 (常见面试题):
- 基于注解 @Aspect。
- 基于自定义注解(@annotation)。
- 基于 Spring API(通过 xml 配置的方式,自从 SpringBoot 广泛使用之后,这种方法几乎看不到了)。
- 基于代理来实现(更加久远的一种实现方式,写法笨重,不建议使用)
四 代理模式
Spring AOP 是基于动态代理来实现 AOP 的
其代理模式 也称委托模式
定义:为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤.在某些情况下, ⼀个对象不适合或者不能直接引⽤另⼀个对象, ⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤.

代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进⾏⼀些功能的附加与增强.
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态代理:在程序运行时,运用反射机制动态创建而成
总结:
- AOP是⼀种思想, 是对某⼀类事情的集中处理. Spring框架实现了AOP, 称之为SpringAOP
- Spring AOP常⻅实现⽅式有两种: 1. 基于注解@Aspect来实现 2. 基于⾃定义注解来实现, 还有⼀些 更原始的⽅式,⽐如基于代理, 基于xml配置的⽅式, 但⽬标⽐较少⻅
- Spring AOP 是基于动态代理实现的, 有两种⽅式: 1. 基本JDK动态代理实现 2. 基于CGLIB动态代理 实现. 运⾏时使⽤哪种⽅式与项⽬配置和代理的对象有关