自定义注解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注解
就能实现记录日志