目录
一、Spring AOP 是什么?
AOP 就是面向切面的编程, 是一种思想,是对某一类事情的集中处理。
例如登陆权限的检验(检测当前用户是否登录),在学习 AOP 前,我们会将这样一个检测机制封装成一个方法,在需要检测的地方调用该方法即可,但想象以下,首先,如果这个方法有 1000 个地方要进行调用, 那么你去一个一个写调用函数很累,其次,一旦这个登陆检查方法需要修改,比如增加一个参数,那么,你就需要修改 1000 方法调用的参数… 但如果你会 AOP 后,我们只需要在某处配置一下,就可以实现用户的登录检测,不需要每一个方法中都写登录检验的调用方法啦!
AOP 是一种思想,而 Spring AOP 是一个框架,提供了对 AOP 思想的实现(类似于 IoC 和 DI 的关系)。
二、学习AOP 有什么作用?
例如刚刚我们所讲到的登录检测机制,这一个方法一旦需要调用的地方多了,不仅写起来麻烦,维护起来成本也是很高的,所以,对于这种功能统一,且使用地方较多的功能,就可以考虑 AOP 来进行统一的处理!
例如以下常见的使用场景:
- 统一登录检测机制。
- 统一方法的执行时间统计。
- 统一的返回格式设置。
- 统一异常处理。
- 事务的开始和提交。
Ps: AOP 是对某一功能进行的统一处理,大大降低了代码维护的成本,所以可以说 AOP 是 OOP 的补充和完善~
三、AOP 的组成
3.1、切面(Aspect)
切面,在程序中就是对某一功能进行统一处理的类, 这个类里包含了很多方法,这些方法就是由 切点 和 通知 组成。
3.2、切点(Pointcut)
用来进行主动拦截的规则(配置)。
这里拦截的是什么,过程是什么样的呢?就是对用户向服务器发送的请求进行拦截,检测用户的操作是否符合预期,发现问题并统一处理的过程,如下图:
3.3、通知(Advice)
通知就是 AOP 的具体执行动作。具体的,在程序中被拦截后会触发一个具体的动作,就是通知中具体实现的业务代码。
在 Spring 中,可以在方法上使用以注解,设置方法为通知方法,被拦截后满足条件就会调用通知方法:
- 前置通知(@Before):执行 目标方法(被拦截的方法)之前执行的方法。
- 后置通知(@After):执行了目标方法之后执行的方法。
- 返回之后通知(@AfterReturning):目标方法执行了返回数据(return)时,执行的方法。
- 抛异常后通知(@AfterThrowing):在执行目标方法出现异常时,执行的方法。
- 环绕通知(@Around):在目标方法执行的周期范围内(执行之前,执行中,执行后)都可以执行的方法(Ps:如果已经有了前置和后置通知,再使用环绕通知,那么周期范围就在前置通知之前 ~ 后置通知之后)。
3.4、连接点
会触发 AOP 规则的所有的点(所以请求)。
四、实现 Spring AOP 一个简单demo
4.1、添加 Spring AOP 框架依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Ps:
1.创建 Spring Boot 项目时是没有 Spring AOP 框架可以选择的。
2.添加 Spring AOP 框架可以去中央仓库,值得注意的是要选择 Spring Boot 对应的 AOP ,而不是 Spring 对应的 AOP。
3.最好选择 Spring Boot 对应版本的 AOP ,以上就是 2.7.9版本。
4.2、定义切面
使用 @Aspect 注解修饰类,告诉框架是一个切面类。
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
}
4.3、定义切点
使用 @Pointcut 修饰一个方法,它不需要由方法体。方法名就是起到一个标识的作用,标识通知方法具体指的是哪一个切点(切点可能有多个)。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
/**
* 切点:配置拦截规则
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
}
4.3.1、切点表达式说明
AspectJ ⽀持三种通配符,如下:
- * :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
- .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
- + :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法如下(注意使用空格进行分割):
execution(<修饰符> <返回类型> <包.类.⽅法(参数)> <异常>)
其中,修饰符和异常可以省略,具体含义如下:
4.4、定义通知
使用通知方法中的五个注解,其中前置通知、后置通知、环绕通知最常用,那么以下代码我将用这三个注解来举例:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
/**
* 切点:配置拦截规则
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before("pointcut()")
public void beforeAdvice() {
System.out.println("执行了前置通知");
}
/**
* 后置通知
*/
@After("pointcut()")
public void aftereAdvice() {
System.out.println("执行了后置通知");
}
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入了环绕通知~");
Object obj = null;
obj = joinPoint.proceed();
System.out.println("退出了环绕通知~");
return obj;
}
}
执行结果如下:
4.5、实现原理
五、Spring AOP 实现原理
5.1、原理概述
Spring 的切面是代理类实现的,包裹了目标对象,也就是说,用户只能先通过代理类,进行校验,如果没有问题才会进一步访问到目标对象。
5.2、织入
织入简单理解就是代理生成的时机,一般情况下,在 Spring AOP 动态代理的植入时机是程序的运行期。
5.3、动态代理
Spring AOP 是建立在动态代理的基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使用 AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类。
JDK 和 CGLIB 底层都是基于反射实现的。、
5.4、面试题:DK 和 CGLIB 实现的区别
1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏ 时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类对象。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129783.html