文章目录
- 什么是Spring AOP?
- 为什么要使用AOP?
- AOP相关组成的概念
- 切面
- 切点
- 通知
- 连接点
- Spring AOP实现
- 创建切面
- 创建切点
- 创建通知
- 创建连接点
- 示例演示
- Spring AOP的实现原理
什么是Spring AOP?
想要知道Spring AOP,就得先了解AOP
AOP是面向切面编程,是一种思想,是
对某一类事情的集中处理
,如登录校验功能,我们需要在每个需要验证用户是否登录的地方都要实现一遍相同的逻辑,但是有了AOP,我们只需要在某一处进行配置,所有需要判断用户登录的地方就可以全部实现用户登录校验,不用在每一处需要的地方再进行相同的校验逻辑了
AOP是一种思想,Spring AOP是一种具体实现的框架
为什么要使用AOP?
对一些功能统一,使用较多,我们就可以考虑使用AOP思想进行统一处理,如登录校验,使得我们不用在每一处需要做登录校验的地方进行相同逻辑的代码实现了
除了登录校验,AOP还可以用在这些地方:
- 统一日志记录
- 统一方法执行时间统计
- 统一返回格式
- 统一异常处理
- 事务开启和提交
AOP相关组成的概念
切面
指的是某一类事情的具体内容,比如用户登录校验就是一个切面,日志统一记录也是一个切面,切面由切点和通知组成,通常切面是一个类
切点
切点通常是类中的一个方法
(该方法没有实现),作用是提供一组规则来匹配连接点,如登录拦截规则
通知
通知是方法中内容的具体实现,用来执行AOP具体的业务
- 前置通知
@Before
:在目标方法执行前调用 - 后置通知
@After
:在目标方法执行后调用 - 环绕通知
@Around
:在目标方法执行前后调用 - 异常通知
@AfterThrowing
:在目标方法抛出异常调用 - 方法返回通知
@AfterReturning
:在目标方法返回后调用
连接点
所有可能触发切点的地方就是连接点,如某个页面或者某个接口满足切点中的设置的规则
Spring AOP实现
添加Spring Boot AOP框架支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建切面
切面是包含切点和通知的
@Component //交给容器管理
@Aspect //切面
public class UserAOP {
}
创建切点
切点一般是定义拦截规则
//定义切点,设置拦截规则
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
切点表达式由切点函数组成,其中execution()是最常用的切点函数,用来匹配方法,语法为:
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
其中修饰符和异常可以省略,下面是具体含义:
- 修饰符(一般省略):public(公共方法),*(任意)
- 返回类型(不能省略):void,String,int,*(任意)
- 包:com.demo(固定包),com.*(com包下所有),com.demo..(com.demo包下所有子包
含自己
) - 类:Test(固定类),Test*(以之开头),*test(以之结尾),*(任意)
- 方法名(不能省略):addUser(固定方法),add*(以add开头),*add(以add结尾),*(任意)
- 参数:(),(int),(int,String),(..)任意参数
- 异常(可省略,一般不写)
创建通知
@Before("pointcut()")
public void doBefore(){
System.out.println("执行了前置通知:"+ LocalDateTime.now());
}
创建连接点
@RestController
public class UserController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello spring aop");
return "hello spring aop";
}
}
示例演示
启动项目,访问url,观察打印结果:
从中发现,拦截成功,拦截后,在执行目标方法 hello()
之前,先执行了前置通知@Before方法
再加上后置通知,看看执行效果:
@After("pointcut()")
public void doAfter(){
System.out.println("执行了后置通知:"+LocalDateTime.now());
}
添加环绕通知
,先将刚才的前置后置注释掉,看打印结果:
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前");
Object obj = joinPoint.proceed();
System.out.println("环绕通知后");
return obj;
}
将前置后置打开,观察打印结果:
关于打印顺序的说明:
Spring AOP的实现原理
Spring AOP是建立在动态代理
的基础上,Spring对AOP的支持局限于方法级别的拦截
动态代理呢就是当调用者调用目标对象的时候,它不会与目标对象接触,而是由代理类进行调用
代理的生成时机织入
:织入就是把切面应用到目标对象并创建新的代理对象的过程
,切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里,有多个点可以进行织入
- 编译期间:切面在类编译时被织入
- 类加载期间:切面在类被加载到jvm时被织入
运行期
:切面在程序运行的某一时刻被织入,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的
Spring AOP支持JDK Proxy
和CGLIB
方式实现动态代理,这两种方式都是在程序运行期,动态的将切面织入字节码形成代理对象
JDK动态代理与CGLIB的区别:
- JDK动态代理要求被代理的类必须实现接口,CGLIB动态代理不要求被代理的类实现接口,而是通过继承被代理类
- JDK动态代理性能相对较高,生成代理对象速度较快,而CGLIB动态代理性能相对较低,生成代理对象速度较慢
- CGLIB动态代理无法代理final类和final方法,而JDK动态代理可以代理任意类的方法