当前位置: 首页 > backend >正文

自定义注解facade 实现切面 进行日志记录和参数校验

目录

注解

切面

结论


注解

package cn.hollis.nft.turbo.rpc.facade;
/*** @author Hollis* FacadeAspect 类里的 facade 方法是一个 AOP(面向切面编程)环绕通知,* 它会拦截所有被 @cn.hollis.nft.turbo.rpc.facade.Facade* 注解标记的方法,* 对这些方法进行统一的参数校验、异常捕获和日志记录,增强服务方法的健壮性和可维护性。*/
public @interface Facade {
}

切面

package cn.hollis.nft.turbo.rpc.facade;import cn.hollis.nft.turbo.base.exception.BizException;
import cn.hollis.nft.turbo.base.exception.SystemException;
import cn.hollis.nft.turbo.base.response.BaseResponse;
import cn.hollis.nft.turbo.base.response.ResponseCode;
import cn.hollis.nft.turbo.base.utils.BeanValidator;
import com.alibaba.fastjson2.JSON;
import jakarta.validation.ValidationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;/*** Facade的切面处理类,用于统一进行参数校验、异常捕获和日志记录,增强服务方法的健壮性和可维护性。** @author Hollis*/
@Aspect
@Component
public class FacadeAspect {/*** 日志记录器,用于记录切面处理过程中的相关信息。*/private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class);/*** 环绕通知,拦截所有被 @cn.hollis.nft.turbo.rpc.facade.Facade 注解标记的方法,进行参数校验、异常捕获和日志记录。** @param pjp 连接点对象,包含目标方法的信息,可用于执行目标方法。* @return 目标方法的返回值或处理后的失败响应。* @throws Exception 可能抛出的异常,包括目标方法抛出的异常和切面处理过程中的异常。*/@Around("@annotation(cn.hollis.nft.turbo.rpc.facade.Facade)")public Object facade(ProceedingJoinPoint pjp) throws Exception {// 创建一个计时器,用于记录方法执行耗时StopWatch stopWatch = new StopWatch();stopWatch.start();// 获取目标方法对象Method method = ((MethodSignature) pjp.getSignature()).getMethod();// 获取目标方法的参数数组Object[] args = pjp.getArgs();// 记录方法开始执行的日志,包含方法名和参数信息LOGGER.info("start to execute , method = " + method.getName() + " , args = " + JSON.toJSONString(args));// 获取目标方法的返回类型Class returnType = ((MethodSignature) pjp.getSignature()).getMethod().getReturnType();// 循环遍历目标方法的所有参数,进行参数校验for (Object parameter : args) {try {// 使用 BeanValidator 工具类对参数进行校验BeanValidator.validateObject(parameter);} catch (ValidationException e) {// 参数校验失败,记录失败日志printLog(stopWatch, method, args, "failed to validate", null, e);// 返回通用的失败响应return getFailedResponse(returnType, e);}}try {// 执行目标方法,并获取返回值Object response = pjp.proceed();// 补全响应对象的信息,主要是 code 和 messageenrichObject(response);// 记录方法执行结束的日志printLog(stopWatch, method, args, "end to execute", response, null);return response;} catch (Throwable throwable) {// 目标方法执行过程中抛出异常,记录失败日志printLog(stopWatch, method, args, "failed to execute", null, throwable);// 返回通用的失败响应return getFailedResponse(returnType, throwable);}}/*** 打印方法执行相关的日志信息,使用统一的日志格式。** @param stopWatch 计时器,记录方法执行耗时。* @param method    目标方法对象。* @param args      目标方法的参数数组。* @param action    操作行为描述,如 "failed to validate"。* @param response  目标方法的响应对象。* @param throwable 可能抛出的异常对象。*/private void printLog(StopWatch stopWatch, Method method, Object[] args, String action, Object response,Throwable throwable) {try {// 调用 getInfoMessage 方法生成统一格式的日志信息,并记录日志LOGGER.info(getInfoMessage(action, stopWatch, method, args, response, throwable), throwable);} catch (Exception e1) {// 日志生成或记录过程中出现异常,记录错误日志LOGGER.error("log failed", e1);}}/*** 生成统一格式的日志信息,方便进行日志统计和监控。* 注意:如果调整此处的格式,需要同步调整日志监控配置。** @param action    操作行为描述。* @param stopWatch 计时器,记录方法执行耗时。* @param method    目标方法对象。* @param args      目标方法的参数数组。* @param response  目标方法的响应对象。* @param exception 可能抛出的异常对象。* @return 拼接后的统一格式的日志信息字符串。*/private String getInfoMessage(String action, StopWatch stopWatch, Method method, Object[] args, Object response,Throwable exception) {// 使用 StringBuilder 拼接日志信息StringBuilder stringBuilder = new StringBuilder(action);stringBuilder.append(" ,method = ");stringBuilder.append(method.getName());stringBuilder.append(" ,cost = ");stringBuilder.append(stopWatch.getTime()).append(" ms");// 如果响应对象是 BaseResponse 类型,添加 success 信息if (response instanceof BaseResponse) {stringBuilder.append(" ,success = ");stringBuilder.append(((BaseResponse) response).getSuccess());}// 如果存在异常,添加 success 为 false 的信息if (exception != null) {stringBuilder.append(" ,success = ");stringBuilder.append(false);}stringBuilder.append(" ,args = ");stringBuilder.append(JSON.toJSONString(Arrays.toString(args)));// 如果响应对象不为空,添加响应信息if (response != null) {stringBuilder.append(" ,resp = ");stringBuilder.append(JSON.toJSONString(response));}// 如果存在异常,添加异常信息if (exception != null) {stringBuilder.append(" ,exception = ");stringBuilder.append(exception.getMessage());}// 如果响应对象是 BaseResponse 类型且执行失败,添加执行失败信息if (response instanceof BaseResponse) {BaseResponse baseResponse = (BaseResponse) response;if (!baseResponse.getSuccess()) {stringBuilder.append(" , execute_failed");}}return stringBuilder.toString();}/*** 补全响应对象的信息,主要是设置 responseCode 和 responseMessage。* 如果响应对象是 BaseResponse 类型,根据 success 状态设置默认的 responseCode。** @param response 响应对象。*/private void enrichObject(Object response) {if (response instanceof BaseResponse) {if (((BaseResponse) response).getSuccess()) {// 如果状态是成功的,且 responseCode 未设置,则设置为 SUCCESSif (StringUtils.isEmpty(((BaseResponse) response).getResponseCode())) {((BaseResponse) response).setResponseCode(ResponseCode.SUCCESS.name());}} else {// 如果状态是失败的,且 responseCode 未设置,则设置为 BIZ_ERRORif (StringUtils.isEmpty(((BaseResponse) response).getResponseCode())) {((BaseResponse) response).setResponseCode(ResponseCode.BIZ_ERROR.name());}}}}/*** 定义并返回一个通用的失败响应。* 如果返回类型是 BaseResponse 的子类,则创建一个失败的 BaseResponse 实例并设置相关信息。** @param returnType 目标方法的返回类型。* @param throwable  抛出的异常对象。* @return 通用的失败响应对象,若返回类型不是 BaseResponse 的子类则返回 null。* @throws NoSuchMethodException     如果找不到指定的构造方法。* @throws IllegalAccessException    如果无法访问构造方法。* @throws InvocationTargetException 如果构造方法调用抛出异常。* @throws InstantiationException    如果实例化对象失败。*/private Object getFailedResponse(Class returnType, Throwable throwable)throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {// 判断返回类型是否为 BaseResponse 的子类if (returnType.getDeclaredConstructor().newInstance() instanceof BaseResponse) {// 创建 BaseResponse 实例BaseResponse response = (BaseResponse) returnType.getDeclaredConstructor().newInstance();// 设置响应状态为失败response.setSuccess(false);if (throwable instanceof BizException bizException) {// 如果是业务异常,设置响应信息和错误码response.setResponseMessage(bizException.getErrorCode().getMessage());response.setResponseCode(bizException.getErrorCode().getCode());} else if (throwable instanceof SystemException systemException) {// 如果是系统异常,设置响应信息和错误码response.setResponseMessage(systemException.getErrorCode().getMessage());response.setResponseCode(systemException.getErrorCode().getCode());} else {// 其他异常,设置响应信息和默认错误码response.setResponseMessage(throwable.toString());response.setResponseCode(ResponseCode.BIZ_ERROR.name());}return response;}// 返回类型不是 BaseResponse 的子类,记录错误日志LOGGER.error("failed to getFailedResponse , returnType (" + returnType + ") is not instanceof BaseResponse");return null;}
}

结论

那么我们在指定类上方打上 facade注解

就能实现记录日志

http://www.xdnf.cn/news/12138.html

相关文章:

  • Xcode 16.4 + iOS 18 系统运行时崩溃:___cxa_current_primary_exception 符号丢失的原因与解决方案
  • 用 n8n 提取静态网页内容:从 HTTP Request 到 HTML 节点全解析
  • 国产linux系统(银河麒麟,统信uos)使用 PageOffice在线编辑word文件保存数据同时保存文件
  • Ubuntu20.04设置为开机后直接自动进入纯命令行界面
  • mysql复合查询mysql子查询
  • 深度学习姿态估计实战:基于ONNX Runtime的YOLOv8 Pose部署全解析
  • IDEA:配置 Git 需要完成 Git 路径设置、账号认证以及仓库关联三个主要步骤
  • 目标检测实战:让AI“看见“并定位物体(superior哥AI系列第11期)
  • [Zynq] Zynq Linux 环境下 AXI UART Lite 使用方法详解(代码示例)
  • ArcGIS Pro 3.4 二次开发 - 宗地
  • HarmonyOS:如何在启动框架中初始化HMRouter
  • 【前端】vue3性能优化方案
  • 【Linux】Linux基础指令1
  • RPA+AI:自动化办公机器人开发指南
  • 基于值函数的强化学习算法之Double Q-Learning详解
  • 129、QT搭建FFmpeg环境
  • vue3+ts实现百度地图鼠标绘制多边形
  • 【websocket】安装与使用
  • 在word中点击zotero Add/Edit Citation没有反应的解决办法
  • 前端js获取当前经纬度(H5/pc/mac/window都可用)
  • 腾讯云V3签名
  • php apache构建 Web 服务器
  • 【Rust宏编程】Rust有关宏编程底层原理解析与应用实战
  • 【Linux】POSIX信号量
  • uniapp运行在微信开发者工具中流程
  • 佳易王钟表手表维修养护管理系统:高效便捷的维修管理解决方案
  • 使用cephadm离线部署reef 18版并配置对接openstack
  • 传统足浴行业数字化转型:线上预约平台的技术架构与商业逻辑
  • 今日行情明日机会——20250604
  • FTP 和 SFTP 介绍及 C/C++ 实现分析