Spring AOP(AOP概念,组成成分,实现,原理)

news2024/10/6 16:30:49

目录

1. 什么是Spring AOP?

2. 为什么要用AOP?

3. AOP该怎么学习?

3.1 AOP的组成

(1)切面(Aspect)

(2)连接点(join point)

(3)切点(Pointcut)

(4)通知(Advice)

 4. Spring AOP实现

4.1 添加 AOP 框架支持

​编辑

 4.2 定义切面

4.3 定义切点

4.4 定义通知

4.5 切点表达式说明 AspectJ

5.使用 AOP 统计 UserController 每个方法的执行时间 StopWatch

6. Spring AOP 实现原理

6.1 生成代理的时机 :织入(Weaving)

6.2 JDK 动态代理实现

6.3 CGLIB 动态代理实现

6.4 JDK 和 CGLIB 实现的区别(面试常问)


1. 什么是Spring AOP?

AOP(Aspect Oriented Programming):面向切面编程,它和 OOP(面向对象编程)类似。

面向切面编程就是面对某一方面、某个问题做集中的处理

针对某一类事情进行集中处理,这一类事情就是切面

比如用户登录权限的效验,在学习 AOP 之前,在需要判断用户登录的页面,都要各自实现或调用用户验证的方法,学习 AOP 之后,我们只需要在某一处配置一下,那么所有需要判断用户登录的页面就全部可以实现用户登录验证了,不用在每个方法中都写用户登录验证了

 AOP 是一种思想,而 Spring AOP 是实现(框架),这种关系和 IOC(思想)与 DI(实现)类似
 

 

2. 为什么要用AOP?

  • 采用AOP,我们可以不修改源代码,添加新的功能。我们单独编写独立的权限判断模块,并通过配置,将其配置到登录流程中(比如用户登录验证等)
  • 更加便捷,想象我们做一个类似CSDN这类博客系统,在之前我们除了登录、注册不需要验证用户是否登录,其余所有页面几乎都要验证,且在每个功能的代码都需要再写一遍,这就显得十分麻烦,对于这类功能统一的,且地方使用较多的功能,AOP会表现的更加出色

除了统一的用户登录判断之外,AOP还可以实现:

  • 统一日志记录
  • 统一方法执行时间统计
  • 统一的返回格式设置
  • 统一的异常处理
  • 事务的开启和提交等

也就是说使用AOP可以扩充多个对象的某个能力,所以AOP可以说是OOP(Object OrientedProgramming,面向对象编程)的补充和完善。

3. AOP该怎么学习?

  1. 学习 AOP 是如何组成
  2. 学习 Spring AOP 的使用
  3. 学习 Spring AOP 实现原理

3.1 AOP的组成

(1)切面(Aspect)

定义 AOP 是针对某个统一的功能的,这个功能就叫做一个切面,比如用户登录功能或方法的统计日志,他们就各是一个切面。切面是由切点和通知组成的。

通俗的理解就是,切面就是处理某一个具体问题的一个类,类中包含了很多方法,这些方法就是切点和通知

(2)连接点(join point)

所有可能触发 AOP(拦截方法的点)就称为连接点

(3)切点(Pointcut)

切点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知,总的来说就是,定义 AOP 拦截的规则的

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中一条一条的数据)

用来进行主动拦截的规则(配置)

(4)通知(Advice)

拦截到这个行为后要做什么事就是通知

 Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后悔通知本方法进行调用:

  • 前置通知使用@Before:通知方法会在目标方法调用之前执行
  • 后置通知使用@After:通知方法会在目标方法调用之后执行
  • 返回之后通知使用@AfterReturning通知方法会在目标方法返回后调用
  • 抛异常后通知@AfterThrowing:通知方法会在目标方法爬出异常之后调用
  • 环绕通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为

就相当于在一个生产型公司中

通知相当于底层的执行者,切点是小领导制定规则,切面是大领导制定公司的发展方向,连接点是属于一个普通的消费者用户

以CSDN的登录为例子:

 4. Spring AOP实现

Spring AOP 实现步骤

  1.     添加 Spring AOP 框架支持
  2.     定义切面和切点
  3.     实现通知

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的方法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。

4.1 添加 AOP 框架支持

创建Spring Boot项目时是没有Spring AOP框架可以选择的,这个没关系,咱们创建好项目之后,再在pom. xml中添加Spring AOP的依赖即可。

 4.2 定义切面

@Aspect // 告诉框架我是一个切面类
@Component // 随着框架的启动而启动
public class UserAspect {
}

4.3 定义切点

@Aspect // 告诉框架我是一个切面类
@Component // 随着框架的启动而启动
public class UserAspect {

    /**
     * 切点(配置拦截规则)
     */
    @Pointcut("execution(* com.example.demo.Controller.*.*(..))")
    public void pointcut() {
    }
}

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

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

4.4 定义通知

@Aspect // 告诉框架我是一个切面类
@Component // 随着框架的启动而启动
public class UserAspect {

    /**
     * 切点(配置拦截规则)
     */
    @Pointcut("execution(* com.example.demo.Controller.*.*(..))")
    public void pointcut() {
    }

    /**
     * 前置通知
     */
    @Before("pointcut()")
    public void beforeAdvice() {
        System.out.println("执行了前置通知~");
    }

    /**
     * 后置通知
     */
    @After("pointcut()")
    public void afterAdvice() {
        System.out.println("执行了后置通知~");
    }

    /**
     * 环绕通知
     */
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
        Object obj = null;
        System.out.println("进入环绕通知之前");
        // 执行目标方法
        try {
            obj = proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("退出环绕通知了");
        return obj;
    }
}

UserController实体类:

package com.example.demo.Controller;

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

/**
 * Created with IntelliJ IEDA.
 * Description:
 * User:86186
 * Date:2023-08-10
 * Time:16:43
 */
@RestController
@RequestMapping("/user")
public class UserController {

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

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

}

ArticleController实体类:

package com.example.demo.Controller;

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

/**
 * Created with IntelliJ IEDA.
 * Description:
 * User:86186
 * Date:2023-08-10
 * Time:16:45
 */

@RestController
@RequestMapping("/art")
public class ArticleController {

    @RequestMapping("/hi")
    public String sayHi() {
        System.out.println("文章的 sayHI~");
        return "Hi, world.";
    }

}

当浏览art/hi时:

 

此时控制台只有articleControlle中的打印,没有前置、后置通知

但是当我们去访问

再次刷新

4.5 切点表达式说明 AspectJ

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

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

 

AspectJ 语法(Spring AOP 切点的匹配语法):

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

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

AspectJ ⽀持三种通配符

  • * :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
  • … :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
  • + :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身

修饰符,一般省略

  •     public 公共方法
  •     *任意

返回值,不能省略

  •     void 返回没有值
  •     String 返回值字符串
  •     *任意

包,通常不省略,但可以省略

  •     com.gyf.crm 固定包
  •     com.gyf.crm.*.service crm 包下面子包任意(例如:com.gyf.crm.staff.service)
  •     com.gyf.crm… crm 包下面的所有子包(含自己)
  •     com.gyf.crm.*service… crm 包下面任意子包,固定目录 service,service 目录任意包

类,通常不省略,但可以省略

  • UserServiceImpl 指定类
  • *Impl 以 Impl 结尾
  • User* 以 User 开头
  • *任意

方法名,不能省略

  • addUser 固定方法
  • add* 以 add 开头
  • *DO 以 DO 结尾
  • *任意

参数

  • () 无参
  • (int) 一个整形
  • (int,int)两个整型
  • (…) 参数任意

throws可省略,一般不写

表达式示例

  • execution(* com.cad.demo.User.*(…)) :匹配 User 类⾥的所有⽅法
  • execution(* com.cad.demo.User+.*(…)) :匹配该类的⼦类包括该类的所有⽅法
  • execution(* com.cad..(…)) :匹配 com.cad 包下的所有类的所有⽅法
  •   execution(* com.cad….(…)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法
  • execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int

5.使用 AOP 统计 UserController 每个方法的执行时间 StopWatch

 Spring AOP 中统计时间用 StopWatch 对象:

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // spring 中的时间统计对象
        StopWatch stopWatch = new StopWatch();
        Object result = null;
        try {
            stopWatch.start(); // 统计方法的执行时间,开始计时
            // 执行目标方法,以及目标方法所对应的相应通知
            result = joinPoint.proceed();
            stopWatch.stop(); // 统计方法的执行时间,停止计时
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println(joinPoint.getSignature().getDeclaringTypeName() + "." +
                joinPoint.getSignature().getName() +
                "执行花费的时间:" + stopWatch.getTotalTimeMillis() + "ms");
        return result;
    }

 

 

6. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截

 

Spring AOP 动态代理实现:

默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类

  •     JDK Proxy(JDK 动态代理)
  •     CGLIB Proxy:默认情况下 Spring AOP 都会采用 CGLIB 来实现动态代理,因为效率高
  •     CGLIB 实现原理:通过继承代理对象来实现动态代理的(子类拥有父类的所有功能)
  •     CGLIB 缺点:不能代理最终类(也就是被 final 修饰的类)
     

6.1 生成代理的时机 :织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中

在目标对象的生命周期中有多个点可以进行织入

  1. 编译期:切面在目标类编译时被织入,这种方法需要特殊的编译器,AspectJ 的织入编译器就是以这种方式织入切面的
  2. 类加载期:切面在目标类加载到 JVM 时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码,AspectJ5 的加载时织入 (load-time weaving. LTW)就支持以这种方式织入切面
  3. 运行期:切面在应用运行的某一时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP 就是以这种方式织入切面的
     

6.2 JDK 动态代理实现

JDK 动态代理就是依靠反射来实现的

//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被
代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {
 
 	//⽬标对象即就是被代理对象
 	private Object target;
 
 	public PayServiceJDKInvocationHandler( Object target) {
		 this.target = target;
 	}
 
	//proxy代理对象
 	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		 //1.安全检查
		 System.out.println("安全检查");
		 //2.记录⽇志
		 System.out.println("记录⽇志");
 		//3.时间统计开始
 		System.out.println("记录开始时间");
 		//通过反射调⽤被代理类的⽅法
 		Object retVal = method.invoke(target, args);
 		//4.时间统计结束
 		System.out.println("记录结束时间");
 		return retVal;
 	}
 	public static void main(String[] args) {
 		PayService target= new AliPayService();
 		//⽅法调⽤处理器
 		InvocationHandler handler = new PayServiceJDKInvocationHandler(target);
		//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
 		PayService proxy = (PayService) Proxy.newProxyInstance(
		 target.getClass().getClassLoader(),new Class[]{PayService.class},handler);
 		proxy.pay();
 	}
}

6.3 CGLIB 动态代理实现

public class PayServiceCGLIBInterceptor implements MethodInterceptor {
 	//被代理对象
	 private Object target;
 
	 public PayServiceCGLIBInterceptor(Object target){
	 	this.target = target;
 	 }
 
	 @Override
	 public Object intercept(Object o, Method method, Object[] args, MethodProxymethodProxy)throws Throwable {
 		 //1.安全检查
		 System.out.println("安全检查");
		 //2.记录⽇志
 		System.out.println("记录⽇志");
		 //3.时间统计开始
 		System.out.println("记录开始时间");
         //通过cglib的代理⽅法调⽤
 		Object retVal = methodProxy.invoke(target, args);
		 //4.时间统计结束
		System.out.println("记录结束时间");
		return retVal;
 	}
 
	 public static void main(String[] args) {
		 PayService target= new AliPayService();
 		PayService proxy= (PayService) Enhancer.create(target.getClass(),
                   new PayServiceCGLIBInterceptor(target));
         proxy.pay();
 	 }
}

6.4 JDK 和 CGLIB 实现的区别(面试常问)

  1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHander 及 Proxy,在运行时动态的在内存中生成了代理对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式),只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成的
  2. CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象,这种方式实现方式效率高

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/860640.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt画波浪球(小费力)

画流动波浪 #ifndef WIDGET3_H #define WIDGET3_H#include <QWidget> #include <QtMath> class widget3 : public QWidget {Q_OBJECT public:explicit widget3(QWidget *parent nullptr);void set_value(int v){valuev;}int get_value(){return value;} protecte…

FineReport 使用汇总(不定期更新)

1&#xff0c;下载地址 免费下载FineReport - FineReport报表官网 这里注意 2&#xff0c;后台统计 sql 还是需要自己写 就会有数据 而直接查询表&#xff0c; 没有数据 不过&#xff0c;可能是我不会用。还需要再研究。

Java ThreadLocal是什么

文章目录 引子&#xff1a;SimpleDateFormat类ThreadLocal是什么ThreadLocal 的另一个用途**总结**ThreadLocal的两大用途ThreadLocal 的源代码ThreadLocalMapThreadLocalMap 的问题ThreadLocal的key为什么设置成弱引用&#xff1f;value为什么不是弱引用&#xff1f;Thread、T…

ubuntu 安装 nvidia 驱动

ubuntu 安装 nvidia 驱动 初环境与设备查询型号查询对应的驱动版本安装驱动验证驱动安装结果 本篇文章将介绍ubuntu 安装 nvidia 驱动 初 希望能写一些简单的教程和案例分享给需要的人 环境与设备 系统&#xff1a;ubuntu 设备&#xff1a;Nvidia GeForce RTX 4090 查询型…

每天一道leetcoed:剑指 Offer 28. 对称的二叉树(适合初学者树)

今日份题目&#xff1a; 请实现一个函数&#xff0c;用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样&#xff0c;那么它是对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,nu…

【打印100之内的素数——筛选法】

打印100之内的素数——筛选法 筛选法 1.题目分析 素数&#xff1a;约数为1和该数本身的数字称为素数&#xff0c;即质数 2.方法解析 筛选法&#xff1a;又称为筛法。先把N个自然数按次序排列起来。1不是质数&#xff0c;也不是合数&#xff0c;要划去。第二个数2是质数留下来…

[C++ 网络协议] 套接字

目录 1. 套接字 1.1 在Linux平台下构建套接字 1.1.1 用于接听的套接字(服务器端套接字) 1.1.2 用于发送请求的套接字(客户端套接字) 1.2 在Windows平台下构建套接字 1.2.1 Winsock的初始化 1.2.2 用于接听的套接字(服务器端套接字) 1.2.3 用于发送请求的套接字(客户端套…

Linux Linux系统上C程序的编译与调试

一、环境配置 在Linux操作系统中&#xff0c;打开终端&#xff0c;以管理员root模式登录 1.更新&#xff1a;输入命令apt update 2.下载vim&#xff1a;输入命令apt install vim -y 3.下载gcc&#xff1a;输入命令apt install gcc -y 4.下载g&#xff1a;输入命令apt install …

7.6 通俗易懂解读残差网络ResNet 手撕ResNet

一.举例通俗解释ResNet思想 假设你正在学习如何骑自行车&#xff0c;并且想要骑到一个遥远的目的地。你可以选择直接骑到目的地&#xff0c;也可以选择在途中设置几个“中转站”&#xff0c;每个中转站都会告诉你如何朝着目的地前进。 在传统的神经网络中&#xff0c;就好比只…

八、复用(2)

本章概要 结合组合和继承 保证适当的清理名称隐藏 组合与继承的选择protected向上转型 再论组合和继承 结合组合与继承 你将经常同时使用组合和继承。下面的例子展示了使用继承和组合创建类&#xff0c;以及必要的构造函数初始化: class Plate {Plate(int i) {System.out.…

君子签“签约+存证+诉讼”为银行建立可靠的契约关系和信任机制

随着互联网金融业的发展&#xff0c;商业银行经营转型与创新发展任重而道远。根据现有银行开展的业务来看&#xff0c;业务拓展过程中遇到的瓶颈越来越明显&#xff0c;集中体现在以下几个方面&#xff1a; 传统签署方式存在多种弊端&#xff0c;亟需转型 互联网金融服务采用…

【广州华锐视点】AR电力职业技能培训系统让技能学习更“智慧”

随着科技的发展&#xff0c;教育方式也在不断地进步和创新。其中&#xff0c;增强现实(AR)技术的出现&#xff0c;为教育领域带来了全新的可能。AR电力职业技能培训系统就是这种创新教学方法的完美实践&#xff0c;它将虚拟与现实相结合&#xff0c;为学生提供了一个沉浸式的学…

Android T 窗口层级其一 —— 容器类

窗口在App端是以PhoneWindow的形式存在&#xff0c;承载了一个Activity的View层级结构。这里我们探讨一下WMS端窗口的形式。 可以通过adb shell dumpsys activity containers 来看窗口显示的层级 窗口容器类 —— WindowContainer类 /*** Defines common functionality for c…

中睿天下入选河南省网信系统2023年度网络安全技术支撑单位

近日&#xff0c;河南省委网信办发布了“河南省网信系统2023年度网络安全技术支撑单位名单”&#xff0c;中睿天下凭借出色的网络安全技术能力和优势成功入选。 本次遴选由河南省委网信办会同国家计算机网络与信息安全管理中心河南分中心&#xff08;以下简称安全中心河南分中心…

MySQL高级-存储引擎+存储过程+索引(详解01)

目录 1.mysql体系结构 2.存储引擎 2.1.存储引擎概述 2.2.1.InnoDB 2.2.2.MyISAM 2.2.3.存储引擎选择 3.存储过程 3.1.存储过程和函数概述 3.2.创建存储过程 3.3.调用存储过程 3.4.查看存储过程 3.5.删除存储过程 3.6.语法 3.6.1.变量 3.6.2.if条件判断 3.6.3.…

【STM32】利用CubeMX对FreeRTOS用按键控制任务

对于FreeRTOS中的操作&#xff0c;最常用的就是创建、删除、暂停和恢复任务。 此次实验目标&#xff1a; 1.创建任务一&#xff1a;LED1每间隔1秒闪烁一次&#xff0c;并通过串口打印 2.创建任务二&#xff1a;LED2每间隔0.5秒闪烁一次&#xff0c;并通过串口打印 3.创建任…

[oeasy]python0083_[趣味拓展]字体样式_正常_加亮_变暗_控制序列

字体样式 回忆上次内容 上次了解了 一个新的转义模式 \033 逃逸控制字符 esc esc 让输出 退出 标准输出流进行 控制信息的设置 可以 清屏也可以 设置光标输出的位置 还能做什么呢&#xff1f; 可以 设置 字符的颜色吗&#xff1f;&#xff1f;&#xff1f;&#x1f914; 查…

Vue3 组件基础简单应用

去官网学习→组件基础 | Vue.js 运行示例&#xff1a; 自定义组件 代码&#xff1a; MyComponent.vue <template><h2>MyComponent.vue 组件</h2> </template><script>// 导出export default{name:"MyComponent"} </script><…

Java一般用于postgis空间数据库通用的增删查改sql命令

目录 1 增加 2 删除 3 查询 4 更新 "public"."JGSQGW_Geo"为某模式下得表 一般postgrel有这样的设计模式 1 增加 #前端绘制出的数据插入 INSERT INTO "public"."JGSQGW_Geo" ( "geom","gridone","gridon…

prometheus告警发送组件部署

一、前言 要实现Prometheus的告警发送需要通过alertmanager组件&#xff0c;当prometheus触发告警策略时&#xff0c;会将告警信息发送给alertmanager&#xff0c;然后alertmanager根据配置的策略发送到邮件或者钉钉中&#xff0c;发送到钉钉需要安装额外的prometheus-webhook…