【springBoot】springAOP

AOP的概述

AOP是面向切面编程。切面就是指某一类特定的问题,所以AOP也可以理解为面向特定方法编程。AOP是一种思想,拦截器,统一数据返回和统一异常处理是AOP思想的一种实现。简单来说:AOP是一种思想,对某一类事务的集中处理。

spring对AOP进行了实现,并且提供了一些API,这就是spring AOP.

spring AOP的简单使用

预先准备:

@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        String sum = "";
        for (int i = 1; i <= 10000; i++) {
            sum += 'a';
        }
        return "t1";
    }

    @RequestMapping("/t2")
    public String t2(){
        StringBuilder sum = new StringBuilder();
        for (int i = 1; i <= 10000; i++) {
            sum.append('a');
        }
        return "t2";
    }
}
  1. 引入springAOP依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 编写AOP程序
@Slf4j
@Aspect//表示是一个切面类
@Component
public class TimeAspect {
    @Around("execution(* com.example.aopdemo.controller.*.*(..))")//作用域
    public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();//方法执行前的逻辑
        Object result = joinPoint.proceed();//执行目标方法
        long end = System.currentTimeMillis();//方法执行后的逻辑
        log.info(joinPoint + "消耗时间:" + (end - start) + "ms");
        return result;
    }
}

我们通过AOP⼊⻔程序完成了业务接⼝执⾏耗时的统计.

@Around:环绕通知,在⽬标⽅法的前后都会被执⾏.后⾯的表达式表⽰对哪些⽅法进⾏增强

优点:

  • 代码⽆侵⼊:不修改原始的业务⽅法,就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变
  • 减少了重复代码
  • 提⾼开发效率
  • 维护⽅便

spring AOP 详解

spring AOP 的核心概念(了解)

  1. 切点:一组规则,通过表达式来描述
  2. 连接点: 切面要作用的方法,目标方法切点描述的方法.
  3. 通知:具体的逻辑,要做什么事情
  4. 切面:切点+通知

image-20240201200843227

com.example.aopdemo.controller目录下的方法就是连接点。

切点和连接点的关系:连接点是满⾜切点表达式的元素.切点可以看做是保存了众多连接点的⼀个集合.

spring AOP通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行 --> 使用最多
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing:异常后通知,此注解标注的通知方法发⽣异常后执行
@Slf4j
@Aspect
@Component
public class AspectDemo {
    //定义切点
    @Pointcut("execution(* com.example.aopdemo.controller.*.*(..))")
    public void pt(){
    }
    @Before("pt()")//后置通知
    public void doBefore(){
        log.info("执行AspectDemo doBefore……");
    }
    @After("pt()")//前置通知
    public void doAfter(){
        log.info("执行AspectDemo doAfter……");
    }
    @AfterReturning("pt()")//返回后通知
    public void doAfterReturning(){
        log.info("执行AspectDemo doAfterReturning……");
    }
    @AfterThrowing("pt()")//异常后通知
    public void doAfterThrowing(){
        log.info("执行AspectDemo doAfterThrowing……");
    }
    @SneakyThrows
    @Around("pt()")//环绕通知
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("执行AspectDemo doAround前……");
        Object result = joinPoint.proceed();
        log.info("执行AspectDemo doAround后……");
        return result;
    }
}

正常执行结果:

先执行around,再执行before。先执行after,再执行Around。

image-20240201204352259

异常执行结果:

当发生异常时,不执行AfterReturning,也不执行Around的方法后的逻辑

image-20240201204522479

@PointCut,定义切点

如果其他类需要使用,需要把切点声明为public.使用时,类的全限定名称+切点名称 ==> 包+类名

@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    @After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
    public void doAfter(){
        log.info("执行AspectDemo1 doAfter……");
    }
}

切面的优先级

定义三个切面类:

@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    @Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
    public void doBefore(){
        log.info("执行AspectDemo1 doBefore……");
    }
    @After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
    public void doAfter(){
        log.info("执行AspectDemo1 doAfter……");
    }
}
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
    @Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
    public void doBefore(){
        log.info("执行AspectDemo2 doBefore……");
    }
    @After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
    public void doAfter(){
        log.info("执行AspectDemo2 doAfter……");
    }
}
@Slf4j
@Aspect
@Component
public class AspectDemo3 {
    @Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
    public void doBefore(){
        log.info("执行AspectDemo3 doBefore……");
    }
    @After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
    public void doAfter(){
        log.info("执行AspectDemo3 doAfter……");
    }
}

存在多个切面类时,默认按照切面类的类名字母排序:

  • @Before通知:字母排名靠前的先执行
  • @After通知:字母排名靠前的后执行

image-20240201211100129

优先级高:先执行before,后执行After

Spring给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order。使用@Order时,数字越小,优先级越高

@Order(2)
public class AspectDemo1 {
}
@Order(3)
public class AspectDemo2 {
}
@Order(1)
public class AspectDemo3 {
}

执行结果:

image-20240201211631225

@Order控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法.

image-20240201211754075

切点表达式

切点表达式常见有两种表达方式

  1. execution(…):根据方法的签名来匹配
  2. @annotation(…):根据注解匹配

execution表达式上面已经用过.

主要介绍切点表达式支持通配符表达:

    • :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法去参数
    • 包名使用 * 表示任意包(一层包使用一个*)
    • 类名使用 * 表示任意类
    • 返回值使用 *表示任意返回值类型
    • 方法名使用 * 表示任意方法
    • 参数使用 * 表示一个任意类型的参数
  1. … :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

    • 使用 … 配置包名,标识此包以及此包下的所有子包
    • 可以使用 … 配置参数,任意个任意类型的参数

execution表达式更适用有规则的,如果我们要匹配多个无规则的方法,比如:TestController中的t1和UserController中的u1()这两个方法。这个时候我们使用execution这种切点表达式来描述就不是是很方便了。我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点。

实现步骤:

  1. 编写自定义注解
  2. 使用@annotation表达式来描述切点
  3. 在连接点的方法上添加自定义注解

准备测试代码:

@RestController
public class UserController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
    @RequestMapping("/h1")
    public String h1(){
        return "h1";
    }
    @RequestMapping("/h2")
    public String h2(){
        return "h2";
    }
}

编写自定义注解:

@Target({ElementType.METHOD})//标识了 Annotation所修饰的对象范围,即该注解可以用在什么地方(方法上)
@Retention(RetentionPolicy.RUNTIME)//指Annotation被保留的时间长短,标明注解的生命周期(运行时注解)
public @interface MyAspect {
}

使用@annotation表达式来描述切点

@Slf4j
@Component
@Aspect
public class MyAspectDemo {
    @Before("@annotation(com.example.aopdemo.aspect.MyAspect)")
    public void doBefore(){
        log.info("执行MyAspectDemo before...");
    }
    @After("@annotation(com.example.aopdemo.aspect.MyAspect)")
    public void doAfter1(){
        log.info("执行MyAspectDemo doAfter...");
    }
	//所有使用RequestMapping注解都会触发
    @After("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void doAfter(){
        log.info("执行RequestMapping doAfter...");
    }
}

在连接点的方法上添加自定义注解:

@MyAspect    
@RequestMapping("/h1")
public String h1(){
    return "h1";
}

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值