ControllerInterceptor 

package com.fiend.ou.cdp.monitorcollect.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author langpf
 */
@Aspect
@Component
public class WebQueryAespect {
    private final Logger log = LoggerFactory.getLogger(getClass());

    //ThreadLocal 维护变量 避免同步
    //ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    ThreadLocal<Long> startTime = new ThreadLocal<>();// 开始时间

    /**
     * map1存放方法被调用的次数O
     */
    ThreadLocal<Map<String, Long>> methodCalledNumMap = new ThreadLocal<>();

    /**
     * map2存放方法总耗时
     */
    ThreadLocal<Map<String, Long>> methodCalledTimeMap = new ThreadLocal<>();

    /**
     * 定义一个切入点. 解释下:
     * <p>
     * ~ 第一个 * 代表任意修饰符及任意返回值. ~ 第二个 * 定义在web包或者子包 ~ 第三个 * 任意方法 ~ .. 匹配任意数量的参数.
     */
    static final String pCutStr = "execution(public * com.fiend.*..*(..))";

    @Pointcut(value = pCutStr)
    public void logPointcut() {
    }

    /**
     * Aop:环绕通知 切整个包下面的所有涉及到调用的方法的信息
     * @param joinPoint jp
     * @return o
     * @throws Throwable t
     */
    @Around("logPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //初始化 一次
        if (methodCalledNumMap.get() == null) {
            methodCalledNumMap.set(new HashMap<>());
        }

        if (methodCalledTimeMap.get() == null) {
            methodCalledTimeMap.set(new HashMap<>());
        }

        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            if (result == null) {
                //如果切到了 没有返回类型的void方法,这里直接返回
                return null;
            }
            long end = System.currentTimeMillis();

            log.info("===================");
            String targetClassName = joinPoint.getSignature().getDeclaringTypeName();
            String methodName      = joinPoint.getSignature().getName();

            Object[] args = joinPoint.getArgs();// 参数
            int argsSize = args.length;
            String argsTypes = "";
            String typeStr = joinPoint.getSignature().getDeclaringType().toString().split(" ")[0];
            String returnType = joinPoint.getSignature().toString().split(" ")[0];
            log.info("类/接口:" + targetClassName + "(" + typeStr + ")");
            log.info("方法:" + methodName);
            log.info("参数个数:" + argsSize);
            log.info("返回类型:" + returnType);
            if (argsSize > 0) {
                // 拿到参数的类型
                for (Object object : args) {
                    argsTypes += object.getClass().getTypeName().toString() + " ";
                }
                log.info("参数类型:" + argsTypes);
            }

            Long total = end - start;
            log.info("耗时: " + total + " ms!");

            if (methodCalledNumMap.get().containsKey(methodName)) {
                Long count = methodCalledNumMap.get().get(methodName);
                methodCalledNumMap.get().remove(methodName);//先移除,在增加
                methodCalledNumMap.get().put(methodName, count + 1);

                count = methodCalledTimeMap.get().get(methodName);
                methodCalledTimeMap.get().remove(methodName);
                methodCalledTimeMap.get().put(methodName, count + total);
            } else {
                methodCalledNumMap.get().put(methodName, 1L);
                methodCalledTimeMap.get().put(methodName, total);
            }

            return result;
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("====around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : "
                    + e.getMessage());
            throw e;
        }

    }

    //对Controller下面的方法执行前进行切入,初始化开始时间
    @Before(value = "execution(public * com.fiend.*.controller.*.*(..))")
    public void beforeMethod(JoinPoint jp) {
        startTime.set(System.currentTimeMillis());
    }

    //对Controller下面的方法执行后进行切入,统计方法执行的次数和耗时情况
    //注意,这里的执行方法统计的数据不止包含Controller下面的方法,也包括环绕切入的所有方法的统计信息
    @AfterReturning(value = "execution(* com.fiend.*.controller.*.*(..))")
    public void afterMethod(JoinPoint jp) {
        long end = System.currentTimeMillis();
        long total = end - startTime.get();
        String methodName = jp.getSignature().getName();
        log.info("连接点方法为:" + methodName + ",执行总耗时为:" + total + "ms");

        //重新new一个map
        Map<String, Long> map = new HashMap<>();

        //从map2中将最后的 连接点方法给移除了,替换成最终的,避免连接点方法多次进行叠加计算
        //由于map2受ThreadLocal的保护,这里不支持remove,因此,需要单开一个map进行数据交接
        for (Map.Entry<String, Long> entry : methodCalledTimeMap.get().entrySet()) {
            if (entry.getKey().equals(methodName)) {
                map.put(methodName, total);

            } else {
                map.put(entry.getKey(), entry.getValue());
            }
        }

        for (Map.Entry<String, Long> entry : methodCalledNumMap.get().entrySet()) {
            for (Map.Entry<String, Long> entry2 : map.entrySet()) {
                if (entry.getKey().equals(entry2.getKey())) {
                    System.err.println(entry.getKey() + ",被调用次数:" + entry.getValue() + ",综合耗时:" + entry2.getValue() + "ms");
                }
            }

        }
    }
}

2. 统计接口调用次数及成功率

介绍:
  很多时候会需要提供一些统计记录的,比如某个服务一个月的被调用量、接口的调用次数、成功调用次数等等。
优点:
  使用AOP+Hendler对业务逻辑代码无侵入,完全解耦。通过spring boot自带的健康检查接口(/health)方便、安全。
注意:
  数据没有被持久化,只保存在内存中,重启后数据将被重置。可按需自己实现 
代码:
  AOP:在AOP中调用Handler

2.1 ControllerAdvice

@Component
@Aspect
public class ControllerAdvice {
  private static ILogger log = LoggerFactory.getLogger(ControllerAdvice.class);

  @Around("execution(public * *..*controller.*.*(..))")
  public Object handle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    Object result;
    try {
      Function<ProceedingJoinPoint, AbstractControllerHandler> build = AbstractControllerHandler.getBuild();
      if (null == build) {
        AbstractControllerHandler.registerBuildFunction(DefaultControllerHandler::new);
      }
      build = AbstractControllerHandler.getBuild();
      AbstractControllerHandler controllerHandler = build.apply(proceedingJoinPoint);
      if (null == controllerHandler) {
        log.warn(String.format("The method(%s) do not be handle by controller handler.", proceedingJoinPoint.getSignature().getName()));
        result = proceedingJoinPoint.proceed();
      } else {
        result = controllerHandler.handle();
      }
    } catch (Throwable throwable) {
      RuntimeHealthIndicator.failedRequestCount++;
      log.error(new Exception(throwable), "Unknown exception- -!");

      throw throwable;
    }

    return result;
  }
}

2.2 Handler:执行记录的逻辑

2.2.1 抽象类:AbstractControllerHandler 

public abstract class AbstractControllerHandler {
  private static ILogger log = LoggerFactory.getLogger(AbstractControllerHandler.class);

  private static Function<ProceedingJoinPoint, AbstractControllerHandler> build;

  public static Function<ProceedingJoinPoint, AbstractControllerHandler> getBuild() {
    return build;
  }

  public static void registerBuildFunction(Function<ProceedingJoinPoint, AbstractControllerHandler> build) {
    Assert.isNotNull(build, "build");

    AbstractControllerHandler.build = build;
  }

  protected ProceedingJoinPoint proceedingJoinPoint;
  protected HttpServletRequest httpServletRequest;
  protected String methodName;
  protected String uri;
  protected String requestBody;
  protected String ip;
  protected Method method;
  protected boolean inDataMasking;
  protected boolean outDataMasking;


  public AbstractControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
    Assert.isNotNull(proceedingJoinPoint, "proceedingJoinPoint");

    this.proceedingJoinPoint = proceedingJoinPoint;
    Signature signature = this.proceedingJoinPoint.getSignature();
    this.httpServletRequest = this.getHttpServletRequest(this.proceedingJoinPoint.getArgs());
    this.methodName = signature.getName();
    this.uri = null == this.httpServletRequest ? null : this.httpServletRequest.getRequestURI();
    this.requestBody = this.formatParameters(this.proceedingJoinPoint.getArgs());
    this.ip = null == this.httpServletRequest ? "" : CommonHelper.getIp(this.httpServletRequest);
    this.inDataMasking = false;
    this.outDataMasking = false;
    if (signature instanceof MethodSignature) {
      MethodSignature methodSignature = (MethodSignature) signature;
      try {
        this.method = proceedingJoinPoint.getTarget().getClass().getMethod(this.methodName, methodSignature.getParameterTypes());
        if (null != this.method) {
          LogDataMasking dataMasking = this.method.getDeclaredAnnotation(LogDataMasking.class);
          if (null != dataMasking) {
            this.inDataMasking = dataMasking.in();
            this.outDataMasking = dataMasking.out();
          }
        }
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      }
    }
  }

  public abstract Object handle() throws Throwable;

  protected void logIn() {
    String requestBody = this.requestBody;
    if (this.inDataMasking) {
      requestBody = "Data Masking";
    }
    log.info(String.format("Start-[%s][%s][%s][body: %s]", this.ip, this.uri, this.methodName, requestBody));
  }

  protected void logOut(long elapsedMilliseconds, boolean success, String responseBody) {
    if (success) {
      if (this.outDataMasking) {
        responseBody = "Data Masking";
      }
      log.info(
          String.format(
              "Success(%s)-[%s][%s][%s][response body: %s]",
              elapsedMilliseconds,
              this.ip,
              this.uri,
              this.methodName,
              responseBody));
    } else {
      log.warn(
          String.format(
              "Failed(%s)-[%s][%s][%s][request body: %s][response body: %s]",
              elapsedMilliseconds,
              this.ip,
              this.uri,
              this.methodName,
              this.requestBody,
              responseBody));
    }
  }

  protected HttpServletRequest getHttpServletRequest(Object[] parameters) {
    try {
      if (null != parameters) {
        for (Object parameter : parameters) {
          if (parameter instanceof HttpServletRequest) {
            return (HttpServletRequest) parameter;
          }
        }
      }

      return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    } catch (Exception e) {
      log.error(e);

      return null;
    }
  }

  protected String formatParameters(Object[] parameters) {
    if (null == parameters) {
      return null;
    } else {
      StringBuilder stringBuilder = new StringBuilder();
      for (int i = 0; i < parameters.length; i++) {
        Object parameter = parameters[i];
        if (parameter instanceof HttpServletRequest || parameter instanceof HttpServletResponse) {
          continue;
        }

        stringBuilder.append(String.format("[%s]: %s.", i, JSON.toJSONString(parameter)));
      }

      return stringBuilder.toString();
    }
  }

2.2.2 实现类:

public class DefaultControllerHandler extends AbstractControllerHandler {
  private static ILogger log = LoggerFactory.getLogger(DefaultControllerHandler.class);
  private static int currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1;

  public DefaultControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
    super(proceedingJoinPoint);
  }

  @Override
  public Object handle() throws Throwable {
    long timestamp = System.currentTimeMillis();
    this.logIn();

    ResponseDto responseDto;
    boolean success = false;
    try {
      Object result = proceedingJoinPoint.proceed();
      if (result instanceof ResponseDto) {
        responseDto = (ResponseDto) result;
      } else {
        responseDto = ResponseDto.success(result);
      }
      success = true;
      RuntimeHealthIndicator.successRequestCount++;
    } catch (BusinessException e) {
//      RuntimeHealthIndicator.failedRequestCount++;
      if (this.isDebugLogLevel()) {
        log.error(e);
      }

      responseDto = new ResponseDto<>(e.getCode(), e.getMessage(), null);
    } catch (Exception e) {
      RuntimeHealthIndicator.failedRequestCount++;

      if (this.isDebugLogLevel()) {
        log.error(e);
      }

      responseDto = ResponseDto.failed(ExceptionDefinitions.ServerError, e.getMessage(), null);
    } finally {
      Calendar cale = Calendar.getInstance();
      if (currentMonth != (cale.get(Calendar.MONTH) + 1)) {
        String recodeKey = String.format("%d年%d月",
            cale.get(Calendar.YEAR), cale.get(Calendar.MONTH) + 1);
        String recodeValue = "successCount:" + RuntimeHealthIndicator.successRequestCount +
            " failedCount:" + RuntimeHealthIndicator.failedRequestCount;
        RuntimeHealthIndicator.historyRequestRecode.put(recodeKey, recodeValue);
        RuntimeHealthIndicator.successRequestCount = 0;
        RuntimeHealthIndicator.failedRequestCount = 0;
        currentMonth = cale.get(Calendar.MONTH);
      }
    }

    long duration = System.currentTimeMillis() - timestamp;
    RuntimeHealthIndicator.markRestApiInvoked(this.methodName, (int) duration);
    this.logOut(duration, success, JSON.toJSONString(responseDto));

    return responseDto;
  }

  public boolean isDebugLogLevel() {
    return log.isEnabled(LogLevel.DEBUG);
  }
}

2.3 Health接口

@Component
public class RuntimeHealthIndicator extends AbstractHealthIndicator {
  private static ILogger log = LoggerFactory.getLogger(ApplicationInstanceManager.class);
  private static Map<String, RestApiInvokeStatus> restApiInvokeStatuses = new HashMap<>();
  public static long failedRequestCount = 0;
  public static long successRequestCount = 0;

  public static Map<String, Object> historyRequestRecode;
  private Map<String, Object> details;

  public RuntimeHealthIndicator() {
    this.details = new HashMap<>();
    RuntimeHealthIndicator.historyRequestRecode = new HashMap<>();

    this.details.put("startTime", new Date(ManagementFactory.getRuntimeMXBean().getStartTime()));
    this.details.put("path", RuntimeHealthIndicator.class.getClassLoader().getResource("").getPath());
    this.details.put("osName", System.getProperty("os.name"));
    this.details.put("osVersion", System.getProperty("os.version"));
    this.details.put("javaVersion", System.getProperty("java.version"));
    try {
      this.details.put("ip", ZGHelper.getIpV4());
    } catch (SocketException e) {
      log.error(e, "Failed to get Ipv4.");
    }
  }

  @Override
  protected void doHealthCheck(Health.Builder builder) throws Exception {

    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    while (null != threadGroup.getParent()) {
      threadGroup = threadGroup.getParent();
    }
    this.details.put("threadCount", threadGroup.activeCount());
    OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
    this.details.put("cpuUsageRate", operatingSystemMXBean.getSystemCpuLoad());
    this.details.put(
        "memoryUsageRate",
        (float) (operatingSystemMXBean.getTotalPhysicalMemorySize() - operatingSystemMXBean.getFreePhysicalMemorySize()) / (float) operatingSystemMXBean.getTotalPhysicalMemorySize());
    this.details.put("failedRequestCount", RuntimeHealthIndicator.failedRequestCount);
    this.details.put("successRequestCount", RuntimeHealthIndicator.successRequestCount);
    this.details.put("restApiInvokeStatuses", RuntimeHealthIndicator.restApiInvokeStatuses);
    this.details.put("historyRequestRecode",RuntimeHealthIndicator.historyRequestRecode);

    for (Map.Entry<String, Object> detail : this.details.entrySet()) {
      builder.withDetail(detail.getKey(), detail.getValue());
    }
    builder.up();
  }

  public static void markRestApiInvoked(String name, int duration) {
    if (StringUtils.isBlank(name)) {
      return;
    }

    if (!RuntimeHealthIndicator.restApiInvokeStatuses.containsKey(name)) {
      RuntimeHealthIndicator.restApiInvokeStatuses.put(name, new RestApiInvokeStatus(name));
    }

    RestApiInvokeStatus restApiInvokeStatus = RuntimeHealthIndicator.restApiInvokeStatuses.get(name);
    restApiInvokeStatus.setDuration(duration);
  }
}

2.4 工具类

public class RestApiInvokeStatus {
  private String name;
  private Date startDate;
  private Date latestDate;
  private long times;
  private float averageDuration;
  private int minDuration;
  private int maxDuration;
  private int[] durations;

  public String getName() {
    return name;
  }

  public Date getStartDate() {
    return startDate;
  }

  public Date getLatestDate() {
    return latestDate;
  }

  public long getTimes() {
    return times;
  }

  public int getMinDuration() {
    return minDuration;
  }


  public int getMaxDuration() {
    return maxDuration;
  }

  public RestApiInvokeStatus(String name) {
    Assert.isNotBlank(name, "name");

    this.name = name;
    this.durations = new int[1000];
    this.minDuration = Integer.MAX_VALUE;
    this.maxDuration = Integer.MIN_VALUE;
    Date now = new Date();
    this.startDate = now;
    this.latestDate = now;

  }

  public void setDuration(int duration) {
    this.durations[(int) (this.times % this.durations.length)] = duration;
    this.maxDuration = this.maxDuration > duration ? this.maxDuration : duration;
    this.minDuration = this.minDuration < duration ? this.minDuration : duration;
    this.latestDate = new Date();
    this.times++;
  }

  public float getAverageDuration() {
    long length = this.times < this.durations.length ? this.times : this.durations.length;

    int count = 0;
    for (int i = 0; i < length; i++) {
      count += this.durations[i];
    }
    this.averageDuration = (float) count / (float) length;

    return this.averageDuration;
  }
}