控制器中 Spring 异步请求处理出现问题 - 获取 java.lang.IllegalStateException:提交响应后无法转发
Trouble with Spring async request processing in Controller - getting java.lang.IllegalStateException: Cannot forward after response has been committed
非常感谢任何帮助解决这个问题的人!
应该提到 - 我没有使用 jsp。我正在使用 js 和 jquery,这是一个 REST API 调用,但此时我没有使用任何正式的 Spring REST 支持。直接写回复就好了。所以也许有一些特定的东西。
在将其集成到我的代码库之前,我正在尝试对该功能进行 "simple test"。我已阅读 MVC 文档并遵循此示例:https://github.com/spring-projects/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/DeferredResultController.java
我的 web.xml 文件中的每个过滤器和一个 Servlet 定义中都有 true。
我的 servlet 上下文文件有这个:
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</mvc:message-converters>
<mvc:async-support default-timeout="3000" />
</mvc:annotation-driven>
我的任务执行器设置如下:
<task:annotation-driven executor="taskExecutor"
scheduler="taskScheduler" />
<task:executor id="taskExecutor" pool-size="1-25"
queue-capacity="100" rejection-policy="CALLER_RUNS" />
<task:scheduler id="taskScheduler" pool-size="10" />
根据文档,Spring 默认是使用实现 AsyncTaskExecutor 的 ThreadPoolTaskExecutor 所以我认为我应该全部设置在那里:http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html
我有一个非常简单的控制器方法和计划任务 return 并在 DeferredResult 上设置状态:
@RequestMapping(value = "/collectMetaData", method = RequestMethod.POST)
public DeferredResult<ResponseEntity<String>> collectMetaDataDeferredResult(
@ModelAttribute @Valid CollectMetaDataForm collectMetaDataForm,
BindingResult result, HttpSession session) {
DeferredResult<ResponseEntity<String>> response = new DeferredResult<ResponseEntity<String>>();
this.responseBodyQueue.add(response);
return response;
}
@Scheduled(fixedRate = 2000)
public void processQueues() {
for (DeferredResult<ResponseEntity<String>> result : this.responseBodyQueue) {
result.setResult(new ResponseEntity<String>("cancelled",
HttpStatus.OK));
this.responseBodyQueue.remove(result);
}
}
应用程序启动并且似乎工作正常,但是当我调用此控制器方法时出现此错误:
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:349)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:467)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:338)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:428)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:436)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:295)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:599)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
此时我什至不确定异步请求处理是否正常工作。这似乎是因为 asyncDispatch 调用。
简短回答:代码很好,我只需要将 TaskExecutor 规范移动到与 spring-servlet 定义(和异步支持的东西)相同的配置文件中。
该错误似乎是 DeferredResult 超时时的默认错误。
我的第一个故障排除是增加超时 - 这增加了我在收到错误之前等待的时间。这似乎表明请求正在正确进入异步模式并且正在返回 DeferredResult(并且没有产生错误),但是在超时设置之前没有调用 setResult。
因此,我将一些打印语句放入该 @Scheduled 方法中,果然,其中没有发生任何事情。上面的原始问题中有两个 XML 部分。第一个在我的 spring.xml 配置文件中,第二个在单独的 applicationConfig.xml 文件中。我将它们都放在 spring 文件中并且它起作用了。
我相信最终错误是基于我的 spring.xml 文件中组件扫描的范围。这让我一次又一次地燃烧。这变得深奥但相当简单 - 我们使用 spring-security 和 PasswordEncoder 来加盐和散列密码。出于我无法理解的范围原因,无法在组件扫描命中使用它的 DAO 的文件中指定 PasswordEncoder 配置(违反直觉)。因此,我们总共有 3 XML 个配置文件,其中 2 个带有组件扫描和排除过滤器以确保它们不重叠。有时我将一些配置放在错误的文件中,并且组件扫描中需要它的 类 无法访问它。如果我能弄清楚如何只扫描一个组件,它就会解决所有这些问题,但偶尔会出现这样的事情。
非常感谢任何帮助解决这个问题的人!
应该提到 - 我没有使用 jsp。我正在使用 js 和 jquery,这是一个 REST API 调用,但此时我没有使用任何正式的 Spring REST 支持。直接写回复就好了。所以也许有一些特定的东西。
在将其集成到我的代码库之前,我正在尝试对该功能进行 "simple test"。我已阅读 MVC 文档并遵循此示例:https://github.com/spring-projects/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/DeferredResultController.java
我的 web.xml 文件中的每个过滤器和一个 Servlet 定义中都有 true。
我的 servlet 上下文文件有这个:
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</mvc:message-converters>
<mvc:async-support default-timeout="3000" />
</mvc:annotation-driven>
我的任务执行器设置如下:
<task:annotation-driven executor="taskExecutor"
scheduler="taskScheduler" />
<task:executor id="taskExecutor" pool-size="1-25"
queue-capacity="100" rejection-policy="CALLER_RUNS" />
<task:scheduler id="taskScheduler" pool-size="10" />
根据文档,Spring 默认是使用实现 AsyncTaskExecutor 的 ThreadPoolTaskExecutor 所以我认为我应该全部设置在那里:http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html
我有一个非常简单的控制器方法和计划任务 return 并在 DeferredResult 上设置状态:
@RequestMapping(value = "/collectMetaData", method = RequestMethod.POST)
public DeferredResult<ResponseEntity<String>> collectMetaDataDeferredResult(
@ModelAttribute @Valid CollectMetaDataForm collectMetaDataForm,
BindingResult result, HttpSession session) {
DeferredResult<ResponseEntity<String>> response = new DeferredResult<ResponseEntity<String>>();
this.responseBodyQueue.add(response);
return response;
}
@Scheduled(fixedRate = 2000)
public void processQueues() {
for (DeferredResult<ResponseEntity<String>> result : this.responseBodyQueue) {
result.setResult(new ResponseEntity<String>("cancelled",
HttpStatus.OK));
this.responseBodyQueue.remove(result);
}
}
应用程序启动并且似乎工作正常,但是当我调用此控制器方法时出现此错误:
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:349)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:467)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:338)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:428)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:436)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:295)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:599)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
此时我什至不确定异步请求处理是否正常工作。这似乎是因为 asyncDispatch 调用。
简短回答:代码很好,我只需要将 TaskExecutor 规范移动到与 spring-servlet 定义(和异步支持的东西)相同的配置文件中。
该错误似乎是 DeferredResult 超时时的默认错误。
我的第一个故障排除是增加超时 - 这增加了我在收到错误之前等待的时间。这似乎表明请求正在正确进入异步模式并且正在返回 DeferredResult(并且没有产生错误),但是在超时设置之前没有调用 setResult。
因此,我将一些打印语句放入该 @Scheduled 方法中,果然,其中没有发生任何事情。上面的原始问题中有两个 XML 部分。第一个在我的 spring.xml 配置文件中,第二个在单独的 applicationConfig.xml 文件中。我将它们都放在 spring 文件中并且它起作用了。
我相信最终错误是基于我的 spring.xml 文件中组件扫描的范围。这让我一次又一次地燃烧。这变得深奥但相当简单 - 我们使用 spring-security 和 PasswordEncoder 来加盐和散列密码。出于我无法理解的范围原因,无法在组件扫描命中使用它的 DAO 的文件中指定 PasswordEncoder 配置(违反直觉)。因此,我们总共有 3 XML 个配置文件,其中 2 个带有组件扫描和排除过滤器以确保它们不重叠。有时我将一些配置放在错误的文件中,并且组件扫描中需要它的 类 无法访问它。如果我能弄清楚如何只扫描一个组件,它就会解决所有这些问题,但偶尔会出现这样的事情。