目录
一、AOP是什么?Spring AOP是什么?
AOP(Aspect Oriented Programming):面向切面编程,是一种思想,它是对某一类事情的集中处理。
Spring AOP是一个框架,提供了一种对AOP思想的实现。
二、为什么使用AOP?AOP可以完成什么功能呢?
比方说我们编写一个后台系统的代码,为保证系统中功能的安全性,每次操作系统中某个功能的时候都需要进行用户验证(除了注册和登录时不用验证),也就代表我们在每个功能模块都需要写上同样的判断代码,不仅会导致程序冗余代码过多,还会导致后期不好进行维护,因为只要对验证规则稍有变化,所有地方都需要去一个一个改动。有人可能会说,编写到一个方法中,每次调用这个方法,代码确实较少了很多,也比较好维护,但是,我们仍会在每个功能中有个方法调用的代码,当我们修改了方法名字咋办呢?所以这就是为啥有AOP,将使用地方比较多的功能,交给AOP统一处理,后期只需要在一个地方进行维护。
AOP可以实现用户的登录判断,统一日志记录,统一方法执行时间统计,统一的返回格式设置,统一的异常处理,事务的开启和提交等。使用AOP可以扩充多个对象的某个能力,AOP是OOP(面向对象)的补充和完善。
三、Spring AOP的学习
3.1AOP的组成
1.切面(Aspect):在程序中是个类。指的是某一方面的具体内容就是切面,如用户的登录判断就是一个切面。
2.切点(Pointcut):类中的一个方法,定义的拦截规则。
3.通知(Advice):方法具体实现代码,执行AOP逻辑业务。
- 前置通知(@Before):在目标方法(实际要执行方法)调用之前执行的通知;
- 后置通知(@After):在目标方法之后执行的通知;
- 环绕通知(@AfterReturning):在目标方法调用前、后都执行的通知;
- 异常通知(@AfterThrowing):在目标方法抛出异常的时候执行的通知;
- 返回通知(@Around):在目标方法返回的时候执行的通知。
4.连接点(Join Point)
所有可能触发切点的点就叫做连接点。
(如果大家对这个概念还有点懵,那就根据下面这个例子来弄懂吧!)
3.2Spring AOP的实现
1.添加Spring Boot AOP依赖,大家看自己JDK多少,然后选择适配的AOP。(创建SpringBoot项目的时候,无法快速的直接添加,因为库中没提供,我们需要在加载完的项目中,手动添加这个依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.0.4</version>
</dependency>
2.创建切面,在类上加注解@Aspect表示这个类为切面,但是也要加上类注解,目的是Spring框架需要识别并管理这些切面类,以便在合适的时候将切面逻辑织入到目标对象中。
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
//创建一个切面
@Aspect
@Component
public class UserAOP {
}
3.创建切点(定义拦截规则),对哪些类进行拦截,对哪些方法进行拦截。
(1)AspectJ ⽀持三种通配符
* : 匹配任意字符, 只匹配⼀个元素( 包, 类, 或⽅法, ⽅法参数)
.. : 匹配任意字符, 可以匹配多个元素 ,在表示类时, 必须和 * 联合使⽤ 。
+ :表示按照类型匹配指定类的所有类, 必须跟在类名后⾯, 如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身
(2)切点由切点函数组成,execution这个函数经常被使用,表达式如下:
execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
@Aspect
@Component
public class UserAOP {
//创建切点
//这个拦截规则表示将会拦截任意返回类型的com.example.springaopdemo.controller这个包下的UserController的任意方法以及方法中任意参数
@Pointcut("execution(* com.example.springaopdemo.controller.UserController.*(..))")
public void pointCut() {
}
}
切点的方法可以为空方法,不需要有方法体,因为他就是定义拦截规则,起到个标识的作用,标识通知方法指哪个切点。
4.创建通知(通知类型前面已经说过了)
@Aspect
@Component
public class UserAOP {
//创建切点
//这个拦截规则表示将会拦截任意返回类型的com.example.springaopdemo.controller这个包下的UserController的任意方法以及方法中任意参数
@Pointcut("execution(* com.example.springaopdemo.controller.UserController.*(..))")
public void pointCut() {
}
//前置通知
@Before("pointCut()")
public void doBefore() {
System.out.println("执行了前置通知");
}
}
5.创建连接点,连接点说白了也就是我们拦截规则拦截的哪些类哪些方法,拦截的那些所有的方法就可以称为所有的连接点。
@RestController
public class UserController {
@RequestMapping("/test")
public void test() {
System.out.println("连接点执行了");
}
}
6.页面输入url测试,结果如下
注意注意!!!环绕通知的执行以及环绕通知和前置后置的执行顺序
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
Object obj = null;
System.out.println("环绕的前置开始执行");
try {
//执行连接点(方法)
obj = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕的后置开始执行");
return obj;
}
3.3Spring AOP的实现原理
Spring AOP是构建在动态代理基础上,所以Spring对AOP的支持局限于方法级别的拦截。
Spring AOP支持JDK Proxy和CGLIB方式实现动态代理,如果实现了接口的类,使用AOP会基于JDK生成代理类,没有实现接口的类,会基于CGLIB生成代理类。
1.JDK动态代理的底层是通过反射机制实现,要求被代理类一定得实现接口。(速度比较块)
2.CGLIB底层是增强字节码技术实现(生成动态子类),通过实现代理类的子类来实现动态代理。被代理类不能被final修饰(无法动态生成子类)
织入:代理的生成时机
织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程 ,切⾯在指定的连接点被织⼊到⽬标对 象中。
在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
● 编译期:切⾯在⽬标类编译时被织⼊ 。这种⽅式需要特殊的编译器 。AspectJ的织⼊编译器就 是以这种⽅式织⼊切⾯的。
● 类加载期:切⾯在⽬标类加载到JVM时被织⼊ 。这种⽅式需要特殊的类加载器(ClassLoader) ,它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码 。AspectJ5的加载时织⼊ ( load-time weaving. LTW) 就⽀持以这种⽅式织⼊切⾯ 。
● 运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊ 。⼀般情况下 ,在织⼊切⾯时 ,AOP容器会为⽬
标对象动态创建⼀个代理对象 。SpringAOP就是以这种⽅式织⼊切⾯的。
JDK和CGLIB都是在运行期,动态织入字节码生成代理类。