什么是AOP
AOP是一种思想,它叫做面向切面编程,简单的来说就是对某一类事请做集中处理。比如说:登录效验功能,在使用AOP之前,我们进行登录效验需要在每个方法中写一遍登录效验的代码;使用AOP后,我们只需要将登录效验的代码配置起来,在需要登录效验的方法执行之前先经过登录效验的代码,这样既减少了代码量降低了代码的重复性,还让我们程序更加解耦。
什么是Spring AOP
Spring AOP是对AOP思想的一种具体实现。和IoC与DI的关系类似。
为什么要使用AOP
AOP可以实现的功能有很多:统一的事务处理、统一的日志记录、统一的异常处理、统一的返回格式设置.....
使用AOP可以给多个对象扩充某个能力,是非常实用的思想。
AOP的组成及概念
(1)切面:切面有切点和通知组成,它既包含了横切逻辑的定义,也包含了连接点的定义。
大意:切面就是一个包含了拦截逻辑(切点)和通知(拦截后要做的事)的一个类。
(2)连接点:应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
大意:连接点就是可以在拦截规则之中的,可以来到切面的方法。
(3)切点:切点是匹配连接点的谓词。切点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知。
大意:切点是一个拦截规则,满足规则的方法就是连接点,而链接点会来到切面执行通知(执行具体内容)
(4)通知:切面的工作被称之为通知。
通知分为好几种:
前置通知:使用@Before,通知方法会在目标方法调用前执行。
后置通知:使用@After,通知方法会在目标方法返回后或抛出异常后后调用。
返回之后通知:@AfterReturn,通知方法会在目标方法返回后调用。
抛异常后通知:@AfterThrowing,通知方法会在目标方法抛出异常后调用。
环绕通知:@Around,通知将目标方法环绕,在调用目标方法的前后进行通知。
大意:切面执行的具体内容。
用程序逻辑,总结上面的概念:
切点描述的是一个范围,在范围里面的类都是符合切点的对象;
连接点就是执行到的切点,当一个在切点范围内的类,里面的方法被执行时,此时这个方法就是一个链接点;
通知就是当连接点被执行到时,在方法额外中添加的任务。举例:当通知是执行前的通知时,此时会在执行方法之前先执行通知。
切面是包含切点和通知的类。
代码实现
添加依赖到pom.xml文件中
首先打开maven商店:https://mvnrepository.com/?__cf_chl_tk=UNFVbxMbXH6Fwrcs97l6W2f8RlXbZxBy.xvqxEi3Ks0-1684329597-0-gaNycGzNDPs
然后搜索Spring AOP
找到Spring Boot Starter AOP
找到2.x版本进行插入(2.x对应jdk8)
下面这个是2.7.11版本的maven依赖。
<!-- 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.11</version>
</dependency>
切面类:@Aspect注解
切点:@pointcut注解
注解里面的内容如下:分为4个部分
修饰符(可省略):public、private...
返回类型:String、Integer...
范围:包、类、方法(参数) 参数部分用..表示匹配任意参数
异常(可省略):一般不写
举例:
@Aspect //切面
@Component
public class UserAOP {
//切点(配置拦截规则)
@Pointcut("execution(* com.example.demo.controller.UserController.login(..))")
public void pointcut() {
}
//前置通知
@Before("pointcut()")
public void doBefore() {
//打印当前时间
System.out.println("执行了前置通知: " + LocalDateTime.now());
}
//后置通知
@After("pointcut()")
public void doAfter() {
//打印当前时间
System.out.println("执行了后置通知: " + LocalDateTime.now());
}
//return后通知
@AfterReturning("pointcut()")
public void doAfterReturning() {
System.out.println("执行了return后通知: " + LocalDateTime.now());
}
//异常通知
@AfterThrowing("pointcut()")
public void doAfterThrowing() {
System.out.println("执行了异常通知: " + LocalDateTime.now());
}
//环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object obj = null;
System.out.println("开始执行环绕通知了");
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("结束环绕通知了");
return obj;
}
}
执行类:
@Controller
@ResponseBody
public class UserController {
@RequestMapping("/user/sayHi")
public String sayHi() {
System.out.println("执行了 sayHi 方法");
return "hi, spring boot aop.";
}
@RequestMapping("/user/login")
public String login() {
System.out.println("执行了 login 方法");
return "do user login";
}
}
因为切点中定义只有login方法符合,此时login方法执行和sayHi方法执行如下:
login方法:
sayHi方法:
Spring AOP底层原理
AOP的实现主要由两种:静态代理和动态代理。
静态代理可以理解为需要实现接口和类的方式实现代理,理解起来比较容易,但是写起来就比较繁琐。
动态代理是Java在运行时动态生成代理的一种方式,它使用的是Java的反射。
Spring AOP是通过动态代理来实现的。
Java实现动态代理的方式主要由两种:JDK动态代理和CGLIB动态代理。
其中JDK动态代理是使用反射的技术,它的执行速度快,但是被代理的类必须要实现接口。
CGLIB动态代理是通过实现代理类的子类来实现动态代理的,不需要实现接口,但是被final修饰的类不能被代理。
Spring AOP中既使用了JDK动态代理,又实用了CGLIB动态代理。
在默认情况下是使用JDK动态代理,但是如果被代理类没有实现接口,此时会使用CGLIB动态代理。