【问题标题】:How to get method execution flow in spring mvc interceptor如何在spring mvc拦截器中获取方法执行流程
【发布时间】:2016-10-20 11:59:52
【问题描述】:

我想在 spring mvc 项目中获得完整的执行流程以及它们的执行时间。

public class MetricsInterceptor extends HandlerInterceptorAdapter {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (handler instanceof HandlerMethod) {
        HandlerMethod hm = (HandlerMethod) handler;
        Method method = hm.getMethod();
        if (method.getDeclaringClass().isAnnotationPresent(Controller.class)) {
            if (method.isAnnotationPresent(Metrics.class)) {
               // System.out.println(method.getAnnotation(Metrics.class).value());
                System.out.println(method.getName());
            }
        }
    }
    return super.preHandle(request, response, handler);
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    super.afterCompletion(request, response, handler, ex);
}

}

我正在获取带有 @Metrics 注释的控制器类和方法来记录少数方法的指标。我想要的是获得整个方法执行流程(Controller-->Service-->DAO)以及每个方法所花费的时间。无论如何可以在 postHandle() 或 afterCompletion() 中获取该信息。请提出建议。

【问题讨论】:

  • 一个快速的技巧是使用 new Exception().getStracktrace() 来获取整个流程,但它不会给你花费的时间

标签: java spring spring-mvc spring-aop


【解决方案1】:

使用 Spring AOP Around 建议可以实现这一点。

写一个pointcut会拦截

  • 包及其子包中公共方法的所有执行
  • 属于包及其子包
    • 控制器层
    • 服务层(可选择仅限于特定服务)
    • DAO 层

然后写下Around Advice如下

@Component
@Aspect
public class TraceAdvice {

    @Around("execution(* com.test..*(..))  &&" + " (within(com.test.controller..*) || "
        + "(within(com.test.service..*) && this(com.test.service.TestService)) || " + "within(com.test.dao..*))")
    public Object traceCall(ProceedingJoinPoint pjp) throws Throwable {
        /* This will hold our execution details in reverse order 
         * i.e. last method invoked would appear first.
         * Key = Full qualified name of method
         * Value = Execution time in ms
         */
        Map<String, Long> trace = null;
        Signature sig = pjp.getSignature();
        // Get hold of current request
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // check if we are in controller (I used RestController modify it if Controller is required instead) 
        boolean isController = sig.getDeclaringType().isAnnotationPresent(RestController.class);
        if (isController) {
            // set the request attributte if we are in controller
            trace = new LinkedHashMap<>();
            req.setAttribute("trace", trace);
        } else {
            // if its not controller then read from request atributte
            trace = (Map<String, Long>) req.getAttribute("trace");
        }
        Object result = null;
        StopWatch watch = new StopWatch();
        try {
            // start the timer and invoke the advised method
            watch.start();
            result = pjp.proceed();
        } finally {
            // stop the timer 
            watch.stop();
            String methodName = sig.getDeclaringTypeName() + "." + sig.getName();
            // make entry for the method name with time taken
            trace.put(methodName, watch.getTotalTimeMillis());
            if (isController) {
                // since controller method is exit point print the execution trace
                trace.entrySet().forEach(entry -> {
                    System.out.println("Method " + entry.getKey() + " took " + String.valueOf(entry.getValue()));
                });
            }
        }
        return result;
    }
}

在必要的配置之后,示例输出应该如下所示

方法 com.test.dao.TestDAO.getTest 花了 350
方法 com.test.service.TestService.getTest 采用 1954
方法 com.test.controller.TestController.getTest 取了 3751

另外,三个切入点和各自的Around 建议可以编写每个拦截特定包,以取消检查控制器的if-else 部分

我已经测试了设置

  • Spring 4.3.2 Release
  • AspectJ 1.7.4
  • JDK 1.8
  • Tomcat 8.0

如果有任何问题,请在 cmets 中告知。

【讨论】:

    猜你喜欢
    • 2012-08-28
    • 2012-09-08
    • 2014-05-19
    • 1970-01-01
    • 2015-01-10
    • 1970-01-01
    • 1970-01-01
    • 2014-06-11
    • 2013-06-21
    相关资源
    最近更新 更多