Spring AOP快速入门及开发步骤

 更新时间:2024年10月22日 14:48:03   作者:gobeyye  
Spring AOP(面向切面编程)核心概念包括切面(Aspect)、连接点(JoinPoint)、切点(Pointcut)、通知(Advice)等,通过在不改变原代码的情况下,对方法进行增强,实现了代码的解耦和功能扩展,本文带来大家掌握Spring 中 AOP 的开发步骤,感兴趣的朋友一起看看吧

Spring 框架有两大核心 IoC,AOP。在前面我们已经学习过了 IoC 的相关知识,今天就让我们开始 AOP 的学习。

一、AOP 概述

Aspect Oriented Programming(面向切面编程)。

切面就是指某一类特定问题,所以 AOP 也可以理解为面向特定方法编程。

**AOP 是一种思想,是对某一类事情的集中处理。**Spring AOP 是其中的一种实现方式。

AOP 的作用:在程序运行期间,在不修改源代码的基础上,对已有方法进行增强(无侵入性:解耦)。

二、Spring AOP 快速入门

我们先通过下面的程序体验下 AOP 的开发,并掌握 Spring 中 AOP 的开发步骤。

2.1 引入 AOP 依赖:

在 pom.xml 文件中添加配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 编写 AOP 程序:

@Aspect
@Slf4j
@Component
public class TestAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("方法执行前执行");
        Object result = joinPoint.proceed();
        log.info("方法执行后执行");
        return result;
    }
}

controller 类:

@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
    @RequestMapping("/t1")
    public void test1(){
        log.info("我是 test1");
    }
}

调用 controller 中的 test1 方法。

结果如下:

对程序进行简单的讲解:

  • @Aspect:标识这是一个切面类。
  • @Around:环绕通知,在目标方法的前后都会被执行。后面的表达式表示对哪些方法进行增强。
  • ProceedingJoinPoint.proceed()让原始方法执行。

整个代码划分为三部分。

通过上面的程序,我们也可以感受到 AOP 面向切面编程的一些优势:

  • 代码无侵入:不修改原始的业务方法,就可以对原始的业务方法进行了功能的增强或者是功能的改变。
  • 减少了重复代码。
  • 提高开发效率。
  • 维护方便。

三、Spring AOP 详解

3.1 Spring AOP 核心概念:

3.1.1 切点(Pointcut):

切点(Pointcut),也称之为"切入点"。

Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述),告诉程序对哪些方法来进行功能增强。

上面的表达式 execution(* com.example.demo.controller..(…)) 就是切点表达式。

3.1.2 连接点(Join Point):

满足切点表达式规则的方法,就是连接点。也就是可以被 AOP 控制的方法。

切点和连接点的关系:

连接点是满足切点表达式的元素。切点可以看做是保存了众多连接点的一个集合。

3.1.3 通知(Advice):

通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)。

在 AOP 面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。

3.1.4 切面(Aspect):

切面(Aspect)= 切点(Pointcut)+ 通知(Advice)。

通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。

切面既包含了通知逻辑的定义,也包括了连接点的定义。

切面所在的类,我们一般称为切面类(被 @Aspect 注解标识的类)。

3.2 通知类型:

上面我们讲了什么是通知,接下来学习通知的类型。@Around 就是其中一种通知类型,表示环绕通知。Spring 中 AOP 的通知类型有以下几种:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前后都被执行。
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行。
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行。
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法返回后被执行,有异常不会执行。
  • @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行。

没有异常的运行顺序:

程序正常运行的情况下,@AfterThrowing 标识的通知方法不会执行。

出现异常的运行顺序:

@AfterReturning 标识的通知方法不会执行,@AfterThrowing 标识的通知方法执行了。

@Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)。

注意:

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。
  • 一个切面类可以有多个切点。

3.3 @PointCut:

Spring 提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可,便于后续代码的维护。

@Aspect
@Slf4j
@Component
public class TestAspect {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void pt(){}
    @Around("pt()")
    public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("方法执行前执行");
        Object result = joinPoint.proceed();
        log.info("方法执行后执行");
        return result;
    }
}

当切点定义使用 private 修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把 private 改为 public。引用方式为:全限定类名.方法名()。

@Slf4j
@Component
@Aspect
public class TestAspect2 {
    @Before("com.example.demo.aspect.TestAspect.pt()")
    public void doBefore() {
        log.info("执⾏ TestAspect2 -> Before ⽅法");
    }
}

3.4 切面优先级 @Order:

当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法。当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?

我们通过程序来进行验证。

@Slf4j
@Component
@Aspect
public class TestAspect2 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt(){}
    //前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 TestAspect2 -> Before 方法");
    }
    //后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 TestAspect2 -> After 方法");
    }
}
@Aspect
@Component
@Slf4j
public class TestAspect3 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt(){}
    //前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 TestAspect3 -> Before 方法");
    }
    //后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 TestAspect3 -> After 方法");
    }
}
@Aspect
@Component
@Slf4j
public class TestAspect4 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt(){}
    //前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 TestAspect4 -> Before 方法");
    }
    //后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 TestAspect4 -> After 方法");
    }
}

运行上面程序:

通过上述程序的运行结果,可以看出:

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

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

但这种方式不方便管理,我们的类名更多还是具备一定含义的。

Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order。

@Slf4j
@Component
@Aspect
@Order(10)
public class TestAspect2 {
    //代码省略
}
@Aspect
@Component
@Slf4j
@Order(5)
public class TestAspect3 {
    //代码省略
}
@Aspect
@Component
@Slf4j
@Order(1)
public class TestAspect4 {
    //代码省略
}

运行程序:

通过上述程序的运行结果,得出结论:

@Order 注解标识的切面类,执行顺序如下:

  • @Before 通知:数字越小先执行。
  • @After 通知:数字越大先执行。

@Order 的执行顺序可以抽象成下面这张图:

3.5 切点表达式:

上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。

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

  • execution:根据方法的签名来匹配。
  • @annotation:根据注解匹配。

3.5.1 execution 表达式:

execution() 是最常用的切点表达式,用来匹配方法,语法为:

execution (<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

其中:访问修饰符和异常可以省略。

切点表达式支持通配符表达:

  • * :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)。
  • 包名使用 * 表示任意包(一层包使用一个 * )。类名使用 * 表示任意类。返回值使用 * 表示任意返回值类型。
  • 方法名使用 * 表示任意方法(参数可能有限制)。
  • 参数使用 * 表示一个任意类型的参数。

..:匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数。

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

3.5.2 @annotation:

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

实现步骤:

  • 编写自定义注解。
  • 使用 @annotation 表达式来描述切点。
  • 在方法上添加自定义注解。

创建一个注解类(和创建 Class 文件一样的流程,选择 Annotation 就可以了)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

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

@Component
@Slf4j
@Aspect
public class MyAspectDemo {
    @Before("@annotation(com.example.demo.aspect.MyAspect)")
    public void doBefore(){
        log.info("我是 MyAspectDemo");
    }
}

在方法上添加自定义注解。

@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public void test1(){
        log.info("我是 test1");
    }
}

运行程序,访问 test1 方法。

3.6 Spring AOP 的实现方式(常见面试题):

  • 基于注解 @Aspect。
  • 基于自定义注解(@annotation)。
  • 基于 Spring API(通过 xml 配置的方式,自从 SpringBoot 广泛使用之后,这种方法几乎看不到了)。
  • 基于代理来实现(更加久远的一种实现方式,写法笨重,不建议使用)。

四、代理模式

Spring AOP 是基于动态代理来实现 AOP 的。

代理模式,也叫委托模式。

定义:

为其他对象提供一种代理,以控制对这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。

根据代理的创建时期,代理模式分为静态代理动态代理

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

到此这篇关于一文掌握Spring AOP的文章就介绍到这了,更多相关Spring AOP内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring P标签的使用详解

    Spring P标签的使用详解

    这篇文章主要介绍了Spring P标签的使用详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Linux(centos7)安装jdk1.8的详细步骤

    Linux(centos7)安装jdk1.8的详细步骤

    Linux的使用相信大家都要用到java吧,在使用java前我们得先安装jdk以及配置环境变量等工作,下面这篇文章主要给大家介绍了关于Linux(centos7)安装jdk1.8的详细步骤,需要的朋友可以参考下
    2023-10-10
  • Java实现截图小工具的完整代码

    Java实现截图小工具的完整代码

    这篇文章主要介绍了Java实现截图小工具的完整代码,用Java的图形用户界面GUI技术写了一个电脑截图小工具,本程序代码简单,涉及到异常处理,事件处理,图形用户界面等,需要的朋友可以参考下
    2022-05-05
  • Springsecurity Oauth2如何设置token的过期时间

    Springsecurity Oauth2如何设置token的过期时间

    如果用户在指定的时间内有操作就给token延长有限期,否则到期后自动过期,如何设置token的过期时间,本文就来详细的介绍一下
    2021-08-08
  • java 获取内存使用率的流程实例详解

    java 获取内存使用率的流程实例详解

    这篇文章主要为大家介绍了java 获取内存使用率的流程实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 用Java编程输出万年历的功能实现

    用Java编程输出万年历的功能实现

    这篇文章主要介绍了用Java编程输出万年历的功能实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • 如何用Jfinal连接多个数据库

    如何用Jfinal连接多个数据库

    这篇文章主要介绍了如何用Jfinal连接多个数据库,帮助大家更好的理解和学习使用Jfinal,感兴趣的朋友可以了解下
    2021-03-03
  • Spring Boot统一异常处理最佳实践(拓展篇)

    Spring Boot统一异常处理最佳实践(拓展篇)

    这篇文章主要给大家介绍了关于Spring Boot统一异常处理最佳实践(拓展篇)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-02-02
  • springboot多数据源配置及切换的示例代码详解

    springboot多数据源配置及切换的示例代码详解

    这篇文章主要介绍了springboot多数据源配置及切换,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • java针对于时间转换的DateUtils工具类

    java针对于时间转换的DateUtils工具类

    这篇文章主要为大家详细介绍了java针对于时间转换的DateUtils工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12

最新评论