仿@EnableAsync 实现自定义切面日志

  1. 声明启动类注解、需要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 {};
    }
  2. 配置类中需要 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 basePackages;
            private final ConcurrentHashMap 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 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 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);
        }
    }
    
  3. 实际的日志操作处理类

    /**
     * 
     * 日志操作
     */
    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 args;
    }

 

 

转载于:https://my.oschina.net/u/3434392/blog/3068174

你可能感兴趣的:(仿@EnableAsync 实现自定义切面日志)