SpringAOP详解
1. SoringAOP
1.1 什么是AOP?
AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理.
1.2 AOP的作用
就比如说使用csdn时,首先要经过登录页面验证用户信息,然后进入网站后,无论访问任何页面都需要验证你是否登录了,这个时候就需要使用AOP思想,在某处统一配置一下,就可以处理所有需要判断用户是否登录的方法,就这就不需要在每个方法中都实现用户登录判断.
对于那些需要功能统一且使用次数非常之多的功能,可以考虑使用AOP来统一处理了,比如:统一的异常处理,统一的登录拦截,统一的返回格式,统一的日志记录…
1.3 AOP的组成
1.3.1 切面
切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。
通俗来说:AOP是统一事物处理,这个统一处理事物的功能就是切面,
1.3.2 连接点
应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为.
简单来理解就是:能够可能触发AOP(拦截方法的点),.就是连接点
1.3.3 切点
Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice.
通俗说就是:定义AOP 拦截的规则
1.3.4 通知
定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题。
简单来说:就是AOP执行的时机和执行的方法.
Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本
⽅法进⾏调⽤:
- 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
- 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤
- 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
- 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
- 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为
AOP整个概念图如下:
2. SpringAOP 实现
2.1 添加AOP引用
在pom.xml中添加如下代码:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/springboot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 代码演示
流程:
- 创建SpringBoot框架
- 实现切面
- 实现切点
- 定义通知
Controller层代码:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
//@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
System.out.println("sayHi 方法:被执行了。");
return "你好,世界。";
}
@RequestMapping("/sayhello")
public String sayHello() throws InterruptedException {
Thread.sleep(250);
System.out.println("sayHello 方法:被执行了。");
//测试抛出异常
// int num = 10 / 0;
return "世界,你好。";
}
}
AOP配置代码:
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Aspect //此处表示这个类是一个切面
@Component//定义切面
public class UserAspect {
//定义切点
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))") //此处说明切点的定义范围是UserController类下的所有方法
public void pointCut(){
}
//定义前置通知
@Before("pointCut()")
public void doBefore(){
System.out.println("执行了前置通知");
}
//定义后置通知
@After("pointCut()")
public void doAfter() {
System.out.println("后置通知:被执行了。");
}
//返回之后的通知
@AfterReturning("pointCut()")
public void doAfterReturning() {
System.out.println("执行了 AfterReturning 方法");
}
//抛出异常之后的通知
@AfterThrowing("pointCut()")
public void doAfterThrowing() {
System.out.println("执行了 AfterThrowing 方法");
}
//针对环绕通知的计算方法所需的时间
@Around("pointCut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint){
Object o=null;
System.out.println("环绕方法开始了");
StopWatch stopWatch=new StopWatch();//用这个类来实现计算方法使用的时间
try {
stopWatch.start();
o=proceedingJoinPoint.proceed();
stopWatch.stop();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕方法执行了");
System.out.println(proceedingJoinPoint.getSignature().getDeclaringTypeName()+"."+//次方法为获取到路劲和类名
proceedingJoinPoint.getSignature().getName() +"使用时间为:" + //次方法为获取到方法名
stopWatch.getTotalTimeMillis()+"ms"); //次方法获取总的时间
return o;
}
}
结果如下,几种通知的区别很容易看出来.
3. SpringAOP 实现原理
Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截
Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。默认情况下,实现了接⼝的类,使⽤AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
织入:代理的生成时机
织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对象中.
在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就是以这种⽅式织⼊切⾯的。
类加载器:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器(ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码.AspectJ5的加载时织⼊(load-time weaving.LTW)就⽀持以这种⽅式织⼊切⾯。
运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。
动态代理:
此种实现在设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动态的织⼊字节码。
我们学习 Spring 框架中的AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代理⽬标都是被代理类中的⽅法,在运⾏期,动态的织⼊字节码⽣成代理类。
- CGLIB是Java中的动态代理框架,主要作⽤就是根据⽬标类和⽅法,动态⽣成代理类.
- Java中的动态代理框架,⼏乎都是依赖字节码框架(如 ASM,Javassist 等)实现的.
- 字节码框架是直接操作 class 字节码的框架。可以加载已有的class字节码⽂件信息,修改部分信息,或动态⽣成⼀个 class.
3.1JDK和CGLIB 的区别
- JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
- CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类对象。
4. 总结
AOP 是对某⽅⾯能⼒的统⼀实现,它是⼀种实现思想,Spring AOP 是对 AOP 的具体实现,SpringAOP 可通过 AspectJ(注解)的⽅式来实现 AOP 的功能,Spring AOP 的实现步骤是:
- 添加 AOP 框架⽀持。
- 定义切⾯和切点。
- 定义通知。
Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有种:JDK Proxy 和 CGLIB