Spring Controllers:添加一个名为 Elapsed-Time 的响应头参数

Jor*_*ñol 2 java rest spring spring-mvc

我想在每个 API REST 请求上添加一个 Elapsed-Time 响应标头参数,即使是那些以错误结束的请求。

例如:

===>
GET /api/customer/123 HTTP/1.1 
Accept: application/vnd.company.app.customer-v1+json 

<===
HTTP/1.1 200 OK 
Content-Type: application/vnd.company.app.customer-v1+json 
Elapsed-Time: 12 

{ id: 123, name: Jordi, age: 28 }
Run Code Online (Sandbox Code Playgroud)

作为 Elapsed-Time 参数计算为@RequestMapping 方法完成的瞬间与@RequestMapping 方法开始的瞬间之间的毫秒差。

我一直在看 Spring4 HandlerInterceptorAdapter。preHandle 和 postHandle 方法似乎非常适合这种情况(尽管执行链中的每个拦截器都有时间开销)。但是,不幸的是,更改 postHandle 方法中的响应标头不起作用,因为响应已经构建。

Spring 文档指出:

请注意,HandlerInterceptor 的 postHandle 方法并不总是非常适合与 @ResponseBody 和 ResponseEntity 方法一起使用。在这种情况下,HttpMessageConverter 在调用 postHandle 之前写入并提交响应,这使得无法更改响应,例如添加标头。相反,应用程序可以实现 ResponseBodyAdvice 并将其声明为 @ControllerAdvice bean 或直接在 RequestMappingHandlerAdapter 上配置它。

您知道处理这种情况的任何有效的优雅解决方案吗?

我不认为这种情况是在复制Spring - 在处理后修改每个请求的标头(在 postHandle 中),因为我需要捕获一个变量,其值为开始时间(当请愿到达应用程序时并且在 @RequestMapping 方法开始之前) ),然后在@RequestMapping 方法完成后使用此变量来计算经过的时间。

avi*_*bol 7

需要保留 Handle Interceptor,但不实现 postHandle 方法,只实现 preHandle 以便将 startTime 作为参数保存在请求中

public class ExecuterTimeInterceptor extends HandlerInterceptorAdapter {

 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    long startTime = System.currentTimeMillis();
    request.setAttribute("startTime", startTime);
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

当控制器完成并返回响应时,Controller Advice(实现 ResponseBodyAdvice 的类)将获取 Server Request 的 http servlet 请求部分,恢复 startTime 并获取经过的时间,如下所示:

@ControllerAdvice
public class GeneralControllerAdvice implements ResponseBodyAdvice<Object> {

 @Override
 public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
     return true;
 }

 @Override
 public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
     ServletServerHttpRequest servletServerRequest = (ServletServerHttpRequest) request;
     long startTime = (long) servletServerRequest.getServletRequest().getAttribute("startTime");
     long timeElapsed = System.currentTimeMillis() - startTime;
     response.getHeaders().add("Elapsed-Time", String.valueOf(timeElapsed));
     return body;
  }
}
Run Code Online (Sandbox Code Playgroud)

最后,您将拦截器添加到主应用程序中(每个资源都需要 /** 路径)

@SpringBootApplication
@ComponentScan(basePackages = "com.your.package")
@Configuration
public class Application extends WebMvcConfigurerAdapter {

 public static void main(String[] args) {
     SpringApplication.run(Application.class, args);
 }

 @Override
 public void addInterceptors(InterceptorRegistry registry) {
     registry.addInterceptor(new ExecuterTimeInterceptor()).addPathPatterns("/**");
  }

}
Run Code Online (Sandbox Code Playgroud)