我的Spring MVC应用程序中有以下内容:
@RestController
public class SomeController {
@GetMapping(value = "/csv", produces = { "text/csv", MediaType.APPLICATION_JSON_VALUE })
public Future someAsyncMethod() {
return CompletableFuture
.supplyAsync(() -> generateCsvSlowly()))
.thenApply(csv -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Disposition", "attachment; filename=" + "Filename_.csv");
httpHeaders.add("Cookie", "fileDownload=true; path=/");
return new HttpEntity<>(csv, httpHeaders);
});
}
}
}
所以它只是简单地生成csv,但速度很慢,我必须使这个调用异步。
我正在尝试以下列方式记录所有响应正文:
@Component
public class LoggingFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
private static final AtomicLong ID = new AtomicLong();
static final String SOME_FORMAT_STRING = ...;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
long id = ID.incrementAndGet();
HttpServletResponse responseToUse = response;
if (!(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
try {
filterChain.doFilter(request, responseToUse);
}
finally {
byte[] responseBodyBytes = ((ContentCachingResponseWrapper) responseToUse).getContentAsByteArray();
LOGGER.info(SOME_FORMAT_STRING, id, responseToUse.getStatus(), responseToUse.getContentType(),
new ServletServerHttpResponse(responseToUse).getHeaders(), new String(bodyBytes, UTF_8));
((ContentCachingResponseWrapper) responseToUse).copyBodyToResponse();
}
}
}
这是我的异常处理程序:
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ApplicationException.class)
@ResponseBody
public ResponseEntity<Status> handleException(ApplicationException exception) {
Status status = new Status();
...
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
...
return new ResponseEntity(status, headers, exception.getHttpCodeMvc());
}
@Override
protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleException(new ApplicationException(ex, ApplicationStatus.GENERIC_ERROR));
}
}
此处Status
是简单的POJO,ApplicationException
是自定义异常类。
当generateSlowlyCsv
抛出异常时,它会在handleException
中处理,但没有记录任何内容,也没有任何正文返回给客户端。其他非异步控制器方法记录错误(即使是同一个)就好了并返回响应体。
当生成csv(我在调试器中看到它)没有错误时,调用只是挂起而我无法找到它(它从可完成的未来返回)。如果没有LoggingFilter
,一切正常,但当然没有日志。
如何在发生异常时松开响应体并在生成异常时返回csv?非常感谢你!
P.S。从控制器方法返回Callable
和MvcAsyncTask
也无济于事
答案 0 :(得分:0)
我结束了以下事情:
package com.ololo.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import static java.lang.System.lineSeparator;
import static java.nio.charset.StandardCharsets.UTF_8;
@ControllerAdvice
public class LoggingAdvice implements ResponseBodyAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class);
private static final AtomicLong ID = new AtomicLong();
private static final String SOME_RESPONSE_MESSAGE_FORMAT;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
long id = ID.incrementAndGet();
ServletServerHttpResponse responseToUse = (ServletServerHttpResponse) response;
HttpMessageConverter httpMessageConverter;
LoggingHttpOutboundMessageWrapper httpOutputMessage = new LoggingHttpOutboundMessageWrapper();
try {
httpMessageConverter = (HttpMessageConverter) selectedConverterType.newInstance();
httpMessageConverter.write(body, selectedContentType, httpOutputMessage);
LOGGER.info(SOME_RESPONSE_MESSAGE_FORMAT, id, responseToUse.getServletResponse().getStatus(), responseToUse.getServletResponse().getContentType(),
responseToUse.getHeaders(), httpOutputMessage.getResponseBodyInString());
} catch (InstantiationException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
return body;
}
private static final class LoggingHttpOutboundMessageWrapper implements HttpOutputMessage {
private HttpHeaders httpHeaders = new HttpHeaders();
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@Override
public OutputStream getBody() throws IOException {
return byteArrayOutputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
public String getResponseBodyInString() {
return new String(byteArrayOutputStream.toByteArray());
}
}
}
是的,我知道,它真可怕,但至少它适用于所有@ResponseBody
控制器方法。我不认为(至少现在)我需要别的东西。我尝试编写异步过滤器,但我无法将AsyncListener
添加到AsyncContext
(有关详细信息,请参阅the following question)。希望这会对你有所帮助。
答案 1 :(得分:0)
我有一个类似的问题。
所有Spring Framework Servlet过滤器实现已根据需要进行了修改,以在异步请求处理中工作。至于任何其他过滤器,有些将起作用-通常是那些进行预处理的过滤器,而另一些则需要修改-通常是那些在请求结束时进行后处理的过滤器。此类过滤器将需要识别何时退出了初始Servlet容器线程,从而让另一个线程继续处理,以及何时将它们作为异步调度的一部分调用以完成处理。
org.springframework.web.filter.ShallowEtagHeaderFilter
是一个很好的例子。
通过在需要的地方ShallowEtagHeaderFilter
处从doFilter
复制方法来完成这项工作:
if (!isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper((HttpServletResponse) response);
}
然后在末尾:
if (!isAsyncStarted(request)) {
updateResponse(response);
}