基于SpringBoot使用AOP开发接口的访问日志信息

SpringBoot的AOP原理

Spring Boot的AOP(面向切面编程)原理是基于动态代理实现的。

在Spring Boot中,AOP通过代理模式对目标对象进行包装,实现在目标对象的方法执行前后增加额外的逻辑。AOP可以在不修改目标对象的情况下,通过代理对象对目标对象进行方法的增强。

Spring Boot中的AOP主要使用了两种代理方式:JDK动态代理和CGLIB动态代理。

  • JDK动态代理:JDK动态代理是基于接口的代理,目标对象必须实现至少一个接口。Spring Boot使用JDK动态代理机制生成代理对象,代理对象实现了目标对象所实现的接口,并且将方法调用转发给目标对象。JDK动态代理通过Proxy类和InvocationHandler接口实现。
  • CGLIB动态代理:CGLIB动态代理是基于类的代理,不需要目标对象实现接口。Spring Boot使用CGLIB动态代理机制通过生成目标对象的子类来实现代理,代理对象继承了目标对象的所有方法,并且可以重写其中的方法来实现增强逻辑。

当目标对象的方法被调用时,AOP框架会根据切面定义和通知定义来决定是否需要对目标对象的方法进行增强。如果需要增强,AOP框架会通过代理对象来调用目标对象的方法,并在调用前后执行通知中定义的增强逻辑。

背景功能说明

在开发应用系统的时候,我需要了解什么接口是什么IP在访问,用时多少,用什么参数来请求,请求之后的结果返回的事情全部记录下来,用来帮助自己分析本系统的热点数据、接口访问频率,访问地址等等信息,可以根据这些信息做系统的改进策略。

代码开发

创建数据表

CREATE TABLE `api_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`ip_addr` varchar(100) DEFAULT NULL COMMENT '访问IP地址',`uri` varchar(500) DEFAULT NULL COMMENT '请求接口地址',`method_name` varchar(255) DEFAULT NULL COMMENT '请求接口方法名',`description` varchar(255) DEFAULT NULL COMMENT '请求接口描述',`duration` bigint(20) DEFAULT NULL COMMENT '请求接口用时',`start_time` datetime DEFAULT NULL COMMENT '请求开始时间',`end_time` datetime DEFAULT NULL COMMENT '请求结束时间',`params` text COMMENT '请求接口参数',`log_result` text COMMENT '请求接口返回结果',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=84110 DEFAULT CHARSET=utf8;

注解类

此类用于注解在控制器的方法类上。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {/*** 日志信息描述* @return*/String value() default "";/*** 是否保存到数据库* @return*/boolean save() default true;/*** 是否输出到控制台* @return*/boolean console() default true;}

使用AOP保存接口信息

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.NetUtil;
import com.alibaba.fastjson.JSON;
import com.api.annotation.Loggable;
import com.api.entity.ApiLog;
import com.api.service.ApiLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;@Aspect
@Component
@Slf4j
public class LogAspect {/*** 操作开始时间*/private Long startTime = 0L;@Resourceprivate ApiLogService apiLogService;/*** Controller层切点*/@Pointcut("execution(* com.api.controller..*.*(..))")public void controllerAspect() {}/*** 前置通知 在方法前使用*/@Before("controllerAspect()")public void beforeControllerLog(JoinPoint joinpoint){}@Around("controllerAspect()")public Object aroundControllerLog(ProceedingJoinPoint point) throws Throwable{//获取RequestRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);//获取目标方法Method method = ((MethodSignature) point.getSignature()).getMethod();//判断是否有@Loggable注解if(!method.isAnnotationPresent(Loggable.class)){//如果没有直接返回结果return point.proceed();}//记录日志信息ApiLog logMessage = new ApiLog();/*** 获取方法上的注解实例*/Loggable loggable = method.getAnnotation(Loggable.class);//处理接口请求日志handleRequestLog(point, loggable, request, logMessage);//执行目标方法内容,获取执行结果Object result = point.proceed();//处理接口响应日志handleResponseLog(logMessage, loggable, result);return result;}private void handleRequestLog(ProceedingJoinPoint point, Loggable loggable, HttpServletRequest request, ApiLog logMessage) {//类名String targetName = point.getTarget().getClass().toString();//方法名称String methodName = point.getSignature().getName();Map<String, Object> params = getMethodParamNames(point);//判断是否输出日志if(loggable.console()){log.info("targetName:{},methodName:{},params:{}",targetName,methodName,params);}startTime = System.currentTimeMillis();logMessage.setStartTime(DateUtil.date());logMessage.setDescription(loggable.value());logMessage.setMethodName(methodName);logMessage.setAppKey(request.getParameter("appKey"));logMessage.setUri(request.getRequestURI());logMessage.setIpAddr(getIpAddress(request)==null?request.getRemoteAddr():getIpAddress(request));logMessage.setParams(JSON.toJSONString(params));}private void handleResponseLog(ApiLog logMessage, Loggable loggable, Object result) {/*** 操作结束时间*/Long endTime = System.currentTimeMillis();Long duration = endTime - startTime;logMessage.setDuration(duration);logMessage.setEndTime(DateUtil.date());logMessage.setLogResult(JSON.toJSONString(result));if(loggable.save()){//这里考虑用异步操作进行优化apiLogService.save(logMessage);}if(loggable.console()){log.info("方法执行所需时间 : {} , 输出的结果 : {}",duration,result);}}//获取请求方法的参数private static Map<String, Object> getMethodParamNames(ProceedingJoinPoint point) {Map<String, Object> param = new HashMap<>();Object[] paramValues = point.getArgs();String[] paramNames = ((CodeSignature) point.getSignature()).getParameterNames();for (int i = 0; i < paramNames.length; i++) {param.put(paramNames[i], paramValues[i]);}return param;}// 获取代理前的IPprivate String getIpAddress(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}if (ip.contains(",")) {return ip.split(",")[0];} else {return ip;}}
}

上述代码即可实现保存接口访问的信息。

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

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

相关文章

BGP公认必遵属性——Origin(二)

BGP公认必遵属性共有三个&#xff0c;分别是&#xff1a;Next-hop、Origin、As-path&#xff0c;本期介绍Origin 点赞关注&#xff0c;持续更新&#xff01;&#xff01;&#xff01; Origin Origin属性用来定义路径信息的来源&#xff0c;只要不被修改&#xff0c;该属性就不…

【Java集合篇】ConcurrentHashMap是如何保证线程安全的

ConcurrentHashMap是如何保证线程安全的 ✔️典型解析✔️ 拓展知识仓✔️ 什么是CAS&#xff08;Compare And Swap&#xff09;✔️CAS和互斥量有什么区别✔️如何使用CAS和互斥量 ✔️CAS和Synchronized的区别✔️ConcurrentHashMap的优缺点✔️能用ConcurrentHashMap实现队列…

python对常见的激活函数绘图操作(详细代码讲解)

写论文的时候需要做一些激活函数的图像&#xff0c;为此将常见的激活函数进行整理汇总了一下&#xff0c;方便后续的复习 激活函数的作用是为让模型处理非线性问题&#xff0c;故次激活函数都是非线性的 生活中&#xff0c;非线性问题占大多数&#xff0c;而模型的训练通常都是…

metartc5_jz源码阅读-yang_send_avpacket

//pushh264中调用此方法将rtp包发送给p2p对端。 int32_t yang_send_avpacket(YangRtcSession *session, YangRtpPacket *pkt, YangBuffer *pbuf) {int32_t err Yang_Ok;//获取到pbuf的size作为要加密的sizeint32_t nn_encrypt yang_buffer_pos(pbuf);//将pbuf中的数据根据seq…

在React里面使用mobx状态管理详细步骤

1、安装MobX和MobX React&#xff1a; 在你的项目目录下运行以下命令安装MobX和MobX React&#xff1a; npm install mobx mobx-react2、创建MobX Store&#xff1a; 创建一个用于管理状态的MobX Store。这个Store应该包含你希望全局管理的状态和相关的操作。以下是一个简单…

flask flask-sqlalchemy sqlit3

这次是数据库使用&#xff0c;拒绝花哨主打就是一个简单 pip install flask-sqlalchemy 调用数据库现在配置里边设置下然后绑上APP后&#xff0c;定义数据结构类.下面是我认为最简单的数据库增删查改结构。 from flask_sqlalchemy import SQLAlchemy app.config[SQLALCHEMY_DAT…

哈希表-散列表数据结构

1、什么是哈希表&#xff1f; 哈希表也叫散列表&#xff0c;哈希表是根据关键码值(key value)来直接访问的一种数据结构&#xff0c;也就是将关键码值(key value)通过一种映射关系映射到表中的一个位置来加快查找的速度&#xff0c;这种映射关系称之为哈希函数或者散列函数&…

Rollup-plugin-bundle-analyzer VS Rollup-plugin-visualizer

分析和可视化Rollup打包后的文件的插件 Rollup-plugin-bundle-analyzerRollup-plugin-visualizer Rollup-plugin-bundle-analyzer和Rollup-plugin-visualizer都是用于分析和可视化Rollup打包后的文件的插件&#xff0c;但它们在功能和使用方式上存在一些差异。 Rollup-plugi…

PostGIS教程学习十九:基于索引的聚簇

PostGIS教程学习十九&#xff1a;基于索引的聚簇 数据库只能以从磁盘获取信息的速度检索信息。小型数据库将完全位于于RAM缓存&#xff08;内存&#xff09;&#xff0c;并摆脱物理磁盘访问速度慢的限制。但是对于大型数据库&#xff0c;对物理磁盘的访问将限制数据库的信息检…

FFmpeg获取音视频流信息

文章目录 前言一、需求二、源码三、运行结果 前言 本文记录用 FFmpeg 获取视频流音频流的信息&#xff08;编码格式、分辨率、帧率、播放时长…&#xff09;&#xff0c;所用的工程基于上个博客编译成功的工程&#xff1a;使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c 一、需求…

sqlcmd执行sql文件

可以使用以下命令来在SQL Server中执行SQL脚本文件&#xff08;.sql&#xff09;&#xff1a; sqlcmd -S <服务器名称> -d <数据库名称> -i <脚本文件路径> 其中&#xff0c;<服务器名称>为要连接的 SQL Server 实例的名称或 IP 地址&#xff1b; &l…

掌握 Spring IoC 容器与 Bean 作用域:详解 singleton 与 prototype 的使用与配置

在您的应用程序中&#xff0c;由 Spring IoC 容器管理的形成其核心的对象被称为 "bean"。一个 bean 是由 Spring IoC 容器实例化、组装和管理的对象 这些 bean 是通过您提供给容器的配置元数据创建的。Bean 定义包含了所谓的配置元数据&#xff0c;容器需要了解以下内…

sqlcmd导出sql文件

使用SQLCMD命令行工具可以将数据库中的查询结果导出为SQL文件。 下面是示例代码&#xff1a; sqlcmd -S <服务器名称> -d <数据库名称> -U <用户名> -P <密码> -Q "<查询语句>" -o <输出路径\文件名.sql> 其中&#xff0c;需…

透明OLED拼接屏:重塑大屏显示的新篇章

随着科技的快速发展&#xff0c;大屏显示技术已经逐渐渗透到我们生活的方方面面。作为显示技术领域的一大革新&#xff0c;透明OLED拼接屏以其独特的透明显示特性&#xff0c;正逐渐成为大屏显示市场的新宠。尼伽小编将深入探讨透明OLED拼接屏的技术特点、应用场景以及市场前景…

中国葡萄酒消费者的口味偏好

有一段时间&#xff0c;“中国口味”的问题是全世界葡萄酒销售者的热门话题&#xff0c;因为他们积极探索每一个线索&#xff0c;以发现让他们在市场上领先的秘密。为此进行了大量研究&#xff0c;多年前葡萄酒销售商或多或少形成了一个共识&#xff1a;尽管中国人的口味差异很…

系列十四、while do...while switch模板代码

一、while & do...while & switch模板代码 1.1、while /*** 需求&#xff1a;使用while循环打印5遍Hello World!*/ Test public void print5() {int i 1;while (i < 5) {System.out.println("Hello World! " LocalDateTime.now());// 线程休眠&#x…

Android Framework 常见解决方案(25-2)定制CPUSET解决方案-system修改及编译部分调整

1 原理说明 这个方案有如下基本需求&#xff1a; 构建自定义CPUSET&#xff0c;/dev/cpuset中包含一个全新的cpuset分组。且可以通过set_cpuset_policy和set_sched_policy接口可以设置自定义CPUSET。开机启动后可以通过zygote判定来对特定的应用进程设置CPUSET&#xff0c;并…

Spring MVC学习之——了解MVC设计模式

MVC设计模式 MVC介绍 MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09; - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑&#xff0c;在数据变化时更新…

HTML5中form表单防止重复提交的两种方法

form表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题 (1)点击提交按钮两次。 (2)点击刷新按钮。 (3)使用浏览器后退按钮重复之前的操作 导致重复提交表单。 (4)浏览器重复的HTTP请求。 (5)用户提交表单时可能因为网…

Java项目:112SSM在线电影订票系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 在线电影订票系统基于SpringSpringMVCMybatis开发&#xff0c;系统分为前台和后台&#xff0c;前台主要用来用户浏览电影信息&#xff0c;订票&#xff0c…