Spring AOP
spring AOP基础知识
AOP是什么
AOP是一种变成思想,AOP全名Aspect Orient Programming,直译过来就是面向切面编程。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP简单的一点的理解就是,不改变源代码的情况下,对主功能进行增强或添加新功能。这就很像python中的装饰器一样。
AOP的底层实现
AOP 的底层是通过 Spring 提供的的动态代理技术实现 的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
AOP的相关术语
- 通知(Advice): 通知描述了切面何时执行以及如何执行增强处理。
- 连接点(join point): 类里面哪些方法可以被增强,这些方法就成为连接点
- 切入点(PointCut): 实际被真正增强的方法(这里是看别人写的有点难理解)。
- 切面(Aspect): 切面是通知和切点的结合,把通知应用到切入点的过程,是一个动作。
通知又分为五种类型:
- 前置通知:before
- 后置通知:afterreturning
- 环绕通知:around
- 异常通知:只有发生异常才会通知
- 最终通知:after无论是否发生异常都会通知
AOP案例
这里使用spring boot来演示
这里通过对原有的订单功能使用AOP进行登录校验,只有登录用户才可以访问订单功能。
准备环境
准备一个ordercontroller
package com.bkhb.springbootaopdemo.controller;
@RestController
@RequestMapping("order")
public class OrderController {
@GetMapping
public String listOder() {
System.out.println("返回订单信息");
return "返回订单信息";
}
@PostMapping
public String addOder() {
return "添加订单成功";
}
}
导入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建增强类
@Component
@Aspect // 定义一个切面
public class LoginAdvice {
// 定义一个切点:OrderController中的所有方法都增强
@Pointcut(value = "execution(* com.bkhb.springbootaopdemo.controller.OrderController.* (..))")
private void loginAdvicePointcut(){}
// 前置通知
@Before(value = "loginAdvicePointcut()")
public void before() {
System.out.println("before...");
}
// 后置通知
@AfterReturning(value = "loginAdvicePointcut()")
public void afterReturning() {
System.out.println("afterReturning...");
}
// 最终通知
@After(value = "loginAdvicePointcut()")
public void after() {
System.out.println("after...");
}
// 异常通知
@AfterThrowing(value = "loginAdvicePointcut()")
public void afterThrowing() {
System.out.println("afterThrowing...");
}
// 环绕通知 也可以直接将切入点放在通知的value中
@Around(value = "execution(* com.bkhb.springbootaopdemo.controller.OrderController.* (..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
return proceed;
}
}
测试一下看看他们的执行顺序
# 正常情况下的打印
环绕之前.........
before...
返回订单信息
afterReturning...
after...
环绕之后.........
取controller中模拟一个异常
@GetMapping
public String listOder() {
System.out.println("返回订单信息");
int i = 1 / 0 ;
return "返回订单信息";
}
# 异常情况下的打印
环绕之前.........
before...
返回订单信息
afterThrowing...
after...
由此可见:
- 正常情况:环绕通知->前置通知->切入点方法->后置通知->最终通知->环绕通知
- 异常情况:环绕通知->前置通知->切入点方法->异常通知->最终通知
判断是否登录
这里将认证放入前置通知
改造一下前置通知
@Before(value = "loginAdvicePointcut()")
public void before() {
System.out.println("before...");
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 从请求头中获取用户信息
String user = sra.getRequest().getHeader("user");
if (user == null) {
System.out.println("用户未登录");
throw new RuntimeException("用户为登录");
}
System.out.println(user + "已登录");
}
注:如何是环绕通知,可以直接环绕一个值
测试一下
# 不带用户信息
环绕之前.........
before...
用户未登录
直接报错了
# 带用户信息
环绕之前.........
before...
zhangsan已登录
返回订单信息
afterReturning...
after...
环绕之后.........
切入点表达式
execute表达式
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
语法结构: execution([权限修饰符] [返回类型(可省略)] [类全路径] [方法名称]([参数列表]) )
例子如下:
例1:对 com.bkhb.dao.BookDao 类里面的 add 进行增强
execution(* com.bk.dao.BookDao.add(..))
例2:对 com.bk.dao.BookDao 类里面的所有的方法进行增强
execution(* com.bk.dao.BookDao.* (..))
例3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.bk.dao.*.* (..))

浙公网安备 33010602011771号