AOP概念
AOP的全名是aspect-oriented programming面向切面编程,它是面对对象编程(OOP)的一种方式
这个AOP的思想主要是指对一个事务的集中处理.将多个类都要完成的功能都在一个类中统一完成.如用户登录的校验功能,每一个页面其实都要进行这个校验,AOP技术就是单独在一个另外的类中统一的进行校验
AOP技术是一种思想,Spring AOP是这个思想的一个技术实现
AOP主要完成的事务
AOP主要可以完成以下几种事务:
- 统一日志记录
- 统一方法执行时间统计
- 统一的方法返回结果设置
- 统一的异常处理
- 事务的开启和提交
组成
切面Aspect
切面是由切点和通知组成的.
主要就是一个包含了一些实现功能的AOP集合类,不仅包括了切点和通知,还包含了连接点的定义
连接点 Join Point
程序中调用切面的一个位置,叫做连接点,这个连接点可以是方法调用,可以是抛出异常时,可以是修改参数时
切点 Pointcut
Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹 配 Join Point,给满⾜规则的 Join Point 添加 Advice。
切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,⽽连接点就是表中⼀条⼀条 的数据
通配符
AspectJ ⽀持三种通配符
-
*
匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)..
匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。 -
+
表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身
切点表达式:
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符,异常一般省略
包,类一般不省略
返回类型,方法名,参数不可以省略
通知 Advice
切点的方法体的实现就是通知.
Spring AOP中有以下几种通知:
前置通知@Before
通知方法会在目标方法之前进行执行
后置通知@After
通知方法在目标方法返回之后或者异常抛出之后
返回之后通知@AfterReturning
通知方法在目标方法返回之后通知
抛异常后通知@AfterThrowing
通知方法在目标方法抛出异常之后执行
环绕通知@Around
通知方法在目标方法执行之前和执行之后都通知
@Aspect//加上Aspect就表示它是一个切面
@Component
public class UserAspect {
@Autowired
UserController userController;
//定义切点,并设置切点的范围
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}//切点没有方法体
@Before("pointcut()")
public void doBefore(){
System.out.println("执行before方法");
}
@After("pointcut()")
public void doAfter(){
System.out.println("执行after方法");
}
@AfterReturning("pointcut()")
public void doAfterReturning(){
System.out.println("执行AfterReturning");
}
@AfterThrowing("pointcut()")
public void doAfterThrow(){
System.out.println("执行AfterThrow");
}
public Object doAround(ProceedingJoinPoint joinPoint){
Object object=null;
System.out.println("执行around方法之前");
try {
object=joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("执行around方法之后");
return object;
}
}
当有异常抛出时,如果同时有上面的这些通知,那么这些通知的顺序是什么呢?
执行before方法
执行AfterThrow
执行after方法
先执Before通知,在执行抛出异常通知,最后执行After通知.
因为程序没有正常的结束,所以不会执行AfterReturn通知
这也就说明了:
after通知无论如何都会执行,但是afterReturn在遇到异常之后不会正常执行,这个是两者的区别
对于Around通知来说,如果加上了它,这些通知的执行顺序是什么呢?
执行around方法之前
执行before方法
目标文件执行
执行AfterReturning
执行after方法
执行around方法之后
- 环绕通知的优先级最高
- 接着是前置通知
- 执行目标函数
- 执行afterReturn通知
- 执行后置通知
- 最后是有环绕通知结尾
也就是说,环绕通知最先出现,最晚离开
在正常执行的情况下,afterReturn出现在后置通知之前
使用around注解完成执行时间的统计
使用spring提供的stopWatch和joinPoint来进行统计时间
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object object=null;
StopWatch stopWatch=new StopWatch();
System.out.println("执行around方法之前");
try {
stopWatch.start();
object=joinPoint.proceed();
stopWatch.stop();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
System.out.println(joinPoint.getSignature().getDeclaringType()+"."+
joinPoint.getSignature().getName()+"执行了"+stopWatch.getTotalTimeMillis()+"ms");
}
System.out.println("执行around方法之后");
return object;
}
四大组成的联系
Spring AOP的实现
主要分为以下四个步骤:
一.创建SpringBoot AOP框架
还是在pom.xml中引入SPringBoot框架的AOP依赖,我们可以去Maven中央仓库中找到这个依赖,然后赋值到项目的pom.xml中.
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.0</version>
</dependency>
记住,一定时SpringBoot的依赖,不要添加成Spring的依赖了,因为现在大多数的项目都是使用SpringBoot较多hh
二.创建切面
切面是一个类,在类的前面加上注解@Aspect就表示这是一个切面类.
同时,它也要加上五大类注解@Component
@Aspect//加上Aspect就表示它是一个切面
@Component
public class UserAspect {
}
三.创建切点
在函数的前面加上@Pointcut表示是一个切点.
@Pointcut的后面是指定该切点函数的范围.
//定义切点,并设置切点的范围
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}//切点没有方法体
括号里面的内容是AspectJ语言,
execution(返回类型 包名 类名 方法名 参数类型)
execution(*(返回类型) com.example.demo.controller(包名).UserController(类名).*(方法名)(..)(参数类型))
四.实现通知
直接在切面类中,创建通知函数就可以,在方法的前面加上通知类型.
通知类型的括号里面写通知对应的切点是谁.
@Before("pointcut()")
public void UserLoginOrNot(){
System.out.println("执行before方法");
}
整体代码:
@Aspect//加上Aspect就表示它是一个切面
@Component
public class UserAspect {
@Autowired
UserController userController;
//定义切点,并设置切点的范围
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}//切点没有方法体
@Before("pointcut()")
public void UserLoginOrNot(){
System.out.println("执行before方法");
}
}
实现原理
SpringAOP是基于动态代理的方式实现的.
在没有AOP之前,前端直接和后端进行交互,但是在引入了AOP之后,要先经过AOP的一些通知,这就是动态代理.
主要有两种原理实现:
- JDK proxy
- CGLIB proxy
这两个原理相对来说,大部分时间还是使用CGLIB proxy的,因为它的效率高,当CGLIB无法完成的时候再使用JDK proxy
CGLIB是通过继承目标类来实现动态代理的,对于哪些不可以被继承的类(被final修饰的类) 我们就只好使用JDK proxy
织入
织入是创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对 象中。 在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就 是以这种⽅式织⼊切⾯的。
类加载器:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。
这两种方法都是在运行时发挥动态代理的