Spring 当请求输入无效时,围绕控制器的 AOP 不起作用

Spring AOP around controllers does not work when request input are invalid

我已经使用 @Around:

编写了一个 request/response 记录器
@Around(value = "execution(* com.xyz.example.controller.*.*(..))")
public Object logControllers(ProceedingJoinPoint joinPoint) throws Throwable {
    Object response = joinPoint.proceed();

    // Log request and response

    return response;
}

但是,我意识到如果提供的请求输入(即请求主体)无效,例如,如果number是请求主体中的必填字段,则它必须是Integer,但我输入了一个 String 作为它的值并发送到端点,Spring 将 return 一个 400 响应而不会触及这个方面。但是如果我输入了一些合法的输入,让请求实际通过端点,那么这个方面就会完成它的工作。那么有没有办法让这方面在我上面提到的情况下起作用呢?

PS:我曾尝试将 @ControllerAdvice@ExceptionHandler 一起使用,但如果 @ControllerAdvice 被触发,它似乎也不会通过上述记录器方面。

首先,让我解释一下为什么您对方面和控制器建议的尝试失败了。

流程是这样的(大大简化但仍然希望 'deep' 足以解释这一点):

  1. 请求点击网络包含(例如tomcat)
  2. Tomcat 将 HTTP 原始请求文本(有效负载)转换为 java class 表示 http 请求(HttpServletRequest 等)的 es
  3. 如果指定了请求,则请求通过 javax.servlet.Filter-s 对 "raw" 请求对象进行操作。
  4. Tomcat 找到处理请求的 servlet。在这种情况下,它的 Spring MVC 的 DispatcherServlet
  5. Tomcat 将执行传递给 DispatcherServlet。在这一点上 spring 出现了。
  6. DispatcherServlet 找到要调用的控制器
  7. DispatcherServlet "understands" 如何将数据从原始请求转换为控制器中指定方法的参数(应映射到什么 "path",如何转换 Body 等等)。例如,如果其预期为 JSON,则将使用 Jackson 进行实际转换。
  8. 调用控制器

现在,重新分级 AOP 部分: AOP 建议有效地包装您的控制器,提供来自真实控制器的代理 "indistinguishable"。因此可以在步骤“8”

期间调用此代理(对 Spring MVC 透明)

这是一个"happy path"。但是,如果查询错误会怎样?

  • 如果它不是 Http 请求,它将在第 2 步中失败并且显然不会到达第 8 步
  • 如果它是一个有效的 http 请求但未正确映射(如错误的 url 映射路径等)DispatcherServlet 将找不到相关的控制器来处理它,因此它将在步骤“6”期间失败“
  • 如果JSON有效负载错误,第7步将失败。

无论如何都不会到达第 8 步,这就是为什么您的建议没有被调用的原因

现在关于 @ControllerAdvice。它是 spring MVC 中的一个 "special" 异常处理机制,有助于正确映射控制器内部发生的异常(或控制器调用的 class,如服务、Dao 等)。由于流量甚至还没有到达控制器,因此它在这里几乎无关紧要。

现在就条款或决议而言:

基本上有两种抽象,您可以尝试以编程方式进行:

  • javax.servlet 的过滤器 以 Web 容器的方式完成
  • HandlerInterceptorAdapter 以 spring MVC 的方式

在这两种情况下,您都必须处理原始请求。 这是 spring mvc 方式的示例:

@Component
public class LoggingInterceptor 
  extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(
      HttpServletRequest request, 
      HttpServletResponse response, 
      Object handler) {
        return true; // called before the actual controller is invoked
    }

    @Override
    public void afterCompletion(
      HttpServletRequest request, 
      HttpServletResponse response, 
      Object handler, 
      Exception ex) {
        // called after the controller has processed the request,
        // so you might log the request here
    }
}

为了像这样正确地注册和映射拦截器,您可以使用WebMvcConfigurer。总而言之,参见 this 示例

其他解决方案包括:

  1. Spring Boot Actuator 有一个记录最后 50 个请求的端点
  2. Tomcat(例如,如果您使用 tomcat)有一个特殊的 Valve 可以记录请求(访问日志样式)。例如参见 [​​=14=]