【问题标题】:Spring MVC session attribute is lost after uploading a large file上传大文件后 Spring MVC 会话属性丢失
【发布时间】:2013-05-28 17:49:59
【问题描述】:

我的 Spring MVC 应用程序的一个功能是上传大文件(大约 500MB),这些文件应该稍后处理。

控制器是:

@RequestMapping("/folderManagement/")
@Controller("FolderFormController")
@SessionAttributes({ "tabTitle", "selectedFolder" })
public class FolderFormController {
    @RequestMapping(method = RequestMethod.GET, value = FOLDER_FORM)
    protected ModelAndView showFolderManager(@RequestParam(value = "selectedFolderId", required = false) Long selectedFolderId,
            @RequestParam(value = "selectedDocId", required = false) Long selectedDocId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Generates the page attributes
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(FOLDER_FORM_VIEW);

        return modelAndView;
    }

    @RequestMapping(value = UPLOAD_FILES, method = RequestMethod.POST)
    public ModelAndView postUploadFile(HttpServletRequest request, @ModelAttribute("uploadItem") UploadItem upitem, @ModelAttribute("selectedFolder") FolderForm root, BindingResult result,
            SessionStatus status, ModelAndView modelAndView) {
        // Process upitem
        Log.debug("INIT");
    }

}

UploadItem 是一个内部有 CommonsMultipartFile (org.springframework.web.multipart.commons) 的对象。

它通常可以正常工作。无论文件大小(甚至 800MB),也不管处理时间。但是,有时用户会遇到以下异常:

org.springframework.web.HttpSessionRequiredException: Expected session attribute 'selectedFolder'
    at org.springframework.web.method.annotation.ModelFactory.initModel(ModelFactory.java:103)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:614)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptorFilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:183)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.ajp.AjpAprProcessor.process(AjpAprProcessor.java:448)
    at org.apache.coyote.ajp.AjpAprProtocol$AjpConnectionHandler.process(AjpAprProtocol.java:403)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1703)
    at java.lang.Thread.run(Thread.java:662)

这个异常被这个方法捕获了:

@ExceptionHandler({ Exception.class})
public ModelAndView handleExceptionArray(Exception ex) {
    Log.error("Recogida excepcion " + ex.getClass().getSimpleName(), ex);
    String errorMessage = "Internal error";
    ModelAndView modelAndView = new ModelAndView();
    // modelAndView.setViewName("error/error");
    modelAndView.addObject("name", "");
    modelAndView.addObject("exception", errorMessage);

    modelAndView.setViewName("redirect:" + FolderFormController.getRedirectUrl());
    saveError(modelAndView, errorMessage);
    return modelAndView;
}

此外,日志“INIT”没有打印出来,所以它没有进入控制器方法。

发生了什么?而且,更重要的是,我怎样才能避免这种情况?有什么想法吗?

更新:原因

嗯,原因似乎是用户会话到期。 Tomcat 的过期时间为 30 分钟。如果上传需要的时间不止这些,那么它就不会到达我的控制器。有什么办法可以避免这种会话丢失?

【问题讨论】:

  • 由于此问题是间歇性的并且存在 HttpSessionRequiredException,因此怀疑会话到期(假设尚未调用 SessionStatus.setComplete())。避免这种情况的一种解决方案是在客户端保留 selectedFolder(使用隐藏字段)并在上传文件时发布这些字段。
  • selectedFolder 从具有此属性的表单发布到 postUploadFile。另外,上传文件时会话如何过期?通讯没有停止,是吗?

标签: java session spring-mvc file-upload exception-handling


【解决方案1】:

您已经发现这一定是会话超时的问题,很好。如果在开始上传后没有新请求到达特定会话,则认为“无活动”,这在您的情况下是不正确的。

有几种方法可以解决这个问题:

web.xml

增加web.xml中的超时时间,Tomcat中默认为30'(即如果web.xml中没有指定)。

<session-config>
  <session-timeout>45</session-timeout>
</session-config>

但是,这可能是不可取的,因为根据经验,超时应该“尽可能低”——不管这意味着什么。我们通常使用 5'。

更改会话

您可以将一般会话超时设置为 5',但在上传开始后使用 HttpSession#setMaxInactiveInterval 增加它:

指定客户端请求之间的时间,以秒为单位 servlet 容器将使该会话无效。消极时期 表示会话永远不会超时。

但是,如果您的控制器甚至没有被调用,则必须在 Spring 之前完成。最佳候选人是ServletFilter

AJAX

在上传提交后立即从客户端启动心跳请求。然后循环重复心跳。如果会话超时为 5',您可以使用 4' 的心跳间隔。

【讨论】:

    猜你喜欢
    • 2014-06-11
    • 2015-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 2017-12-18
    • 2015-06-08
    相关资源
    最近更新 更多