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

狂风中的少年 提交于 2020-04-30 10:28:32

问题


I have written a request/response logger using @Around:

@Around(value = "execution(* com.xyz.example.controller.*.*(..))")
public Object logControllers(ProceedingJoinPoint joinPoint) throws Throwable {
    Object response = joinPoint.proceed();

    // Log request and response

    return response;
}

However, I realized if the request input (ie. request body) provided are invalid, for example, if number is a required field in the request body, and it must be an Integer, but I entered a String as its value and send to the endpoint, Spring will return a 400 response without hitting this aspect. But if I entered some legit input, let the request actually goes through the endpoint, then this aspect will do its job. So is there any way to let this aspect works under the situation I mentioned above?

PS: I have tried using @ControllerAdvice with @ExceptionHandler, but looks like it will also not go through the above logger aspect if @ControllerAdvice is triggered.


回答1:


First of all, let me explain why your attempts with aspect and controller advice have failed.

The flow goes like this (greatly simplified but still hopefully 'deep' enough to explain the point):

  1. The Request hits the web contains (tomcat for instance)
  2. Tomcat converts the HTTP raw request text (payload) into java classes that represent the http request (HttpServletRequest etc)
  3. If its specified the request passes through javax.servlet.Filter-s which operate on "raw" request objects.
  4. Tomcat finds the servlet to handle the request. In this case its Spring MVC's DispatcherServlet
  5. Tomcat passes the execution to DispatcherServlet. At this point spring comes into picture.
  6. DispatcherServlet finds the controller to be called
  7. DispatcherServlet "understands" how to convert the data from the raw request to the parameters of the method specified in controller (what should be mapped to "path", how to convert Body and so forth). For example if its expected to be JSON, Jackson will be used for actual conversion.
  8. The controller is invoked

Now, regrading the AOP part: AOP advice wraps your controller effectively providing a proxy "indistinguishable" from the real controller. So this proxy could be invoked (transparently to Spring MVC) during the step "8"

This is a "happy path". However, what happens if the query is wrong?

  • If its not an Http Request it will fail during the step 2 and clearly won't reach step 8
  • If its a valid http request but is not mapped correctly (like the wrong url mapping path, etc) DispatcherServlet won't find a relevant controller to handle it, so it will fail during step "6"
  • If the JSON payload is wrong, step 7 will fail.

In any case it won't reach step 8, and thats the reason of why your advice is not invoked

Now regarding the @ControllerAdvice. Its a "special" exception handling mechanism in spring MVC that helps to properly map exceptions that happen inside the controller (or the class that controller calls to, like service, Dao, etc.). Since the flow haven't even reached the controller, its pretty much irrelevant here.

Now in terms or resolution:

There are basically two abstractions that you can try to do it programmatically:

  • javax.servlet's Filter to do it in a web container's way
  • HandlerInterceptorAdapter to do it in a spring MVC's way

In both cases you'll have to deal with the raw request. Here is an example of the spring mvc way:

@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
    }
}

In order to register and map the interceptor like this correctly you can use WebMvcConfigurer. All-in-all see this example

Other solutions include:

  1. Spring Boot Actuator has an endpoint that logs 50 last requests
  2. Tomcat (if you use tomcat for example) has a special Valve that can log request (access log style). See this thread for instance


来源:https://stackoverflow.com/questions/59817236/spring-aop-around-controllers-does-not-work-when-request-input-are-invalid

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!