- 声明启动类注解、需要import的配置类。 常规情况会额外指定一下Ordered、proxyTargetClass,本例从简
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; import org.springframework.context.annotation.Import; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(LogConfiguration.class) @Documented public @interface EnableLog { /** * 指定包路径 */ String[] basePackages() default {}; }
- 配置类中需要 advise-> LogPointcutAdvisor 、adivce -> LogInterceptor、pointcut -> LogPointcutAdvisor。
import javax.annotation.Resource; import org.springframework.aop.PointcutAdvisor; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.task.TaskExecutor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import lombok.Setter; @Configuration public class LogConfiguration implements ImportAware, EnvironmentAware { @Nullable protected AnnotationAttributes enableLogAttributes; @Setter private Environment environment; @Resource TaskExecutor taskExecutor; @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableLogAttributes = AnnotationAttributes .fromMap(importMetadata.getAnnotationAttributes(EnableLog.class.getName(), false)); if (this.enableLogAttributes == null) { throw new IllegalArgumentException( "@EnableLog is not present on importing class " + importMetadata.getClassName()); } } @Bean public LogInterceptor logInterceptor(TaskExecutor taskExecutor) { return new LogInterceptor(handler(environment), taskExecutor); } @Bean public ILogHandler handler(Environment environment) { return new LocalLogHandler(environment); } @Bean public PointcutAdvisor pointcutAdvisor(LogInterceptor logInterceptor) { LogPointcutAdvisor advisor = new LogPointcutAdvisor(this.enableLogAttributes.getStringArray("basePackages")); advisor.setAdvice(logInterceptor); if (enableLogAttributes != null) { advisor.setOrder(Ordered.LOWEST_PRECEDENCE - 1); } return advisor; } } ---- import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.springframework.aop.ClassFilter; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; import org.springframework.stereotype.Controller; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestMapping; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j public class LogPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor { private static final long serialVersionUID = 1L; private ComposablePointcut pointcut; //组合方式 public LogPointcutAdvisor(String[] basePackages) { pointcut = new ComposablePointcut(new AnnotationMatchingPointcut(Controller.class, RequestMapping.class, true)); if (basePackages != null && basePackages.length > 0) { pointcut.intersection(new LogPackageFilter(Arrays.asList(basePackages))); } } @AllArgsConstructor static class LogPackageFilter implements ClassFilter { private List<String> basePackages; private final ConcurrentHashMap<String, Boolean> classMatchMap = new ConcurrentHashMap<>(50); @Override public boolean matches(Class<?> clazz) { String name = clazz.getName(); boolean match = classMatchMap.computeIfAbsent(name, key -> !CollectionUtils.isEmpty(basePackages) && basePackages.stream().anyMatch(t -> key.startsWith(t))); log.debug("name: {} LogPackageFilter -> {}", name, match); return match; } } @Override public Pointcut getPointcut() { return this.pointcut; } } ---- import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodClassKey; import org.springframework.core.task.TaskExecutor; import org.springframework.lang.Nullable; import org.springframework.web.bind.annotation.RequestMapping; import com.google.common.collect.Maps; public class LogInterceptor implements MethodInterceptor { private final Map<Object, String> uriCache = new ConcurrentHashMap<>(1024); private ILogHandler logHandler; private TaskExecutor taskExecutor; public LogInterceptor(ILogHandler logHandler, TaskExecutor taskExecutor) { this.logHandler = logHandler; this.taskExecutor = taskExecutor; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { long start = System.currentTimeMillis(); String exceptionMsg = null; String exceptionType = null; try { Object result = invocation.proceed(); return result; } catch (Throwable e) { exceptionMsg = e.getMessage(); exceptionType = e.getClass().getName(); throw e; } finally { final String errorMsg = exceptionMsg; final String errorType = exceptionType; long end = System.currentTimeMillis(); taskExecutor.execute(() -> { handLog(invocation, start, end, errorMsg, errorType); }); } } private void handLog(MethodInvocation invocation, long start, long end, final String errorMsg, final String errorType) { Map<String, Object> args = null; Method method = BridgeMethodResolver.findBridgedMethod(invocation.getMethod()); Class<?> targetClass = getTargetClass(invocation.getThis()); String reqUrl = getRequestUrl(method, targetClass); Parameter[] parameters = method.getParameters(); Object[] arguments = invocation.getArguments(); if (parameters != null && parameters.length > 1) { args = Maps.newHashMapWithExpectedSize(15); for (int i = 0; i < parameters.length; i++) { args.put(parameters[i].getName(), i < arguments.length ? arguments[i] : null); } } logHandler.handle(new LogInfo(reqUrl, method.getName(), targetClass.getName(), start, end - start, errorMsg, errorType, args)); } private Class<?> getTargetClass(Object target) { return AopProxyUtils.ultimateTargetClass(target); } public String getRequestUrl(Method method, @Nullable Class<?> targetClass) { if (method.getDeclaringClass() == Object.class) { return null; } Object cacheKey = getCacheKey(method, targetClass); String requestUrl = this.uriCache.get(cacheKey); if (requestUrl == null) { requestUrl = retrieveUriFromHandlerMethod(method, targetClass); this.uriCache.put(cacheKey, requestUrl); } return requestUrl; } private String retrieveUriFromHandlerMethod(Method method, Class<?> targetClass) { RequestMapping classRequestMapping = targetClass.getAnnotation(RequestMapping.class); StringBuilder uriSb = new StringBuilder(256); if (classRequestMapping != null) { String[] value = classRequestMapping.value(); if (ArrayUtils.isNotEmpty(value) && StringUtils.isNotBlank(value[0])) { String classUri = trimFirstSlash(value[0]); classUri = trimLastSlash(classUri); uriSb.append(classUri); } } RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); if (methodRequestMapping != null) { String[] value = methodRequestMapping.value(); if (ArrayUtils.isNotEmpty(value) && StringUtils.isNotBlank(value[0])) { boolean hasClassUri = uriSb.length() != 0; String methodUri = trimFirstSlash(value[0]); if (hasClassUri) { uriSb.append("/"); } uriSb.append(methodUri); } } return uriSb.toString().replaceAll("[{}]", ""); } private String trimFirstSlash(String uri) { return uri.startsWith("/") ? uri.substring(1) : uri; } private String trimLastSlash(String uri) { return uri.lastIndexOf("/") == uri.length() - 1 ? uri.substring(0, uri.length() - 1) : uri; } private Object getCacheKey(Method method, Class<?> targetClass) { return new MethodClassKey(method, targetClass); } }
-
实际的日志操作处理类
/** * * 日志操作 */ public interface ILogHandler { String getAppName(); void handle(LogInfo logInfo); } import org.springframework.core.env.Environment; import com.yy.cs.base.json.Json; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; /** * * 本地日志输出 */ @Slf4j @AllArgsConstructor public class LocalLogHandler implements ILogHandler { Environment environment; @Override public String getAppName() { return environment.getProperty("spring.application.name"); } @Override public void handle(LogInfo logInfo) { log.info("request log: {}", Json.ObjToStr(logInfo)); } } ---- import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class LogInfo { private String requestUrl; private String method; private String clas; private long start; private long cost; private String errorMsg; private String exceptionType; private Map<String, Object> args; }