自定义 Spring AOP 切面实战(鉴权、记录日志)
前言:
从事 Java 的小伙伴都知道 Spring AOP,也都知道 AOP 是面向切面编程,那你知道 AOP 在项目实战中怎么使用吗?本篇简单分享 Spring AOP 在项目中的实际使用。
AOP 知识储备传送门:
深入理解 Spring AOP 源码分析(附源码分析)
AOP 基础知识回顾
AOP 概念
- 连接点(Join point):能够被拦截的地⽅、地点,Spring AOP 是基于动态代理的,所以是⽅法拦截的,每个成员⽅法都可以称之为连接点。
- 切点(Poincut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联,切点分为execution方式和annotation方式,前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
- 通知(Advice):在切面的某个特定的连接点上执行的动作,通知分为:前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)。
- 织⼊(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象,分为:编译时织入、类加载时织入、执行时织入
- 引⼊/引介(Introduction):允许我们向现有的类添加新⽅法或属性,是⼀种特殊的增强。
- 切面(Aspect):切⾯由切点和增强、通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
通知(Advice)分类
- 前置通知(Before Advice):在⽬标⽅法被调⽤前的通知功能。
- 后置通知(After Advice):在⽬标⽅法被调⽤之后的通知功能。
- 返回通知(After-returning):在⽬标⽅法成功执⾏之后的通知功能。
- 异常通知(After-throwing):在⽬标⽅法抛出异常之后的通知功能。
- 环绕通知(Around):把整个⽬标⽅法包裹起来,在被调⽤前和调⽤之后的通知功能。
自定义 AOP 切面常用知识储备
@Aspect 注解的作用?
@Aspect 注解的作用是定义一个切面,需要再自定义切面类上加上次注解。
@Component 注解
@Component 注解表示这个类交给 Spring 容器管理,如果不使用 @Component 注解,也要以其他方法是注册切面类,以确保 Spring 容器能够识别并管理这个切面。
Pointcut 切点中 execution 和 @annotation 定义切面的区别?
-
@annotation:切点表达式是注解的全限类名,是根据注解来匹配的,有注解的方法才会被拦截。
-
execution:切点表达式很灵活,有修饰符匹配、返回值匹配、类路径匹配、方法名匹配、参数匹配、异常类型匹配。
-
作用于任意以 public 修饰的方法:execution(public * *(…))
-
作用于任何一个以 set 开始的方法:execution(* set*(…))
-
作用于 MyService 接口的任意方法:execution(* com.xxx.service.MyService.*
-
作用于定义在 service 包和所有子包里的任意类的任意方法:execution(* com.xxx.service….(…)),第一个 * 表示匹配任意的方法返回值, …(两个点)表示零个或多个,第一个 … 表示 service 包及其子包,第二个 * 表示所有类,第三个 * 表示所有方法,第二个…表示方法的任意参数个数。
JoinPoint 的作用?
PoinPoint 代表程序中的一个点,通常把 JoinPoint 作为一个参数传递到通知方法中,通过 JoinPoint 来获取程序中这个点的相关信息,JoinPoint 只能用于 @Before、@After、@AfterReturning、@AfterThrowing 通知中,不能使用于 @Around 中,JoinPoint 常用方法如下:
- Object[] getArgs():返回目标方法的参数。
- Signature getSignature():返回方法的签名。
- Object getTarget():返回被增强的目标对象,也就是被代理对象。
- Object getThis():返回由目标对象生成的代理对象。
ProceedingJoinPoint 的作用?
ProceedingJoinPoint 是 JoinPoint 的子接口,用于环绕通知 @Around 中,它提供了一个 proceed() 方法,用于执行被拦截的方法也就是目标方法。
自定义 AOP 切面代码演示
自定义 AOP 切面实现接口请求日志记录
记录接口请求日志这种业务场景是非常常见的,如果在每个接口中去记录,显得代码臃肿且不灵活,而且有很大的侵入性,我们可以使用自定义 AOP 面来完成这种功能,代码如下:
@Slf4j
@Component
@Aspect
public class MyApiLogAspect implements Ordered {@Pointcut("execution(* com.my.study.controller..*(..))")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();//获取当前请求对象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();//记录请求日志信息ApiLogVO apiLogDO = new ApiLogVO();MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();String methodName = method.getName();apiLogDO.setMethodName(methodName);apiLogDO.setRequestMethod(request.getMethod());apiLogDO.setMethodParameters(getParameter(method, joinPoint.getArgs()));apiLogDO.setUri(request.getRequestURI());//执行目标方法Object result = joinPoint.proceed();apiLogDO.setResponse(result);long endTime = System.currentTimeMillis();apiLogDO.setTimeConsuming((endTime - startTime));log.info("请求日志信息:{}", JSON.toJSONString(apiLogDO));return result;}/*** 根据方法和传入的参数获取请求参数*/private Object getParameter(Method method, Object[] args) {List<Object> argList = new ArrayList<>();Parameter[] parameters = method.getParameters();for (int i = 0; i < parameters.length; i++) {//将RequestBody注解修饰的参数作为请求参数RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);if (requestBody != null) {argList.add(args[i]);}//将RequestParam注解修饰的参数作为请求参数RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);if (requestParam != null) {Map<String, Object> map = new HashMap<>();String key = parameters[i].getName();if (ObjectUtil.isNotEmpty(requestParam.value())) {key = requestParam.value();}map.put(key, args[i]);argList.add(map);}}if (argList.size() == 0) {return null;} else if (argList.size() == 1) {return argList.get(0);} else {return argList;}}@Overridepublic int getOrder() {return -1;}
}@Data
public class ApiLogVO {@ApiModelProperty("方法名称")private String methodName;@ApiModelProperty("请求方式")private String requestMethod;@ApiModelProperty("方法路径")private String uri;@ApiModelProperty("方法耗时")private Long timeConsuming;@ApiModelProperty("方法参数")private Object methodParameters;@ApiModelProperty("方法响应")private Object response;}
从代码来看,我们使用了很少的代码量完成了接口请求日志的记录,而且没有入侵业务代码,Pointcut 支持各种灵活配置,可以丰富我们记录各种场景,因为记录了接口出入参及请求时间,因此我们使用的是环绕通知。
自定义 AOP 切面实现接口鉴权
在业务项目开发中,部分接口需要进行权限校验,部分接口不需要进行权限校验,这也是一种非常常见的场景,我们如果在每个接口中去做判断同样显得代码臃肿且入侵业务代码,我们同样可以通过自定义注解 + AOP 切面的方式完成接口鉴权的功能,代码如下:
@Component
@Slf4j
@Aspect
public class MyAuthLogAspect implements Ordered {@Resourceprivate AuthUtil authUtil;@Pointcut("@annotation(com.com.my.study.annotation.RequiresPermissions)")public void authPointCut() {}@Before("authPointCut()")public void before(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 注解鉴权 有注解表示需要鉴权RequiresPermissions requiresPermissions = signature.getMethod().getAnnotation(RequiresPermissions.class);if (requiresPermissions != null) {authUtil.checkPermission(requiresPermissions);}}@After("authPointCut()")public void after(JoinPoint joinPoint) {}@Overridepublic int getOrder() {//优先级最高 值越小 越先执行return 1;}
}
通过代码可以看出通过自定义 AOP 切面完成接口权限校验也是十分简单方便,因为是进行接口权限校验,我们使用前置通知即可完成接口权限校验的功能。
自定义 AOP 切面执行顺序
当项目中有多个切面的时候,我们该如何保证切面的执行顺序?我们可以通过实现 Order 接口来自定义切面的执行顺序,order 的值越小,对应的切面的优先级就越高,也就越先执行。
自定义 AOP 切面的使用场景
- 接口日志记录。
- 权限校验。
- 接口限流。
- 。。。。。。
欢迎提出建议及对错误的地方指出纠正。
相关文章:
自定义 Spring AOP 切面实战(鉴权、记录日志)
前言: 从事 Java 的小伙伴都知道 Spring AOP,也都知道 AOP 是面向切面编程,那你知道 AOP 在项目实战中怎么使用吗?本篇简单分享 Spring AOP 在项目中的实际使用。 AOP 知识储备传送门: 深入理解 Spring AOP 源码分析…...
JAVA面试题大全(九)
1、为什么要使用 spring? 方便解耦,便于开发支持aop编程声明式事务的支持方便程序的测试方便集成各种优秀的框架降低JavaEE API的使用难度 2、解释一下什么是 aop? AOP 是 Aspect-Oriented Programming 的缩写,中文翻译为“面向…...
React 组件三大核心之 ref
文章目录 用法React.createRef()useRef Hook 注意 ref 是 React 中的一个重要概念,它用于访问和操作 DOM 元素或者类组件实例。 在React中,ref 提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。这对于执行DOM操作、读取…...
json 读写 python
目录 这里对json保存做了格式封装 调用代码: python json原始保存是所有json保存一行, 这里对json保存做了格式封装 import numpy as np class MyEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, np.integer):return int(obj)…...
Docker快速部署Seata的TC服务以及微服务引入Seata教程
目录 一、使用docker部署Seata的TC服务 1、拉取TC服务镜像 2、创建并运行容器 3、修改配置文件 4、在Nacos中添加TC服务的配置 5、重启TC服务 二、微服务集成Seata 1、引入依赖 2、修改配置文件 Seata是阿里的一个开源的分布式事务解决方案,能够为分布…...
我的第一个JAVA程序IDEA版
目录 第一步 新建一个空项目第二步 新建模块第三步 新建包第四步 新建类第五步 新建main方法 第一步 新建一个空项目 第二步 新建模块 第三步 新建包 第四步 新建类 然后在包文件夹下新建类 第五步 新建main方法...
轻量SEO分析报告程序网站已开心去授权
轻量SEO分析报告程序网站已开心去授权,可以让你生成有洞察力的、 简洁的、易于理解的SEO报告,帮助你的网页排名和表现更好 网站源码免费下载地址抄笔记 (chaobiji.cn)https://chaobiji.cn/...
本机与华为云ping不通的问题
进入华为云控制台。依次选择:云服务器->点击服务器id->安全组->更改安全组->添加入方向规则,添加一个安全组规则(ICMP),详见下图 再次ping公网ip就可以ping通了 产生这一问题的原因是ping的协议基于ICMP…...
FastJSON2 > FastJSON 好在何处
FastJSON 是一种广泛使用的 JSON 解析库,其高性能和简单易用的特点受到开发者的喜爱。然而,随着应用场景的复杂化和安全要求的提高,FastJSON 逐渐暴露出一些问题。为了解决这些问题并进一步提升性能和安全性,阿里巴巴推出了 FastJ…...
7个常见的SQL慢查询问题及其解决方法
大家好,得益于摩尔定律,计算机性能已大幅提升,加上数据库的进步以及微服务所倡导的各种反模式设计,因此现在编写复杂SQL查询的机会越来越少。业界已经开始提倡不要进行专门的SQL优化,因为节省下来的资源并不足以抵消员…...
《Rust奇幻之旅:从Java和C++开启》第1章Hello world 1/5
讲动人的故事,写懂人的代码 很多程序员都在自学Rust。 🤕但Rust的学习曲线是真的陡,让人有点儿怵头。 程序员工作压力大,能用来自学新东西的时间简直就是凤毛麟角。 📕目前,在豆瓣上有7本Rust入门同类书。它们虽有高分评价,但仍存在不足。 首先,就是它们介绍的Rust新…...
将富文本编辑器中的H标签处理成树形结构,支持无限层级
做富文本编辑器时,需要将文本里的标题整理成树形数据, // 这里是数据结构 const data [{"id": "hkyrq2ndc-36yttda0lme00","text": "阿萨德阿萨德阿萨","level": 1,"depth": 1,},{"…...
探索移动云:我的ES与Kibana之旅
目录 引言: 如何免费体验移动云产品 登录并完成实名认证 选择试用ECS云主机 安全组配置 安装Elasticsearch和Kibana 安装Elasticsearch 编辑安装kibana 测试结果 使用感觉 引言: 移动云技术产品的发展已经取得了巨大的进步。云数融合、A1、大…...
java 线程执行原理,java线程在jvm中执行流程
java 线程执行原理,java线程在jvm中执行流程 从jvm视角看java线程执行过程 ##首先thread.c注册jni函数 JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) {(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(…...
[Redis]基本全局命令
Redis存储方式介绍 在 Redis 中数据是以键值对的凡事存储的,键(Key)和值(Value)是基本的数据存储单元。以下是对 Redis 键值对的详细讲解: 键(Key): 类型:…...
【Linux】- HBase集群部署 [19]
简介 apache HBase是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。 和Redis一样,HBase是一款KeyValue型存储的数据库。 不过和Redis涉及方向不同 Redis设计为少量数据,超快检索HBase设计为海量数据,快速检索 HBase在大数据邻域…...
js如何遍历FormData的值
遍历FormData的值,一般有2种方法:forEach 和 for...of entries const data new FormData();data.append(aaa, 111); data.append(bbb, 222);// 方法1 data.forEach((value, key) > {console.log(key, value); }) 输出 aaa 111 和 bbb 222// 方法2 …...
【C语言】明析部分C语言内存函数
目录 1.memcpy 2.memmove 3.memset 4.memcmp 以下都是内存函数,作用单位均是字节 1.memcpy memcpy是C/C语言中的一个内存拷贝函数,其原型为: void* memcpy(void* dest, const void* src, size_t n);目标空间(字节)…...
一阶数字高通滤波器
本文的主要内容包含一阶高通滤波器公式的推导和数字算法的实现以及编程和仿真 1 计算公式推导 1.1.2 算法实现及仿真 利用python实现的代码如下: import numpy as np # from scipy.signal import butter, lfilter, freqz import matplotlib.pyplot as plt #2pifW…...
Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和锁,线程同步和条件变量,线程其他知识点
Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和互斥锁,线程同步和条件变量,线程其他知识点 1.前言 一.模拟C11线程库自己封装简易语言级线程库1.实现框架2.迅速把构造等等函数写完3.start和work1.尝试一2.尝试二3.最终版本4.给出代码 二.模拟实现多线程(为编写线程池做…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
跨平台商品数据接口的标准化与规范化发展路径:淘宝京东拼多多的最新实践
在电商行业蓬勃发展的当下,多平台运营已成为众多商家的必然选择。然而,不同电商平台在商品数据接口方面存在差异,导致商家在跨平台运营时面临诸多挑战,如数据对接困难、运营效率低下、用户体验不一致等。跨平台商品数据接口的标准…...
