控制器中的Spring异步请求处理问题 - 获取java.lang.IllegalStateException:响应提交后无法转发

时间:2015-01-13 21:55:04

标签: spring spring-mvc asynchronous

非常感谢任何帮助解决这个问题!

应该提到 - 我没有使用jsp。我使用js和jquery,这是一个REST API调用,但此时我还没有使用任何正式的Spring REST支持。只是直接写回复。所以也许还有一些特定的东西。

我试图做一个简单的测试"在将其集成到我的代码库之前的功能。我已阅读MVC文档并遵循此示例:https://github.com/spring-projects/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/DeferredResultController.java

我的每个Filter和我的一个Servlet定义中的web.xml文件都是如此。

我的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默认使用ThreadPoolTask​​Executor实现AsyncTaskExecutor所以我想我应该全部设置在那里:http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html

我有一个非常简单的控制器方法和计划任务来返回和设置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调用。

1 个答案:

答案 0 :(得分:0)

简短的回答:代码很好,我只需要使用spring-servlet定义(以及async-support stuff)将TaskExecutor规范移动到相同的配置文件中。

当DeferredResult遇到超时时,错误似乎是默认值。

我的第一个故障排除是增加超时 - 这增加了我在收到错误之前等待的时间。这似乎表明请求正确进入异步模式并且返回了DeferredResult(并且没有生成错误),但是在超时设置之前没有调用setResult。

所以,我在@Scheduled方法中加入了一些打印语句,果然,没有发生任何事情。在上面的原始问题中有两个XML部分。第一个是在我的spring.xml配置文件中,第二个是在一个单独的applicationConfig.xml文件中。我把它们都放在spring文件中然后就可以了。

我认为最终错误是基于spring.xml文件中组件扫描的范围。这一次又烧毁了我。这变得深奥但相当简单 - 我们使用spring-security和PasswordEncoder来加密和散列密码。由于范围上的原因,我不能在具有组件扫描的文件中指定PasswordEncoder配置,该组件扫描会触及它所使用的DAO(违反直觉)。因此,我们总共有3个XML配置文件,2个包含组件扫描和排除过滤器,以确保它们不重叠。偶尔我会将一些配置放在错误的文件中,并且组件扫描中的类无法访问它。如果我能弄清楚如何只有一个组件扫描它将解决所有这一切,但偶尔会出现这样的事情。