Spring AOP的介绍与实现

文章目录

  • [Spring AOP](#Spring AOP)
    • [1. Spring AOP概念](#1. Spring AOP概念)
    • [2. Spring AOP的作用](#2. Spring AOP的作用)
    • 3.AOP的组成
    • [4. Spring AOP的实现](#4. Spring AOP的实现)
      • [4.1 添加Spring AOP依赖](#4.1 添加Spring AOP依赖)
      • [4.2 定义切面(创建切面类)](#4.2 定义切面(创建切面类))
      • [4.3 定义切点(配置拦截规则)](#4.3 定义切点(配置拦截规则))
        • [4.3.1 切点表达式语法](#4.3.1 切点表达式语法)
      • [4.4 定义通知的实现](#4.4 定义通知的实现)
    • [5. Spring AOP实现原理](#5. Spring AOP实现原理)

Spring AOP

1. Spring AOP概念

AOP (Aspect Oriented Programming) :⾯向切⾯编程,是⼀种思想,它是对某⼀类事情的集中处理。

举个例子:

当我们进行用户登录权限的校验,在需要进行校验的页面中,我们都各⾃实现或调⽤⽤户验证的⽅法,有了 AOP 之后,我们只需要在某⼀处配置⼀下,需要登录校验的页面就可以自主实现,不再需要每个方法中都写相同的登录校验。

AOP与Spring AOP的关系和IoC和DI类似,AOP是一种思想,而Spring AOP是一个具体的框架对AOP的实现。

2. Spring AOP的作用

对于以上的登录校验例子来说,我们使用Spring AOP我们可以减少代码的重复,提高我们程序员开发的效率,在具体开发中它可以实现以下功能:

  1. 统一日志记录
  2. 统一方法执行时间统计
  3. 统一的返回格式设置
  4. 统一的异常处理
  5. 事务开启和提交
  6. 统一登录校验等

对于我们对象来说,AOP扩展了它的能力,在某个方面我们可以说AOP是OOP(面向对象编程)的补充和完善,它们都是一种思想。

3.AOP的组成

  • 切⾯(Aspect):切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。 在程序中就是一个处理某方面具体问题的类,类中包含很多方法,这些方法就是切点和通知。

  • 连接点(Join Point):应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是方法调用时,抛出异常时或者修改字段时。切⾯代码可以利⽤这些点插⼊到应用的正常流程之中,并添加新的行为。它是可能会触发AOP的所有点(请求)。

  • 切点(Pointcut):切点是提供一组规则来匹配连接点,给满足规则的连接点添加通知。 切点就是用来进行主动拦截的规则(配置)然后触发AOP。

  • 通知(Advice):切面的工作被称为通知,定义了切⾯是什么,何时使用,描述了切⾯要完成的工作,还解决何时执行这个工作问题。也就是程序中被拦截请求触发的具体动作,在通知中的的具体业务代码。

通知的分类:使用注解可以实现

  • 前置通知:在执行目标方法之前执行的方法,@Before实现。比如我们要请求某页面要进行的登录检验相关方法,登录校验就是前置通知通过。请求页面的方法就是目标方法。
  • 后置通知:在执行目标方法之后执行的方法,通过@After实现。
  • 异常通知:在执行目标方法出现异常时执行的方法,通过@AfterThrowing实现。
  • 返回通知:目标方法执行了返回数据时(return)执行的通知,@AfterReturning实现。
  • 环绕通知:在目标方法执行的周期范围内(执行之前,执行中,执行后)都可以执行的。通过@Around实现。

4. Spring AOP的实现

4.1 添加Spring AOP依赖

首先创建一个Spring Boot项目,在创建过程中我们没有Spring AOP框架可选,需要在pom.xml文件中自己添加Spring AOP 依赖。

添加依赖,在Maven仓库中根据我们创建的spring boot项目添加以下依赖:

由于我使用的spring版本是2.7.13,所以我选用的版本也是2.7.13。

4.2 定义切面(创建切面类)

对于切面我使用的注解是@Component,让该类醉着框架启动。通过@Aspect表示该类是一个切面;

java 复制代码
package com.example.demo_aop.component;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
 * @author zq
 * @date 2023-07-19 20:04
 */
@Component
@Aspect
public class UserAspect {
    
}

4.3 定义切点(配置拦截规则)

定义切点通过@Poincut注解,使用Aspect的表达式语法;该方法是空方法只起到表示作用,指向后面多个对应的通知。

java 复制代码
  //定义切点,使用Aspect的表达式语法
//    表示拦截该UserController下所有方法
    @Pointcut("execution(* com.example.demo_aop.controller.UserController.*(..))")
    public void poincut(){

    }

4.3.1 切点表达式语法

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

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

*号可以匹配任意字符,只匹配一个元素如包、类、方法、方法参数;...符号可以匹配任意字符,多个元素,在表示类时,需要与 星号联合使用;+号表示按照类型匹配指定类的所有类,必须跟在类名后面。如com.example.User+,就表示继承该类的所有子类和本身。

** 注意:**

  1. 修饰符和异常通常可以省略,返回值不能省略。
  2. 在我们上述代码中的表达式就省略了修饰符,用*号表示返回值类型。
  3. 返回值与包是两个东西所以加上了空格
  4. 在我们上述代码表达式省略了异常。括号中的点点表示匹配任意参数的方法。

4.4 定义通知的实现

以下实现前置通知和后置通知;注解中对应切点的方法名。

java 复制代码
//    表示该通知针对该方法
//    前置通知
    @Before("pointcut()")
    public void beforeAdvice(){
        System.out.println("执行力前置通知");
    }
//    后置通知
    @After("pointcut()")
    public void AfterAdvice(){
        System.out.println("执行了后置通知");
    }

解释说明:我们不在切点中实现具体方法,是因为我们实现的方法有很多种,所以通过通知进行实现,它们具有1对多的关系。

在UserController类中创建方法检验AOP执行过程:

java 复制代码
package com.example.demo_aop.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zq
 * @date 2023-07-19 20:25
 */

@RequestMapping("/user")
@RestController
public class UserController {

    @RequestMapping("/hi")
    public String sayHi(String name){
        System.out.println("执行了sayHi方法");
        return "Hi" + name;
    }

    @RequestMapping("/hello")
    public String sayHello(String name){
        System.out.println("执行了sayHello方法");
        return "Hello" + name;
    }


}

如图当我们启动程序,访问http://localhost:8080/user/hi时,控制台输出结果符合AOP执行过程

同理我们可以在切面类中也实现环绕通知,指的注意的是我们在环绕通知中必须通过 object = joinPoint.proceed(); 才能执行目标方法,不然只是执行环绕通知 ;

java 复制代码
//    环绕通知
    @Around("poincut()")
//    它必须有返回值返回给框架
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object object = null;
        System.out.println("进入环绕通知");
//        执行目标方法
        object = joinPoint.proceed();
        System.out.println("退出环绕通知");
        return object;
    }

执行结果如下:

5. Spring AOP实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。Spring AOP ⽀持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类。

也就是说我们本来要访问某个对象,但是我们Spring AOP构造了一个对应的代理类,我们访问时会通过代理类来访问我们的目标对象,不能直接访问

如图所示:对于我们以上代码来说我们本来是访问UserController类,但是我们不能直接访问了,而是通过访问代理类访问。

两种代理方式的区别:

  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运行时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样接⼝来实现,只是该代理类是在运行期时,动态的织⼊统⼀的业务逻辑字节码来完成。
  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的生成代理类对象。
相关推荐
极限实验室5 分钟前
INFINI Labs 产品更新 | INFINI Console 1.29.6 发布 – 优化监控图表异常毛刺等
数据库·产品
先睡8 分钟前
优化MySQL查询
数据库·sql
一头生产的驴12 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao19 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78722 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
小张是铁粉25 分钟前
oracle的内存架构学习
数据库·学习·oracle·架构
专注API从业者29 分钟前
构建淘宝评论监控系统:API 接口开发与实时数据采集教程
大数据·前端·数据库·oracle
藏在歌词里32 分钟前
数据库-元数据表
数据库
小乌龟不会飞2 小时前
Ubuntu 安装 etcd 与 etcd-cpp-apiv3
数据库·etcd
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展