基本思想
- 凡在目标实例上或在目标实例方法(非静态方法)上标注自定义注解
@AutoLog
,其方法执行时将触发AOP操作;
@AutoLog
只有一个参数,用来控制是否打印该方法的参数和返回结果的json字符串,默认不打印,通过@AutoLog(true)
开启
- 通过AOP拦截方法并打印日志
代码
package com.yan.mssm.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoLog {
// log params and result when true
boolean value() default false;
}
package com.yan.mssm.aop;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.el.MethodNotFoundException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Aspect
public class AutoLogAspectJ {
private static final String PREFIX = "[";
private static final String SUFFIX = "]";
private static final String END = "end";
private static final String BEGIN = "begin";
private static final String PARAMS = "params";
private static final String RESULT = "result";
private static final String DELIMITER = " || ";
private static final int STRING_MAX_LENGTH = 1 << 10;
private static final Class<AutoLog> AUTO_LOG_CLASS = AutoLog.class;
private static final String LOG_FORMAT = "[AOP-LOG]->Method {}: {}";
private static final Class<RequestMapping> REQUEST_MAPPING_CLASS = RequestMapping.class;
private static Logger LOGGER;
private Method method;
private String url;
private Class<?> targetClass;
private String methodSignature;
private boolean debug;
private static Object getCglibProxyTargetObject(Object proxy) {
Field h;
try {
h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
} catch (Exception e) {
return null;
}
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) {
try {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
} catch (Exception e) {
return null;
}
}
private Object getTarget(Object proxy) {
if (!AopUtils.isAopProxy(proxy)) {
return proxy;
}
if (AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
}
return getCglibProxyTargetObject(proxy);
}
@Around(value = "@within(com.yan.mssm.aop.AutoLog) || @annotation(com.yan.mssm.aop.AutoLog)")
public Object test(ProceedingJoinPoint point) throws Throwable {
init(point);
Optional.ofNullable(url).ifPresent(u -> LOGGER.info("[AOP-LOG]->Input URL:{}", u));
LOGGER.info(LOG_FORMAT, BEGIN, methodSignature);
printParams(point);
Object result = point.proceed();
printResult(result);
LOGGER.info(LOG_FORMAT, END, methodSignature);
return result;
}
private void init(ProceedingJoinPoint point) {
Signature signature = point.getSignature();
methodSignature = signature.toString();
targetClass = point.getTarget().getClass();
LOGGER = LoggerFactory.getLogger(targetClass);
method = Arrays.stream(targetClass.getDeclaredMethods())
.filter(method -> method.toString().equals(signature.toLongString()))
.findFirst().orElseThrow(MethodNotFoundException::new);
setUrl();
debug = isDebug();
}
private void printResult(Object result) {
if (!debug) {
return;
}
Optional.ofNullable(result).ifPresent(r -> LOGGER.info(LOG_FORMAT, RESULT, r));
}
private void printParams(ProceedingJoinPoint point) {
if (!debug) {
return;
}
Object[] args = point.getArgs();
if (args.length == 0) {
return;
}
MethodSignature methodSignature = (MethodSignature) point.getSignature();
String[] paramNames = methodSignature.getParameterNames();
Map<String, Object> argMap = new LinkedHashMap<>();
for (int i = 0; i < args.length; i++) {
String argName = paramNames[i];
argMap.put(argName, getTarget(args[i]));
}
String paramString = toJsonString(argMap);
Optional.ofNullable(paramString)
.ifPresent(str -> LOGGER.info(LOG_FORMAT, PARAMS, str));
}
private boolean isDebug() {
return (targetClass.isAnnotationPresent(AUTO_LOG_CLASS) && targetClass.getAnnotation(AUTO_LOG_CLASS).value())
|| (method.isAnnotationPresent(AutoLog.class) && method.getAnnotation(AUTO_LOG_CLASS).value());
}
private void setUrl() {
boolean controller = targetClass.isAnnotationPresent(RestController.class)
|| targetClass.isAnnotationPresent(Controller.class);
if (!controller) {
url = null;
return;
}
List<String> urlList = getUrlList();
url = getCollect(urlList);
}
private List<String> getUrlList() {
List<String> urlList = new ArrayList<>();
String[] urls;
boolean hasControllerMapping = hasControllerMapping();
boolean hasMethodMapping = hasMethodMapping();
if (!hasControllerMapping && !hasMethodMapping) {
return urlList;
}
if (!hasControllerMapping) {
urls = method.getAnnotation(REQUEST_MAPPING_CLASS).value();
urlList.addAll(Arrays.asList(urls));
return urlList;
}
if (!hasMethodMapping) {
urls = targetClass.getAnnotation(REQUEST_MAPPING_CLASS).value();
urlList.addAll(Arrays.asList(urls));
return urlList;
}
Arrays.stream(targetClass.getAnnotation(REQUEST_MAPPING_CLASS).value())
.forEach(ctlrUrl ->
Arrays.stream(method.getAnnotation(REQUEST_MAPPING_CLASS).value())
.forEach(methodUrl -> {
StringBuilder sbd = new StringBuilder();
urlList.add(sbd.append(ctlrUrl).append(methodUrl).toString());
}));
return urlList;
}
private boolean hasMethodMapping() {
return method.isAnnotationPresent(REQUEST_MAPPING_CLASS)
&& method.getAnnotation(REQUEST_MAPPING_CLASS).value().length > 0;
}
private boolean hasControllerMapping() {
return targetClass.isAnnotationPresent(REQUEST_MAPPING_CLASS)
&& targetClass.getAnnotation(REQUEST_MAPPING_CLASS).value().length > 0;
}
private String getCollect(List<String> urlList) {
return urlList.stream().collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX));
}
private String toJsonString(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String result = null;
try {
byte[] bytes = objectMapper.writeValueAsBytes(object);
result = new String(bytes, "utf-8");
result = result.length() > STRING_MAX_LENGTH ? null : result;
} catch (Exception e) {
LOGGER.error("AutoLogAspectJ.toJsonString exception:
{}", e.getMessage());
}
return result;
}
}