需要帮助解决使用 Tomcat 在 Spring 应用程序中启用异步支持的问题

Posted

技术标签:

【中文标题】需要帮助解决使用 Tomcat 在 Spring 应用程序中启用异步支持的问题【英文标题】:Need help troubleshooting async support enablement in Spring application with Tomcat 【发布时间】:2015-10-05 23:05:51 【问题描述】:

有一个使用 Tomcat 7 的工作 Spring REST 端点,使用 JDK 1.7 运行。使用 gradle 构建,其中 build.gradle 依赖于 Spring 库 v4.1.6 和 javax.servlet v3.0.1 等等。向@Controller 类添加了一个具有简单实现的新方法,该方法返回值DeferredResult<String>。当我向端点发送GET 请求并调用新方法时,大约在它返回一个值时,我看到服务器日志中记录了以下异常:(有关异常堆栈,请参见下文)

web.xml 文件(内置到我的.war 中)中有3-4 个过滤器(包括springSecurityFilterChain)和大约8 个过滤器(其中一个用于org.springframework.web.servlet.DispatcherServlet 并带有url 模式/ rest/* - 匹配失败的 GET 请求的 URL - HTTP 错误代码 400)。我尝试将 <async-supported>true</async-supported> 添加到 servlet 和过滤器定义中,但这似乎没有明显的效果。

任何有关如何解决此问题的提示将不胜感激!

(可能值得注意的是,我在单独的工作区中的简单 hello-world Spring REST 端点,当部署到相同版本的 Tomcat 后,在我将.xml...)

java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml.
at org.springframework.util.Assert.state(Assert.java:385) ~[spring-core-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.startAsync(StandardServletAsyncWebRequest.java:103) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.startAsyncProcessing(WebAsyncManager.java:422) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.startDeferredResultProcessing(WebAsyncManager.java:402) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler.handleReturnValue(DeferredResultMethodReturnValueHandler.java:49) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966) [spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) [spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) [servlet-api.jar:na]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842) [spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.39]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:168) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:158) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:116) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:152) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176) [spring-security-web-4.0.0.RELEASE.jar:na]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) [spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) [spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.39]
at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:399) [urlrewritefilter-4.0.4.jar:4.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.39]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.39]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.39]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [catalina.jar:7.0.39]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.39]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.39]
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:947) [catalina.jar:7.0.39]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.39]
at ch.qos.logback.access.tomcat.LogbackValve.invoke(LogbackValve.java:178) [logback-access-1.0.13.jar:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.39]
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1009) [tomcat-coyote.jar:7.0.39]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) [tomcat-coyote.jar:7.0.39]
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) [tomcat-coyote.jar:7.0.39]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]

web.xml 目前看起来像这样(我已经暂时注释掉了 springSecurity 过滤器,试图使其尽可能简单):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<display-name>XXX</display-name>

<resource-ref>
    <res-ref-name>jdbc/server</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
</resource-ref>
<resource-ref>
    <res-ref-name>jdbc/serverXA</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
</resource-ref>
<resource-ref>
    <res-ref-name>jms/serverXA</res-ref-name>
    <res-type>javax.jms.ConnectionFactory</res-type>
</resource-ref>

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/config/web-application-config.xml
    </param-value>
</context-param>

<servlet>
    <servlet-name>Content Servlet</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
      <param-name>fork</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>xpoweredBy</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>Content Servlet</servlet-name>
    <url-pattern>/content/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>Resources Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.ResourceServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>Resources Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>WebMVC Logos</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/webmvc-logos.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>WebMVC Logos</servlet-name>
    <url-pattern>/logos/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>WebMVC Downloads</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/webmvc-downloads.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>WebMVC Downloads</servlet-name>
    <url-pattern>/downloads/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>WebMVC WebApp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/webmvc-servlet-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>WebMVC WebApp</servlet-name>
    <url-pattern>/webapp/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>Remoting Dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/remoting-servlet-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>Remoting Dispatcher</servlet-name>
    <url-pattern>/remoting/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>WebMVC REST</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/webmvc-rest.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>WebMVC REST</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

<session-config>
    <session-timeout>10</session-timeout>
</session-config>

<servlet>
    <servlet-name>repository</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/repository-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>repository</servlet-name>
    <url-pattern>/repository/*</url-pattern>
</servlet-mapping>

</web-app>

【问题讨论】:

我也有同样的问题,据我所知tomcat对异步没有很好的支持。我希望你已经使用了异步所需的 NIOConnector。 按照您的建议,我尝试为 Tomcat 编辑 server.xml 并将 8080 端口连接器的协议更改为 NIO 协议,但这似乎没有什么不同。此外,正如我所提到的,对于我来说,为 servlet 设置了支持异步标志的更简单的“hello-world”风格的 Spring REST 项目似乎在同一版本的 Tomcat 中运行良好...... 我的意思是 tomcat 的异步需要 NIO 连接器,这不是问题。无论如何,编辑您的帖子并将您的 web.xml 放在那里。 好的,这个错误意味着你需要添加支持异步的所有过滤器,但我只能看到一个。如果您使用任何 IDE,请尝试在每个子部分中添加 asyn-supported 为 true,如果不允许,IDE 将抱怨,将其删除。如果 IDE 没有抱怨,那就让它留下吧。然后重启服务器,让我知道。 正如我在原始帖子中提到的,我已经尝试将它添加到所有过滤器和 servlet 定义中......我担心似乎没有什么不同。 【参考方案1】:

ch.qos.logback.access.tomcat.LogbackValve默认配置为同步处理。

所以应该配置启用异步支持。

这是一个例子:

@Bean
public EmbeddedServletContainerFactory servletContainer() 

  TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();

  LogbackValve logbackValve = new LogbackValve();
  logbackValve.setAsyncSupported(true);
  logbackValve.setFilename(logbackAccessFile);

  tomcat.addContextValves(logbackValve);
  return tomcat;

【讨论】:

以上是关于需要帮助解决使用 Tomcat 在 Spring 应用程序中启用异步支持的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在Spring Boot属性中设置Tomcat的SlowQueryReport拦截器的阈值

使用 Grafana 监控来自 tomcat 的 http 错误

在 tomcat 中运行的 Spring Boot,所需的请求部分“文件”不存在

Spring boot - 没有嵌入式 tomcat 的 Rest Call 客户端

关于spring中的配置怎么解决

Tomcat 中的 LTPA 令牌(Spring 安全性)