SpringBoot中的Aop用法示例详解

目录
  • 什么是Aop
  • Aop概述
    • 相关注解
    • 相关概念
  • 实例1
  • @PointCut 表达式详解
    • execution(表达式)
    • within
    • this
    • target
    • args:
    • args用来匹配方法参数
    • @annotation:
    • @args:
    • 逻辑运算符
  • 实例2
  • 环绕通知

什么是Aop

主要介绍springboot中aop的使用,用过Spring框架的都知道,aop是spring框架的两大核心功能之一,还有一个就是ioc,下面我们就springboot中如何引入aop来做一下探讨

  • 引入AOP依赖包后,一般来说并不需要去做其他配置,使用过Spring注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。
  • 因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy
  • Springboot中有关AOP相关的自动配置包为:AopAutoConfiguration

因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy

Springboot中有关AOP相关的自动配置包为:AopAutoConfiguration

Aop概述

相关注解

  • @Component :将当前类注入到Spring容器内
  • @Aspect :表明是一个切面类
  • @Before :前置通知,在方法执行之前执行
  • @After :后置通知,在方法执行之后执行
  • @AfterRuturning :返回通知,在方法返回结果之后执行
  • @AfterThrowing :异常通知,在方法抛出异常之后执行
  • @Around :环绕通知,围绕着方法执行
  • @Pointcut :切入点,PointCut(切入点)表达式有很多种,其中execution用于使用切面的连接点。

上面所提到的五种通知方法中,除了环绕通知外,其他的四个通知注解中,加或者不加参数JoinPoint都可以,如果有用到JoinPoint的地方就加,用不到就可以不加。

JoinPoint:里包含了类名、被切面的方法名,参数等属性。

环绕通知:参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

返回通知:可以加returning = “XXX”,XXX即为被切入方法的返回值,本例中是controller类中方法的返回值。

异常通知:可以加throwing = “XXX”,供读取异常信息。

返回通知异常通知只会执行一个,如果产生异常,返回通知就不执行,后置通知一定会执行

== 环绕通知一般单独使用,环绕通知可以替代上面的四种通知,后面单独介绍。==

相关概念

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点,通俗的说就是被增强类中的所有方法
  • PointCut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义,通俗的说就是被增强类中的被增强的方法,因为被增强类中并不是所有的方法都被代理了
  • Advice(通知/增强):所谓通知是指拦截到 Joinpoint (被增强的方法)之后所要做的事情就是通知,通俗的说就是对被增强的方法进行增强的代码
  • 通知的类型:前置通知,返回通知,异常通知,后置通知,环绕通知
  • 前置通知:在被代理方法执行之前执行
  • 返回通知:在被代理方法执行之后执行
  • 异常通知:在被代理方法执行出错的时候执行
  • 后置通知:无论怎样都会执行
  • Aspect(切面):是切入点和通知(引介)的结合,通俗的说就是建立切入点和通知方法在创建时的对应关系

注意:返回通知和异常通知只能有一个会被执行,因为发生异常执行异常通知,然后就不会继续向下执行,自然后置通知也就不会被执行,反之亦然。

实例1

使用aop来完成全局请求日志处理。

第一步:使用Spring Initializr快速创建一个springboot web项目,名称为testaop
第二步:引入aop相关的依赖

<!--aop相关的依赖引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第三步:创建个controller

@RestController
public class HelloController {
    @RequestMapping("/helloAop")
    public Object hello(){
        return "hello aop";
    }
    @RequestMapping("/helloError")
    public Object helloError(){
        return 1/0;
    }
}

第四步:创建一个aspect切面类

@Aspect
@Component
public class MyAop {
    //切入点:待增强的方法
    @Pointcut("execution(public * com.aismall.testaop.controller.*.*(..))")
    //切入点签名
    public void log(){
        System.out.println("pointCut签名。。。");
    }
    //前置通知
    @Before("log()")
    public void deBefore(JoinPoint jp) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("CLASS_METHOD : " + jp);
        System.out.println("ARGS : " + Arrays.toString(jp.getArgs()));

    }
    //返回通知
    @AfterReturning(returning = "ret", pointcut = "log()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("返回通知:方法的返回值 : " + ret);
    }

    //异常通知
    @AfterThrowing(throwing = "ex", pointcut = "log()")
    public void throwss(JoinPoint jp,Exception ex){
        System.out.println("异常通知:方法异常时执行.....");
        System.out.println("产生异常的方法:"+jp);
        System.out.println("异常种类:"+ex);
    }

    //后置通知
    @After("log()")
    public void after(JoinPoint jp){
        System.out.println("后置通知:最后且一定执行.....");
    }
}

第六步:启动项目

  • 请求链接:http://localhost:8080/helloAop
  • 控制台返回的结果:
URL : http://localhost:8080/helloAop
HTTP_METHOD : GET
CLASS_METHOD : execution(Object com.aismall.testaop.controller.HelloController.hello())
ARGS : []
返回通知:方法的返回值 : hello aop
后置通知:最后且一定执行.....
  • 请求链接:http://localhost:8080/helloError
  • 控制台返回的结果(部分):
URL : http://localhost:8080/helloError
HTTP_METHOD : GET
CLASS_METHOD : execution(Object com.aismall.testaop.controller.HelloController.helloError())
ARGS : []
异常通知:方法异常时执行.....
产生异常的方法execution(Object com.aismall.testaop.controller.HelloController.helloError())
异常种类java.lang.ArithmeticException: / by zero
后置通知:最后且一定执行.....

分析:返回通知和异常通知只会执行一个,后置通知一定会执行。

@PointCut 表达式详解

  • PointCut:切入点,指哪些方法需要被执行AOP,PointCut表达式可以有一下几种方式 execution

execution(表达式)

  • 表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
  • 标准的表达式写法范例:
public void com.aismall.testaop.controller.HelloController.helloAop()

访问修饰符可以省略

void com.aismall.testaop.controller.HelloController.helloAop()

返回值可以使用通配符*,表示任意返回值

* com.aismall.testaop.controller.HelloController.helloAop()

包名可以使用通配符,表示任意包,但是有几级包,就需要写几个*.

* *.*.*.*.HelloController.helloAop()

包名可以使用…表示当前包及其子包

* *...HelloController.helloAop()

类名和方法名都可以使用*来实现通配

* *..*.*()
  • 参数列表:
  • 可以直接写数据类型:
  • 基本类型直接写名称 :例如,int
  • 引用类型写包名.类名的方式 :例如,java.lang.String
  • 可以使用通配符*表示任意类型,但是必须有参数
  • 可以使用…表示有无参数均可,有参数可以是任意类型

全通配写法:* ….*(…)

within

within(表达式):是用来指定类型的,指定类型中的所有方法将被拦截

  • 表达式:包名.包名.包名…类名
  • 标准的表达式写法范例:
com.aismall.testaop.controller.HelloController

举例:匹配HelloController类对应对象的所有方法,并且只能是HelloController的对象,不能是它的子对象。

within(com.aismall.testaop.controller.HelloController)

也可以使用通配符 * :匹配com.aismall包及其子包下面的所有类的所有方法。

within(com.aismall…*)

this

  • SpringAOP是基于代理的,this就代表代理对象,语法是this(type),当生成的代理对象可以转化为type指定的类型时表示匹配。
  • this(com.aismall.testaop.controller.HelloController) 匹配生成的代理对象是HelloController类型的所有方法的外部调用

target

  • SpringAOP是基于代理的,target表示被代理的目标对象,当被代理的目标对象可以转换为指定的类型时则表示匹配。
  • target(com.aismall.testaop.controller.HelloController) 匹配所有被代理的目标对象能够转化成HelloController类型的所有方法的外部调用。

args:

args用来匹配方法参数

args() 匹配不带参数的方法
args(java.lang.String) 匹配方法参数是String类型的
==args(…) == 带任意参数的方法
args(java.lang.String,…) 匹配第一个参数是String类型的,其他参数任意。最后一个参数是String的同理

@annotation:

带有相应注解的方法,比如对标有@Transactional注解的方法进行增强

@annotation(org.springframework.transaction.annotation.Transactional)
  • @within和 @target针对类的注解
  • @annotation 针对方法的注解

@args:

参数带有相应标注的任意方法,比如@Transactional

@args(org.springframework.transaction.annotation.Transactional)

逻辑运算符

表达式可由多个切点函数通过逻辑运算组成

  • 基本使用:使用log()方法相当于直接使用上面的表达式
//PointCut表达式
@Pointcut("execution(public * com.aismall.testaop.controller.HelloController.*(..))")
//PointCut签名
public void log(){
}

PointCut中的运算符:PointCut中可以使用 && 、|| 、! 运算

@Pointcut("execution(public * com.aismall.testaop.controller.HelloController.*(..))")
public void cutController(){
}

@Pointcut("execution(public * com.aismall.testaop.service.UserService.*(..))")
public void cutService(){
}

//使用 && 运算符,则cutAll()的作用等同于cutController 和 cutService 之和
@Pointcut("cutController() && cutService()")
public void cutAll(){
}

实例2

@annotation方式

第一步:编写一个自定义注解

//表示次注解可以标注在类和方法上
@Target({ElementType.METHOD, ElementType.TYPE})
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLogAnnotation {
    //定义一个变量,可以接受参数
    String desc() default " ";
}

第二步:在HelloController类中添加一个方法

@RequestMapping("helloAnnotation")
//标有这个注解的方法会被增强
@MyLogAnnotation(desc = "@Annotation")
public Object helloAnnotation() {
    return "hello annotation";
}

第三步:切面类

@Aspect
@Component
public class MyAopAnnotation {
    //切入点:增强标有MyLogAnnotation注解的方法
    @Pointcut(value="@annotation(com.aismall.testaop.MyAnnotation.MyLogAnnotation)")
    //切入点签名
    public void logAnnotation(){
        System.out.println("pointCut签名。。。");
    }
    //前置通知
    @Before("logAnnotation()")
    public void deBefore(JoinPoint jp) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
    }
    //返回通知
    @AfterReturning(returning = "ret", pointcut = "logAnnotation()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("返回通知:方法的返回值 : " + ret);
    }

    //异常通知
    @AfterThrowing(throwing = "ex", pointcut = "logAnnotation()")
    public void throwss(JoinPoint jp,Exception ex){
        System.out.println("异常通知:方法异常时执行.....");
        System.out.println("产生异常的方法:"+jp);
        System.out.println("异常种类:"+ex);
    }

    //后置通知
    @After("logAnnotation()")
    public void after(JoinPoint jp){
        System.out.println("后置通知:最后且一定执行.....");
    }
}

第四步:启动项目

  • 请求链接:http://localhost:8080/helloAnnotation
  • 控制台返回的结果:

URL : http://localhost:8080/helloAnnotation

返回通知:方法的返回值 : hello annotation

后置通知:最后且一定执行.....

环绕通知

spring的通知(Advice)中 一共有五种通知,之前已经介绍了四种,为什么不把环绕通知和它们放在一起说,因为环绕通知可以把前面的四种通知都表示出来,而且环绕通知一般单独使用

环绕通知的使用:

  • Spring框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
  • 该接口作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
  • 增强代码写在调用proceed()方法之前为前置通知,之后为返回通知,写在catch中为异常通知,写在finally中为后置通知

第一步:在HelloController类中添加一个方法

@RequestMapping("/helloAround")
public Object helloAround(){
    System.out.println("helloAround执行了。。。");
    return "hello around";
}

第二步:切面类

@Aspect
@Component
public class MyAroundAop {
    //切入点:待增强的方法
    @Pointcut("execution(public * com.aismall.testaop.controller.*.*(..))")
    //切入点签名
    public void logAround() {
        System.out.println("pointCut签名。。。");
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("logAround()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("通知类中的aroundAdvice方法执行了。。前置");

            rtValue = pjp.proceed(args);//明确调用切入点方法(切入点方法)

            System.out.println("通知类中的aroundAdvice方法执行了。。返回");
            System.out.println("返回通知:"+rtValue);

            return rtValue;
        } catch (Throwable e) {
            System.out.println("通知类中的aroundAdvice方法执行了。。异常");
            throw new RuntimeException(e);
        } finally {
            System.out.println("通知类中的aroundAdvice方法执行了。。后置");
        }
    }
}

第三步:启动项目

  • 请求链接:http://localhost:8080/helloAround
  • 控制台返回的结果:

通知类中的aroundAdvice方法执行了。。前置
helloAround执行了。。。
通知类中的aroundAdvice方法执行了。。返回
返回通知:hello around
通知类中的aroundAdvice方法执行了。。后置

到此这篇关于SpringBoot中的Aop用法的文章就介绍到这了,更多相关SpringBoot Aop用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot利用Aop捕捉注解实现业务异步执行

    目录 一.先说说线程的几种创建方式(简要列举) 二.再聊一聊spring自带的@Async注解实现异步任务的方法 三.那么就来设计一下怎么使用自定义的注解实现异步任务 在开发过程中,尽量会将比较耗时且并不会影响请求的响应结果的业务放在异步线程池中进行处理,那么到时什么任务在执行的时候会创建单独的线程进行处理呢?我们可以在对应的业务方法上打上自定义的注解,再利用AOP去捕捉这个注解,就可以利用环绕通知的特性进行异步的设置. 一.先说说线程的几种创建方式(简要列举) 1.继承Thread类,复写ru

  • Springboot项目快速实现Aop功能

    目录 前言 依赖引入 代码实现 核心注解和类 标记切入点的常用方式 Spring Aop的小技巧 Spring Aop注意事项 总结 前言 这篇文章的有几个关键点,第一,关于AOP的一些基础理论知识,在正式使用AOP前需要了解:第二,Springboot项目中怎么快速集成Aop功能的:第三,AOP的一些使用小技巧和注意事项. 依赖引入 Springboot引入AOP依赖包后,一般来说是不需要再做其他配置了,在比较低的版本或者有其他配置影响了AOP的相关功能,导致aop功能不生效,可以试试在启动类

  • SpringBoot搭配AOP实现自定义注解

    目录 1.springBoot的依赖 2.自定义注解的步骤 2.1定义注解类 2.2定义切面 2.3使用注解 3.知识点补充 3.1 关于Target注解补充 3.2 关于Retention注解补充 3.3 关于AOP的一些概念补充 3.4关于AOP中一些类及函数的使用 1.springBoot的依赖 确定项目中包含可以注解的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI

  • springboot实现拦截器的3种方式及异步执行的思考

    目录 springboot 拦截器 springboot 入门案例 maven 引入 启动类 定义 Controller 拦截器定义 基于 Aspect 基于 HandlerInterceptor 基于 ResponseBodyAdvice 测试 异步执行 定义异步线程池 异步执行的 Controller 思考 测试 反思 springboot 拦截器 实际项目中,我们经常需要输出请求参数,响应结果,方法耗时,统一的权限校验等. 本文首先为大家介绍 HTTP 请求中三种常见的拦截实现,并且比较一

  • Springboot项目Aop与拦截器与过滤器横向对比

    目录 前言 功能特性对比 过滤器 拦截器 Spring AOP 代码实现 过滤器实现 拦截器实现 AOP实现 验证结果 工作原理 总结 前言 伟人曾经说过,没有调查就没有发言权(好像是伟人说的,不管谁说的,这句话是正确的),有些东西看着简单,张口就来,但很有可能是错的.我个人的经验是,aop.过滤器.拦截器的实现方式很简单,一学就会,不用就忘,忘了再学,学了再忘,如此循环内耗何必呢?因此,如果你和我一样,有一颗强烈的好奇之心,那么不管多简单,动手敲起来吧,温故而知新呢. 功能特性对比 过滤器 过

  • Python中的Super用法示例详解

    目录 Python Super用法 附:super的典型用法 总结 Python Super用法 这篇文章我们来介绍一下 super,我相信大部分的人使用 super 都是使用这种方式: # 就是我有一个 class 比如说是 Male,然后继承另外一个 class 比如是 Person,然后我在这个 Male 也就是它的子类的 init 函数里面用 super().__init__() 来调用它父类的初识化函数 from objprint import op class Person: def

  • MySql中JOIN的用法示例详解

    目录 笛卡尔积:CROSS JOIN 内连接:INNER JOIN 左连接:LEFT JOIN 右连接:RIGHT JOIN 外连接:OUTER JOIN USING子句 自然连接:NATURE JOIN 上次面试被问到JOIN,自己都已经忘了课堂上讲的笛卡尔积那些就是JOIN,最近重新复习了一遍 JOIN的含义就如英文单词“join”一样,连接两张表,大致分为内连接,外连接,右连接,左连接,自然连接. 先创建两个表,下面用于示例 CREATE TABLE t_blog( id INT PRIM

  • Python标准库中的logging用法示例详解

    目录 1.logging的介绍 2.简单用法示例 3.日志级别 4.打印格式的各个参数 5.日志输出到指定文件 6.日志回滚(按照文件大小滚动) 7.日志回滚(按照时间滚动) 1.logging的介绍 logging是Python标准库中记录常用的记录日志库,通过logging模块存储各种格式的日志,主要用于输出运行日志,可以设置输出日志的等级.日志保存路径.日志文件回滚等. 2.简单用法示例 首先创建一个logger.py的文件,其里面的代码如下所示: import logging # 1.创

  • python中前缀运算符 *和 **的用法示例详解

    这篇主要探讨 ** 和 * 前缀运算符,**在变量之前使用的*and **运算符. 一个星(*):表示接收的参数作为元组来处理 两个星(**):表示接收的参数作为字典来处理 简单示例: >>> numbers = [2, 1, 3, 4, 7] >>> more_numbers = [*numbers, 11, 18] >>> print(*more_numbers, sep=', ') 2, 1, 3, 4, 7, 11, 18 用途: 使用 * 和

  • golang中的三个点 '...'的用法示例详解

    '-' 其实是go的一种语法糖. 它的第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数. 第二个用法是slice可以被打散进行传递. 下面直接上例子: func test1(args ...string) { //可以接受任意个string参数 for _, v:= range args{ fmt.Println(v) } } func main(){ var strss= []string{ "qwr", "234", "yui

  • Pandas中的 transform()结合 groupby()用法示例详解

    首先,假设我们有如下餐厅数据集: import pandas as pd df = pd.DataFrame({ 'restaurant_id': [101,102,103,104,105,106,107], 'address': ['A','B','C','D', 'E', 'F', 'G'], 'city': ['London','London','London','Oxford','Oxford', 'Durham', 'Durham'], 'sales': [10,500,48,12,2

  • Java中枚举类的用法示例详解

    目录 1.引入枚举类 2.实现枚举类 3.枚举类的使用注意事项 4.枚举的常用方法 5.enum细节 1.引入枚举类 Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等. Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割. 示例: enum Color { RED, GREEN, BLUE; } 2.实现枚举类 接下来我们来看一个一个简单的DEMO示例: /** * java枚举 */ p

  • SpringBoot中注解@AliasFor的使用详解

    目录 简介 用法1:注解的属性互为别名 简介 实例 用法2.继承父注解的属性,不重写属性名 简介 代码 用法3:继承父注解的属性,并重写属性名 简介 代码 简介 本文用示例介绍@AliasFor(别名)注解的用法. 用法1:注解的属性互为别名 简介 它可以注解到自定义注解的两个属性上,表示这两个互为别名,也就是说这两个属性其实同一个含义. 其中一个属性名必须是"value" 无论指明设置哪个属性名设置属性值,另一个属性名也是同样属性值,也可以缺省属性名. 若两个都指明属性值,要求值必须

  • Springboot自动扫描包路径来龙去脉示例详解

    我们暂且标注下Springboot启动过程中较为重要的逻辑方法,源码对应的spring-boot-2.2.2.RELEASE版本 public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBoo

  • 支持PyTorch的einops张量操作神器用法示例详解

    目录 基础用法 高级用法 今天做visual transformer研究的时候,发现了einops这么个神兵利器,决定大肆安利一波. 先看链接:https://github.com/arogozhnikov/einops 安装: pip install einops 基础用法 einops的强项是把张量的维度操作具象化,让开发者"想出即写出".举个例子: from einops import rearrange # rearrange elements according to the

随机推荐