JavaWeb - ⭐ AOP 面相切面编程原理及用户校验功能实战

一、概述

  1. 定义: AOP (Aspect Oriented Programming 面向切面编程) ,一种面向方法编程的思想

  2. 功能:管理 bean 对象的过程中,通过底层的动态代理机制对特定方法进行功能的增强或改变

  3. 实现方式:动态代理技术,即创建目标对象的代理对象,并对目标对象中的方法的功能进行增强的技术

  4. 优点

    1. 代码无侵入:不修改原有代码的基础上对原有方法的功能进行增强
    2. 减少重复代码:一次性对一类方法进行功能增强
    3. 提高开发效率
    4. 维护方便
  5. AOP 的依赖

    <dependency> 
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  6. 代理方式

    • 没有切面的对象:不需要代理,Spring 会直接注入目标对象(原始类的实例)
    • **接口类型的目标对象:**Spring 使用 JDK 动态代理 创建代理对象
    • 非接口类型的目标对象(如纯类):Spring 则使用 CGLIB(子类代理) 创建代理对象

二、核心概念

  1. JoinPoint : 连接点,客观可以被 Proxy 的方法(实际上所有的方法都是 JoinPoint,都可以被代理)
  2. PointCut : 切入点,主观需要被 Proxy 的方法(满足匹配条件的 JoinPoint),实际被 Advice 控制的方法
  3. Advice : 通知,共性的功能 (最终体现为一个方法),是对目标 PointCut 的相同处理逻辑
  4. Aspect : 切面,即 PointCut + Advice ,描述 “通知” 与 “切入点” 对应关系
  5. Target : Advice 作用的目标对象,PointCut 所在的类

Aspect 类 (AOP 类)

  1. 定义 : 切面,即 PointCut + Advice ,描述 “通知” 与 “切入点” 对应关系
  2. 功能:用于实现特定切面功能,动态代理某些目标类或目标方法的切面类
  3. 组成
    1. Aspect 类:通过 @Aspect 注解标注,告知 Spring 这是一个 AOP 类
    2. PointCut 切入点:通过 @PointCut 注解标注,指定代理的目标方法,减少 Advice 类型注解中的代码冗余
    3. Advice 方法:通过 Advice 注解标注(如 @Before、@After、@Around ),告知 Spring 这个 Aspect 方法将代理哪些目标方法

PointCut (切入点)

  1. 定义 : 切入点,主观需要被 Proxy 的方法(满足匹配条件的 JoinPoint),实际被 Advice 控制的方法
  2. 功能:用于指定 Aspect 代理的具体方法,抽取出公共切入点减少代码冗余(实际上也可以不单独声明,直接在 Advice 里面声明)
  3. 实现方式:通过 execution 和 annotation 两种方式声明
  4. 书写建议
    1. 建议抽取公共的 PointCut 进行复用(也可以不声明单独的 PointCut,而是在 Advice 方法上的注解中声明 PointCut)
    2. 尽量缩小切入点范围,(比如 : 尽量不用 .. 进行包名匹配,因为范围越大匹配效率越低)
    3. 基于接口描述切入点,而不是直接描述实现类,增强拓展性

execution 表达式

  1. 格式

    execution ( [访问修饰符] 返回值 [包名.类名.] 方法名(方法形参) [throws 异常] )
    
  2. 使用方法

    1. 将目标方法 (被代理的方法) 写到 execution 表达式中
    2. 在 Aspect 类中定义一个空参空返回值方法,给其加上 @PointCut( “execution ...” ) 注解
  3. 书写格式

    1. * :通配1个或0个单独的任意符号,可以通配返回值、包名、类名、方法名、任意类型的一个参数,或者包名、类名、方法名的一部分
    2. .. :通配多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
    3. || && ! :可以用逻辑运算符组合复杂的 PointCut 表达式
  • 示例

    @Pointcut("execution(* com.tlias.service.impl.DeptServiceImpl.*(..))")
    private void myPointCut();
    
    • 第一个 * :前面为任意类型的返回值
    • 包名: PointCut 所在的包名类名方法名
    • 最后一个 * :为任意方法
    • (..) :为任意形参

annotation 表达式

  1. 格式

    @annotation(包名.类名.注解名)
    
  2. 使用方法

    1. 在目标方法 (被代理的方法) 上加上自定义的注解 @MyPointCut
    2. 在 Aspect 类中定义一个空参空返回值方法,给其加上 @PointCut( “@annotation(com.xxx.aop.MyPointCut)” ) 注解
  • 示例
    1. 目标:自定义 annotation,被这个 annotation 标注的方法都会被 Aspect 类代理

    2. annotation

      @Target(ElementType.METHOD)                  // 设置注解使用的位置,ElementType.METHOD 表示这个注解用来标注方法
      @Retention(RetentionPolicy.RUNTIME)          // 设置注解生效的时间,RetentionPolicy.RUNTIME 表示运行时生效
      public @interface MyPointCut {
      }
      
    3. PointCut

      @Slf4j
      @Component
      @Aspect
      public class MyAspect{
      	@Pointcut("execution(* com.tlias.service.impl.DeptServiceImpl.*(..)) && @annotation(com.tlias.annotation.MyPointCut)")
      	private void myPointCut();
      	
      	@Around("myPointCut()")
      	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
      		log.info("around before ...");
      		Object result = proceedingJoinPoint.proceed();
      		log.info("around after ...");
      		return result;
      	}
      	
      }
      

Advice (通知)

  1. 定义:通知(Advice)是自定义的处理逻辑,当被 PointCut 选中的方法在指定时机(Before/Around/After)执行时会触发对应的 Advice 方法

  2. 功能 : 明确 PointCut 指定的方法的切面处理逻辑,此方法中会通过 ProceedingJoinPoint 类来代理目标方法

  3. 基本类型

    注解类型说明
    @Before前置通知,Advice 方法在目标方法开始前执行
    @Around环绕通知,Advice 方法在目标方法前后都会执行,期间需要通过 proceed() 方法主动调用目标方法
    @After后置通知,Advice 方法在目标方法结束后执行,有无异常都会执行
    @AfterReturning返回后通知,Advice 方法在目标方法后被执行,有异常则不会执行
    @AfterThrowing异常后通知,Advice 方法在目标方法发生异常后执行
  4. 对比总结

    切面注解运行时机可修改目标方法行为可获取返回值可获取异常
    @Before目标方法执行前
    @Around方法执行前、执行后均可运行☑️☑️☑️
    @After目标方法执行后(无论成功或失败)
    @AfterReturning目标方法成功返回后☑️
    @AfterThrowing目标方法抛出异常时☑️
  5. 自定义优先级:通过 @Order(x) 给 Advice 指定优先级,注解中 x 越大,则 Before 越先执行,After 越后执行

  • 示例

    @Slf4j
    @Component
    @Aspect
    public class MyAspect{
    
    		// 声明一个 PointCut 切入点, 用于后续 Advice 方法中的切入点复用
    		@Pointcut("execution(* com.tlias.service.impl.DeptServiceImpl.*(..)) && @annotation(com.tlias.annotation.MyPointCut)")
    		private void myPointCut();
    	
    		@Before("myPointCut()")
    		public void before(){
    				log.info("before...");
    		}
    	
    		@Around("myPointCut()")
    		public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
    				log.info("around before ...");
    				Object result = proceedingJoinPoint.proceed();
    				log.info("around after ...");
    				return result;
    		}
    	
    		@After("myPointCut()")
    		public void after(){
    				log.info("after ...");
    		}
    	
    		@AfterReturning("myPointCut()")
    		public void afterReturning(){
    				log.info("afterReturning ...");
    		}
    	
    		@AfterThrowing("myPointCut()")
    		public void afterThrowing(){
    				log.info("afterThrowing ...");
    		}
    
    }
    

JoinPoint(连接点)

  1. 定义

    1. JoinPoint:目标对象中所有可以被增强的方法。这些方法在运行时都可以被 AOP 框架拦截并添加额外的处理逻辑
    2. ProceedingJoinPoint:继承自 JoinPoint 类,专门给环绕通知 (Around) 使用的类,用于代理原有方法的类 (对被拦截方法的一个包装)
  2. 功能:拿到 ProceedingJoinPoint 类相当于拿到了原方法,可以调用 proceed() 方法执行原方法

  3. 常用方法

    方法说明
    proceed()执行被代理的方法
    getArgs()获取传递给目标方法的参数
    getSignature()获取被拦截方法的信息,如方法名、返回类型等(通过反射获取)
    getTarget()获取被拦截方法所属的目标对象(Target Object)
    getThis()获取代理对象(Proxy Object)
  • 示例

    @Around("execution (* com.tlias.service.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
    
    	// 获取目标对象类名
    	String className = joinPoint.getTarget().getClass().getName();
    	log.info("目标对象的类名:{}", className);
    
    	// 获取目标方法的方法名
    	String methodName = joinPoint.getSignature().getName();
    	log.info("目标方法的方法名:{}", methodName);
    
    	// 获取目标方法运行时传入的参数
    	Object[] args = joinPoint.getArgs();
    	log.info("目标方法运行时传入的参数:{}", Arrays.toString(args));
    
    	// 获取目标方法运行的返回值
    	Object result = joinPoint.proceed();
    	log.info("目标方法运行的返回值:{}", result);
    
    	// 最后一定要将结果返回回去,因为此时 around 函数代理了 JoinPoint 函数
    	// 不返回的话 controller 层拿不到返回结果
    	return result;
    }
    

三、执行流程

@Around 流程

  1. 声明切入点:Aspect 类声明将会代理的目标方法(根据 @Around 注解配置的 PointCut 信息决定代理哪些方法)
  2. 依赖注入:Spring 通过动态代理技术,注入代理对象 XxxServiceProxy(如果没有配置 Aspect,则直接注入目标对象 XxxService)
  3. 接收请求:服务器接收客户端 ”调用了被代理的方法(目标方法)“ 的请求
  4. 代理方法:XxxServiceProxy 代理类接收请求,通过 ProceedingJoinPoint 参数获取 PointCut 方法的上下文信息
  5. 执行前置代理逻辑:运行 Aspect 前置代码部分 ( Advice 部分 )
  6. 执行目标方法:通过 joinPoint.proceed() 执行被代理的方法,同时用 Object result 接收被代理方法的返回值
  7. 执行后置代理逻辑:运行 Aspect 后置代码部分 ( Advice 部分 )
  8. 返回结果值

@Before / @After 流程

  1. 声明切入点:Aspect 类声明将会代理的目标方法(根据 @Before、@After 等注解配置的 PointCut 信息决定代理哪些方法)
  2. 依赖注入:Spring 通过动态代理技术,注入代理对象 XxxServiceProxy(如果没有配置 Aspect,则直接注入目标对象 XxxService)
  3. 接收请求:服务器接收客户端 ”调用了被代理的方法(目标方法)“ 的请求
  4. 代理方法:XxxServiceProxy 接收请求
  5. 运行 Before Advice:运行 @Before 注解的方法逻辑
  6. 执行目标方法:代理对象自动执行目标方法
  7. 运行 After Advice:运行 @After 注解的方法逻辑
  8. 返回结果值

四、使用场景

  1. 记录操作日志
  2. 权限控制
  3. 事务管理
  4. 自动填充数据库字段

五、示例

方法用时统计

  1. 目标 : 统计各个业务层方法执行耗时
  2. 步骤
    1. 导入依赖:在 pom.xml 中导入 AOP 依赖

      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
    2. 创建包和类 : com.tlias.aop.TimeAspect

    3. 编写 AOP 程序

      @Aspect    // 声明这是一个AOP类
      @Component
      public class TimeAspect{
      
      	@PointCut("execution (* com.tlias.service.*.*(..))")
      	private void myPointCut(){}
      
      	@Around("myPointCut()")    // 声明这个Aspect将代理哪些方法
      	public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
      		long begin = System.currentTimeMills();                //1. 记录开始时间
      		Object result = proceedingJoinPoint.prceed();          //2. 调用被代理的方法
      		long end = System.currentTimeMills();                	 //3. 记录结束时间
      		log.info(prceedingJoinPoint.getSignature() + "方法执行共耗时" + end-begin + "毫秒");    		//4. 计算时间差并记录
      		return result;
      	}
      

用户权限校验

  1. 目标 : 保证 @authCheck 注解标注的方法都必须具备特定权限才可调用该方法
  2. 步骤
    1. 导入依赖:在 pom.xml 中导入 AOP 依赖

      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
    2. 编写注解(com.yupi.yudada.annotation.AuthCheck)

      @Target(ElemType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface AuthCheck {
      		
      		/**
      		* 被 AuchCheck 标注就必须具备的用户权限等级
      		* 示例: @AuthCheck("ADMIN") 表示当前方法必须拥有管理员权限才可执行
      		**/
      		String mustRole() default "";
      
    3. 编写 AOP 程序(com.yupi.yudada.aop.AuthInterceptor)

      @Aspect    // 声明这是一个AOP类
      @Component
      public class AuthInterceptor{
      
      		@Resource
      		private UserService userService;
      		
      		@Around("@annotation(authCheck)")
      		public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
      				String mustRole = authCheck.mustRole();
      				RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
      				HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
      				
      				
      		}
      
      }
      

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MFC案例:图片文件转图标(ico)格式

本案例程序目的是将一般图像文件转换成图标格式(ico)。实现起来不是很复杂&#xff0c;这里为了介绍MFC的具体使用方法&#xff0c;在程序界面上分成几个功能块&#xff0c;包括&#xff1a;打开图像文件、选择ICON大小、转换、预览、保存等。相关具体步骤如下&#xff1a; 一、…

Scala_【2】变量和数据类型

第二章 注释标识符的命名规范命名规则关键字 变量字符串输出数据类型关系变量和数据类型整数类型&#xff08;Byte、Short、Int、Long&#xff09;浮点类型&#xff08;Float、Double&#xff09;字符类型&#xff08;Char&#xff09;布尔类型&#xff08;Boolean&#xff09;…

R语言数据分析案例46-不同区域教育情况回归分析和探索

一、研究背景 教育是社会发展的基石&#xff0c;对国家和地区的经济、文化以及社会进步起着至关重要的作用。在全球一体化进程加速的今天&#xff0c;不同区域的教育发展水平呈现出多样化的态势。这种差异不仅体现在教育资源的分配上&#xff0c;还表现在教育成果、教育投入与…

8086汇编(16位汇编)学习笔记03.汇编指令

8086汇编(16位汇编)学习笔记03.汇编指令-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net 指令种类 数据传送指令算数运算类指令位操作类指令串操作类指令控制转移类指令处理器控制类指令 数据传送类指令 **传送类指令不影响标志位&#xff0c;**除了标志位传…

Antd react上传图片格式限制

限制分辨率&#xff08;像素&#xff09; <a-upload :before-upload"beforeUpload">// 上传图片宽高比例限制const beforeUpload file > {return new Promise((resolve, reject) > {// // 图片类型限制// let isJpgOrPng file.type image/png || fil…

Confluent Cloud Kafka 可观测性最佳实践

Confluent Cloud 介绍 Confluent Cloud 是一个完全托管的 Apache Kafka 服务&#xff0c;提供高可用性和可扩展性&#xff0c;旨在简化数据流处理和实时数据集成。用户可以轻松创建和管理 Kafka 集群&#xff0c;而无需担心基础设施的维护和管理。Confluent Cloud 支持多种数据…

StartAI图生图局部重绘,让画面细节焕发新生!!

在设计的世界里&#xff0c;每一个细节都承载着我们的创意与心血。然而&#xff0c;有时我们总会遇到一些不尽如人意的画面细节&#xff0c;它们如同瑕疵般破坏了整体的和谐与美感。今天&#xff0c;我要向大家推荐一款强大的工具——StartAI的局部重绘功能&#xff0c;它正是我…

易语言 OCR 文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

重温设计模式--单例模式

文章目录 单例模式&#xff08;Singleton Pattern&#xff09;概述单例模式的实现方式及代码示例1. 饿汉式单例&#xff08;在程序启动时就创建实例&#xff09;2. 懒汉式单例&#xff08;在第一次使用时才创建实例&#xff09; 单例模式的注意事项应用场景 C代码懒汉模式-经典…

ArKTS基础组件3

一.PatternLock 图案密码锁组件&#xff0c;以九宫格图案的方式输入密码&#xff0c;用于密码验证场景 属性: sideLength:设置组件的宽度和高度&#xff08;宽高相同&#xff09;。设置为0或负数时组件不显示。 参数名类型必填说明valueLength是组件的宽度和高度。默认值&a…

python2:数据、运算符与表达式

一&#xff0c;数据类型&#xff1a; 数据类型是计算机对现实中数据的抽象&#xff0c;不同的数据类型其存储格式、数据范围、 计算要求都各不相同。 Python中的数据类型可以分为以下三类 基础类型&#xff1a;字符串(str)、整数(int)、实数(float)、布尔(bool)、复数(compl…

tortoisegit推送失败

tortoisegit推送失败 git.exe push --progress -- "origin" testLidar:testLidar /usr/bin/bash: gitgithub.com: No such file or directory fatal: Could not read from remote repository. Please make sure you have the correct access rights and the reposit…

pyinstaller打包资源文件和ini配置文件怎么放

1.如果出现无法成功完成操作&#xff0c;因为文件包含病毒或潜在的垃圾软件&#xff0c;说明你的版本太高&#xff0c;更换pyinstaller版本。 pip install pyinstaller6.2.02.一开始打包的时windows下尽量选择打成文件夹的并且要是带命令行窗口的&#xff0c;容易查看错误。 …

autMan奥特曼机器人-autMan的PHP环境

直装版请自行安装php环境。 docker版本预置了php环境&#xff0c;如下图&#xff1a; 如果使用插件"test php"测试环境时&#xff0c;实时日志有报错如下&#xff1a; 可进入终端&#xff0c;输入两条命令 apk add curl apk add php-curl

uniApp打包H5发布到服务器(docker)

使用docker部署uniApp打包后的H5项目记录&#xff0c;好像和VUE项目打包没什么区别... 用HX打开项目&#xff0c;首先调整manifest.json文件 开始用HX打包 填服务器域名和端口号~ 打包完成后可以看到控制台信息 我们可以在web文件夹下拿到下面打包好的静态文件 用FinalShell或…

【Leetcode】1705. 吃苹果的最大数目

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 有一棵特殊的苹果树&#xff0c;一连 n n n 天&#xff0c;每天都可以长出若干个苹果。在第 i i i 天&#xff0c;树上会长出 a p p l e s [ i ] apples[i] apples[i] 个苹果&a…

4、数据结构与算法解析(C语言版)--栈

栈的数据存储遵循“后进先出的规则”&#xff0c;这在计算机里面是非常有用的&#xff0c;比如word等编辑软件的"撤销"功能&#xff0c;就是使用栈进行实现的。 1、创建项目 main.h #ifndef _MAIN_H #define _MAIN_H#include <stdio.h> #include <stdlib.…

施耐德变频器ATV320系列技术优势:创新与安全并重

在工业自动化领域&#xff0c;追求高效、安全与智能已成为不可阻挡的趋势。施耐德变频器ATV320系列凭借其强大的设计标准和全球认证&#xff0c;成为能够帮助企业降低安装成本&#xff0c;提高设备性能的创新解决方案。 【全球认证&#xff0c;品质保障】ATV320 系列秉持施耐德…

【软考高级】系统架构设计师复习笔记-精华版

文章目录 前言0 系统架构设计师0.1 考架构还是考系分0.2 架构核心知识0.3 架构教材变化 1 计算机操作系统1.1 cpu 组成1.2 内核的五大功能1.3 流水线技术1.4 段页式存储1.5 I/O 软件1.6 文件管理1.7 系统工程相关 2 嵌入式2.1 嵌入式技术2.2 板级支持包&#xff08;BSP&#xf…

并发编程(19)——引用计数型无锁栈

文章目录 十九、day191. 引用计数2. 代码实现2.1 单引用计数器无锁栈2.2 双引用计数器无锁栈 3. 本节的一些理解 十九、day19 上一节我们学习通过侯删链表以及风险指针与侯删链表的组合两种方式实现了并发无锁栈&#xff0c;但是这两种方式有以下缺点&#xff1a; 第一种方式…