Spring文件上传组件

上传出现FileUploadException:Stream closed

简述

项目中使用CommonsMultipartResolver作为文件上传解析器,拦截器中进行权限校验以及某些参数校验。在前端调用上传接口的过程中,由于前端参数不正确,拦截器直接返回了错误信息,但却意外抛出了FileUploadException: Stream closed异常。

场景

下面是拦截器直接向前端写回错误信息的代码,content是错误信息的json串,正常情况下这种IO流操作写法是完全没有问题的。

// response返回错误信息并关闭流
public static void returnMessage(HttpServletResponse response, String content) {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json");
		PrintWriter pWriter = null;
		try {
			pWriter = response.getWriter();
			pWriter.write(content);
		} catch (IOException e) {
			logger.error("returnMessage", e);
		} finally {
			if (pWriter != null) {
				pWriter.flush();
				pWriter.close();
			}
		}
	}

原因分析

分析异常信息发现,异常发生在CommonsMultipartResolvercleanupMultipart 方法,代码片段如下:

public void cleanupMultipart(MultipartHttpServletRequest request) {
		try {
			cleanupFileItems(request.getMultiFileMap());
		}
		catch (Throwable ex) {
			logger.warn("Failed to perform multipart cleanup for servlet request", ex);
		}
	}

当调用request.getMultiFileMap() 时,执行的是AbstractMultipartHttpServletRequest 类的getMultipartFiles() 。这个方法是懒加载方法,只有需要MultipartFile Map 时才会调用,由于请求被拦截器拦截,即清理时第一次使用,这里会初始化;紧接着去调用parseRequest() 解析请求,在使用输入流readBodyData() 的时候,发现流已经关闭了,抛出异常。

解决方法

粗略探究下发现,虽然关闭的是Response的输出流,但是相应的Request的输入流同时被关闭了。从debug和Tomcat的API可知,Request对象被Tomcat 使用Facade模式进行包装,同时Request对象中包含相对应的Response引用。事实证明关闭Response流会同样关闭Request流,原因有待结合Tomcat原理深入探究。
下面为改造后的代码(或者去掉finally代码块)。

public void returnMessage(HttpServletRequest request, HttpServletResponse response, String content) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter pWriter = null;
        try {
            pWriter = response.getWriter();
            pWriter.write(content);
        } catch (IOException e) {
            log.error("returnMessage", e);
        } finally {
            if (!ServletFileUpload.isMultipartContent(request)) {
                if (pWriter != null) {
                    pWriter.flush();
                    pWriter.close();
                }
            }
        }
    }

建议:在Spring框架中不要手动关闭流,交给系统自己关闭。

扩展

1、SpringMVC的上传解析器分为两种,分别为CommonsMultipartResolverStandardServletMultipartResolver
2、StandardServletMultipartResolver基于servlet 3.0,是MultipartResolver的标准实现,也是SpringBoot的默认上传组件。对于Web项目,需要在web.xml中对需要影响的servlet配置multipart-config ;对于SpringBoot项目,可以直接在application配置文件中配置属性,或者自定义MultipartConfigElement;对于自定义的servlet,也可以在servlet class上加入MultipartConfig注解。
3、 CommonsMultipartResolver 是web项目中常用的上传组件,需要在容器中定义。

下面以SpringBoot为例说明配置方式。

StandardServletMultipartResolver两种配置方式(二选一):
1、直接在application.yml或者application.properties中配置属性即可,自动配置

spring.http.multipart.maxFileSize=10MB
spring.http.multipart.maxRequestSize=50MB

2、定义MultipartConfigElement

    @Bean
    public MultipartConfigElement getMultipartElement() {
        //生成MultipartConfigElement的工厂
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //单个文件最大大小,可以使用这种形式,也可以使用字节数
        factory.setMaxFileSize("50MB");
        //总文件最大大小
        factory.setMaxRequestSize("100MB");
        MultipartConfigElement element = factory.createMultipartConfig();
        return element;
    }

CommonsMultipartResolver配置方式

     @Bean
     public CommonsMultipartResolver getMultipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(439426520l);
        resolver.setResolveLazily(true);//懒加载,需要获取文件信息时才会去解析请求数据
        resolver.setDefaultEncoding("UTF-8");
        return resolver;
    }

其XML配置方式为:

     <bean id="multipartResolver"	class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="resolveLazily" value="true" />
		<!-- 设置上传文件的最大尺寸为10MB -->
		<property name="maxUploadSize" value="10485760" />
		<property name="defaultEncoding" value="UTF-8" />
	 </bean>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值