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' 足以解释这一点):
- 请求点击网络包含(例如tomcat)
- Tomcat 将 HTTP 原始请求文本(有效负载)转换为 java class 表示 http 请求(HttpServletRequest 等)的 es
- 如果指定了请求,则请求通过 javax.servlet.Filter-s 对 "raw" 请求对象进行操作。
- Tomcat 找到处理请求的 servlet。在这种情况下,它的 Spring MVC 的 DispatcherServlet
- Tomcat 将执行传递给 DispatcherServlet。在这一点上 spring 出现了。
- DispatcherServlet 找到要调用的控制器
- DispatcherServlet "understands" 如何将数据从原始请求转换为控制器中指定方法的参数(应映射到什么 "path",如何转换 Body 等等)。例如,如果其预期为 JSON,则将使用 Jackson 进行实际转换。
- 调用控制器
现在,重新分级 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 示例
其他解决方案包括:
- Spring Boot Actuator 有一个记录最后 50 个请求的端点
- Tomcat(例如,如果您使用 tomcat)有一个特殊的 Valve 可以记录请求(访问日志样式)。例如参见 [=14=]
我已经使用 @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;
}
但是,我意识到如果提供的请求输入(即请求主体)无效,例如,如果number
是请求主体中的必填字段,则它必须是Integer
,但我输入了一个 String
作为它的值并发送到端点,Spring 将 return 一个 400 响应而不会触及这个方面。但是如果我输入了一些合法的输入,让请求实际通过端点,那么这个方面就会完成它的工作。那么有没有办法让这方面在我上面提到的情况下起作用呢?
PS:我曾尝试将 @ControllerAdvice
与 @ExceptionHandler
一起使用,但如果 @ControllerAdvice
被触发,它似乎也不会通过上述记录器方面。
首先,让我解释一下为什么您对方面和控制器建议的尝试失败了。
流程是这样的(大大简化但仍然希望 'deep' 足以解释这一点):
- 请求点击网络包含(例如tomcat)
- Tomcat 将 HTTP 原始请求文本(有效负载)转换为 java class 表示 http 请求(HttpServletRequest 等)的 es
- 如果指定了请求,则请求通过 javax.servlet.Filter-s 对 "raw" 请求对象进行操作。
- Tomcat 找到处理请求的 servlet。在这种情况下,它的 Spring MVC 的 DispatcherServlet
- Tomcat 将执行传递给 DispatcherServlet。在这一点上 spring 出现了。
- DispatcherServlet 找到要调用的控制器
- DispatcherServlet "understands" 如何将数据从原始请求转换为控制器中指定方法的参数(应映射到什么 "path",如何转换 Body 等等)。例如,如果其预期为 JSON,则将使用 Jackson 进行实际转换。
- 调用控制器
现在,重新分级 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 示例
其他解决方案包括:
- Spring Boot Actuator 有一个记录最后 50 个请求的端点
- Tomcat(例如,如果您使用 tomcat)有一个特殊的 Valve 可以记录请求(访问日志样式)。例如参见 [=14=]