对于返回CompletionStage的请求,Spring Boot会对过滤器运行两次

Roh*_*bhu 7 java spring spring-mvc

我遇到了一个问题,当方法返回时,我的过滤器运行了两次CompletionStage.从RequestMapping(此处)的文档中,它是受支持的返回值.

CompletionStage(由CompletableFuture实现),应用程序使用它在自己选择的单独线程中生成返回值,作为返回Callable的替代方法.

由于项目非常复杂,并且有很多并发代码,因此我创建了一个新的简单的spring-boot项目.这是(唯一的)控制器:

@Controller
public class BaseController {
    @RequestMapping("/hello")
    @ResponseBody
    public CompletionStage<String> world() {
        return CompletableFuture.supplyAsync(() -> "Hello World");
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一个过滤器:

@WebFilter
@Component
public class GenericLoggingFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        System.out.println(httpServletRequest.getMethod() + " " +
                           httpServletRequest.getRequestURI());

        chain.doFilter(request, response);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我打电话时curl http://localhost:8080/hello,它会GET /hello在控制台上打印两次.当我更改控制器方法以返回String:

@RequestMapping("/hello")
@ResponseBody
public String world() {
    return "Hello World";
}
Run Code Online (Sandbox Code Playgroud)

它只打印一次.即使我将其更改为a Callable,也没有真正的并发意义(当然,spring本身可能会将此作为Async请求处理),因此会出现此行为.

因此,如果spring再次运行整个Web堆栈以获得请求上下文,那么即使这样也没有意义,因为以下内容:

@RequestMapping("/hello")
@ResponseBody
public CompletionStage<String> world() {
    return CompletableFuture.supplyAsync(() -> {
        System.out.println(RequestContextHolder.currentRequestAttributes());
        return "Hello World";
    });
}
Run Code Online (Sandbox Code Playgroud)

抛出异常: IllegalStateException: No thread-bound request found...

令人惊讶的是,以下工作:

@RequestMapping("/hello")
@ResponseBody
public Callable<String> world() {
    return () -> {
        System.out.println(RequestContextHolder.currentRequestAttributes());
        return "Hello World";
    };
}
Run Code Online (Sandbox Code Playgroud)

所以,我不确定相当多的事情.

  1. 看来,CallableCompletionStage在哪个线程的它是在执行上下文被区别对待.
  2. 如果是这种情况,为什么我的过滤器在每种情况下运行两次?如果过滤器的工作是设置某个特定于请求的上下文,那么CompletionStage如果无法访问它,则再次运行它是没有意义的.
  3. 为什么过滤器会以哪种方式运行两次?

Jan*_*tel 5

请替换GenericFilterBeanOncePerRequestFilter