想要看到某一个方法的执行情况,比如现在有一个testService的类,调用该类中的testRpcService方法,想要打印日志如下,一般情况下会把日志打印的代码每次自己手动拷贝类名、方法名,然后写同样的日志:
每次都自己拷贝,有时候还会拷错,我想要优雅的写代码,因此记录几种记录方法执行日志的方式
一、封装lambda表达式成util工具
工具代码:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
/**
* @author ZhaoXu
* @date 2023/4/15 19:22
*/
@Slf4j
public class MethodExecuteUtils {
@FunctionalInterface
public interface LogMethodFunction<Q, B> extends Serializable {
/**
* 执行方法
* @param q
* @return
*/
B execute(Q q);
/**
* 获取lambda方法
*
* @return
* @throws Exception
*/
default SerializedLambda getSerializedLambda() throws Exception {
Method write = this.getClass().getDeclaredMethod("writeReplace");
write.setAccessible(true);
return (SerializedLambda) write.invoke(this);
}
}
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* 执行rpc远程调用逻辑
*
* @param function
* @param <Q> response
* @param <B> response
* @return
*/
public static <Q, B> B logAround(Q request, LogMethodFunction<Q, B> function) {
SerializedLambda serializedLambda = null;
try {
serializedLambda = function.getSerializedLambda();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 类名
String className = serializedLambda.getImplClass();
String[] packageSplit = className.split("/");
className = packageSplit[packageSplit.length - 1];
// 方法名
String methodName = serializedLambda.getImplMethodName();
methodName = className + "." + methodName + " ";
log.info(methodName + "execute before, requestBody: {}", OBJECT_MAPPER.valueToTree(request));
B responseBody = null;
long startTime = System.currentTimeMillis();
try {
responseBody = function.execute(request);
} catch (Throwable e) {
log.error(methodName + "execute error, exception: {}", OBJECT_MAPPER.valueToTree(e));
} finally {
log.info(methodName + "execute after, responseBody: {}", OBJECT_MAPPER.valueToTree(responseBody));
log.info(methodName + "execute time: {}", System.currentTimeMillis() - startTime + " millisecond");
}
return responseBody;
}
}
使用方式:
BaseRequest baseRequest = new BaseRequest();
baseRequest.setPage(1);
BaseResponse baseResponse = MethodExecuteUtils.logAround(baseRequest, testService::testRpcService);
二、使用spring aop切面的形式去增强方法
2.1 首先定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author KuiChi
* @date 2023/4/15 16:18
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAround {
/**
* 执行异常message
* @return
*/
String detailMessage() default "业务执行异常";
}
2.2 然后写一个aspect方法去增强
package com.example.springbootdemoz.aspect;
import com.example.springbootdemoz.annotation.LogAround;
import com.example.springbootdemoz.exception.BusinessException;
import com.example.springbootdemoz.exception.ErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author KuiChi
* @date 2023/6/4 16:13
*/
@Component
@Aspect
@Slf4j
public class LogMethodAspect {
private static final String DEFAULT_SERVICE_STR = "Service";
private static final ClassPool CLASS_POOL = ClassPool.getDefault();
private static Field DETAIL_MESSAGE_FIELD;
private static Field CAUSE_FIELD;
private static Method FILL_IN_STACK_TRACE_METHOD;
static {
try {
DETAIL_MESSAGE_FIELD = Throwable.class.getDeclaredField("detailMessage");
DETAIL_MESSAGE_FIELD.setAccessible(Boolean.TRUE);
CAUSE_FIELD = Throwable.class.getDeclaredField("cause");
CAUSE_FIELD.setAccessible(Boolean.TRUE);
FILL_IN_STACK_TRACE_METHOD = Throwable.class.getDeclaredMethod("fillInStackTrace");
FILL_IN_STACK_TRACE_METHOD.setAccessible(Boolean.TRUE);
} catch (NoSuchFieldException | NoSuchMethodException e) {
e.printStackTrace();
}
}
@Autowired
private ObjectMapper objectMapper;
@Around("@annotation(logAround)")
public Object doAround(ProceedingJoinPoint joinPoint, LogAround logAround) {
Signature signature = joinPoint.getSignature();
Map<String, Object> paramNameAndValue = getParamNameAndValue(joinPoint);
// 类名
String fullClassName = signature.getDeclaringTypeName();
int lastPointIndex = fullClassName.lastIndexOf(".");
String className = fullClassName.substring(lastPointIndex + 1);
// 方法名
String methodName = signature.getName();
methodName = className + "#" + methodName + " ";
Object proceed = null;
long startTime = System.currentTimeMillis();
try {
proceed = joinPoint.proceed();
// 方法所在类.方法名
// TestServiceImpl#testService execute after, params:{}, response:{}
log.info(methodName + "execute success, params:{}, response:{}", objectMapper.valueToTree(paramNameAndValue),
objectMapper.valueToTree(proceed));
log.info(methodName + "execute time:{}", (System.currentTimeMillis() - startTime) + " millisecond");
} catch (Throwable throwable) {
// TestServiceImpl#testService execute error, params:{}
log.error(methodName + "execute error, params:{}", objectMapper.valueToTree(paramNameAndValue), throwable);
throw getException(fullClassName, logAround, throwable);
}
return proceed;
}
private BusinessException getException(String fullClassName, LogAround logAround, Throwable throwable) {
BusinessException businessException;
try {
String subClassName = "";
if (fullClassName.contains(DEFAULT_SERVICE_STR)) {
int service = fullClassName.toLowerCase().lastIndexOf("service");
subClassName = fullClassName.substring(0, service) + "Exception";
} else {
subClassName = fullClassName + "BaseException";
}
Class<?> subClass;
synchronized (CLASS_POOL) {
try {
subClass = Class.forName(subClassName, true, CLASS_POOL.getClassLoader());
} catch (ClassNotFoundException e) {
// 需要生成类
CtClass subCtClass = CLASS_POOL.makeClass(subClassName);
CtClass superClass = CLASS_POOL.get(BusinessException.class.getName());
subCtClass.setSuperclass(superClass);
//构造函数
CtClass stringClass = CLASS_POOL.get(String.class.getName());
CtClass[] constructorParamClassArr = new CtClass[]{stringClass, CtClass.intType};
CtConstructor constructor = CtNewConstructor.make(constructorParamClassArr, null, subCtClass);
subCtClass.addConstructor(constructor);
subCtClass.toClass();
subCtClass.detach();
subClass = Class.forName(subClassName, true, CLASS_POOL.getClassLoader());
}
}
businessException = (BusinessException) subClass.getConstructor(String.class, int.class).newInstance(logAround.detailMessage(), ErrorCode.BUSINESS_EXCEPTION.getCode());
DETAIL_MESSAGE_FIELD.set(businessException, logAround.detailMessage());
CAUSE_FIELD.set(businessException, throwable);
FILL_IN_STACK_TRACE_METHOD.invoke(businessException);
} catch (Exception ex) {
log.error("getException error", ex);
businessException = new BusinessException(ErrorCode.BUSINESS_EXCEPTION.getDesc(), ErrorCode.BUSINESS_EXCEPTION.getCode());
}
return businessException;
}
/**
* 获取参数Map集合
*
* @param joinPoint
* @return
*/
private Map<String, Object> getParamNameAndValue(ProceedingJoinPoint joinPoint) {
Map<String, Object> param = new HashMap<>(8);
Object[] paramValues = joinPoint.getArgs();
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
for (int i = 0; i < paramNames.length; i++) {
param.put(paramNames[i], paramValues[i]);
}
return param;
}
}
使用方法,直接注解在方法上就可以了,会自动进行环绕。
@LogAround(detailMessage = "查询数据异常")
public BaseResponse testRpcService(BaseRequest request) {
BaseResponse baseResponse = new BaseResponse();
baseResponse.setName("zx");
return baseResponse;
}