Java lang illegalstateexception File Has Already Been Moved Cannot Be Transferred Again

在做文件上传时,当写入上传的文件到文件时,会报错"coffee.lang.IllegalStateException: File has been moved - cannot be read again",网上一般说需要配置maxInMemorySize,自己测试发现,maxInMemorySize确实会影响结果,并且项目还跟写入文件到文件夹的transferTo(File dest)方法也有关系,以下是报错截图:

问题说明

通过debug和浏览器提示,浏览器端代码没有问题,问题跟服务端专门处理上传代码并写出到文件夹的部分有关系。以下为代码,其中分别使用了transferTo(),getBytes()和getInputStream()来向文件系统写入文件。

                          ane            package                          Web;                                      two                          3            import                          coffee.io.File;                                      four            import                          java.io.FileOutputStream;                                      5            import                          java.io.IOException;                                      six            import                          java.io.InputStream;                                      7            import                          java.util.HashMap;                                      8            import                          java.util.Map;                                      9                          ten            import                          org.springframework.stereotype.Controller;                                      11            import                          org.springframework.web.demark.notation.RequestMapping;                                      12            import                          org.springframework.spider web.bind.annotation.ResponseBody;                                      13            import                          org.springframework.web.multipart.MultipartFile;                                      fourteen                          15            /**                          sixteen                          * 上传文件的控制器                                      17                          *                        @author                          yangchaolin                                      18                          *                                      19            */                          20            @Controller                                      21            @RequestMapping("/file")                                      22            public            class                          UploadController {                                      23                          24            @RequestMapping("/uploadFile.do")                                      25                          @ResponseBody                                      26            public            Object uploadFile(MultipartFile userfile1,MultipartFile userfile2)            throws                          IllegalStateException, IOException{                                      27            /**                          28                          * Spring MVC中可以使用MultipartFile接受上载的文件,文件中的一切数据都可以从此对象中获取                                      29                          * 比如可以获取文件原始名,文件类型等                                      30            */                          31                          32            //            比如获取上载文件的原始文件名,就是文件系统中的文件名                          33            String filename1=userfile1.getOriginalFilename();                                      34            String filename2=userfile2.getOriginalFilename();                                      35            System.out.println("文件1原始文件名为:"+filename1);                                      36            Arrangement.out.println("文件2原始文件名为:"+filename2);                                      37                          38            Map<String,String> map=new            HashMap<String,String>();                                      39                          40            /**                          41                          * 保存上传的文件有三种方法:                                      42                          * 1 MultipartFile接口的transferTo(File dest)方法                                      43                          * 将文件直接保存到目标文件,适用于大文件                                      44                          * 2 MultipartFile接口的getBytes()方法                                      45                          * 将文件全部读取,返回byte数组,保存在内存,适用于小文件,大文件有爆内存风险                                      46                          * 3 MultipartFile接口的getInputStream()方法,将文件读取后返回一个InputStream                                      47                          * 获取上载文件的流,适用于大文件                                      48            */                          49                          50            //            mac中保存文件地址 /Users/yangchaolin                                      51            //            window中保存地址 D:/yangchaolin                                      52            //            linux中保存地址 /home/soft01/yangchaolin                                      53                          54            //            1 使用transferTo(File dest)                                      55            //                          创建目标文件夹                          56            File dir=new            File("/Users/yangchaolin");                                      57            boolean            makeDirectoryResult=dir.mkdirs();                                      58            System.out.println("文件夹路径是否建立:"+makeDirectoryResult);                                      59            //            往文件夹放第一个文件                          60            File file=new                          File(dir,filename1);                                      61                          userfile1.transferTo(file);                                      62                          63            /**                          64                          * transferTo方法如果不注释掉,后面执行第二种方法写入文件到硬盘会报错                                      65                          * 报错内容:java.lang.IllegalStateException: File has been moved - cannot be read over again                                      66                          * 原因为transferTo方法底层在执行时,会检查需要写入到OutputStream的文件字节数是否超过MultipartResolver配置的大小,                                      67                          * 默认设置为10kib,如果超过了,执行完这个方法后会从内存中删除上传的文件,后面再想读取就会报错                                      68            */                          69                          seventy            //            2 使用getInputStream()方法                          71            File file1=new                          File(dir,filename1);                                      72            InputStream isWithoutBuff=userfile1.getInputStream();                                      73            //            使用FileoutputStream写出到文件                          74            FileOutputStream fosWithoutbuff=new                          FileOutputStream(file1);                                      75            //            InputStream一个字节一个字节的读取,将读取到的结果写入到FileOutputStream                          76            int            b;//            读取一个byte后,以int类型显示数值,范围0~255                                      77            while((b=isWithoutBuff.read())!=-ane) {                                      78            //            read()方法每次只读取文件的一个byte                          79                          fosWithoutbuff.write(b);                                      lxxx                          }                                      81                          isWithoutBuff.close();                                      82                          fosWithoutbuff.close();                                      83                          84            //            同样使用InputStream读取,将读取到的结果写入到FileOutputStream,但使用了缓冲字节数组                          85            File file2=new                          File(dir,filename2);                                      86            InputStream isWithBuff=userfile2.getInputStream();                                      87            FileOutputStream fosWithBuff=new                          FileOutputStream(file2);                                      88            int            due north;//            保存返回读取到的字节数, 一次8192个字节,当不够时就是实际读取到的字节数                          89            byte[] buff=new            byte[8*1024];//            8kib的缓冲字节数组                          xc            while((northward=isWithBuff.read(buff))!=-ane) {                                      91            System.out.println("读取后的字节数:"+n);                                      92            fosWithBuff.write(buff, 0, n);                                      93                          }                                      94                          isWithBuff.close();                                      95                          fosWithBuff.close();                                      96                          97            //            3 使用getBytes()方法                          98            byte[] data=userfile2.getBytes();                                      99            //            写出byte数组到文件            100            File file3=new                          File(dir,filename2);                        101            FileOutputStream fosWithByte=new                          FileOutputStream(file3);                        102            fosWithByte.write(data,0,data.length);                        103                          fosWithByte.close();                        104            105            map.put("Result", "upload Success");                        106            107            render            map;//            需要导入jackson的三个核心包,否则无法正常转换成JSON            108            109                          }                        110            111            }

此外还跟解析器的属性maxInMemorySize配置也有关系,以下是解析器配置:

                          1            <?            xml version="1.0" encoding="UTF-8"            ?>                          2            <            beans                        xmlns            ="http://world wide web.springframework.org/schema/beans"                          xmlns:xsi            ="http://world wide web.w3.org/2001/XMLSchema-instance"                          3                          xmlns:context            ="http://www.springframework.org/schema/context"                          xmlns:util            ="http://www.springframework.org/schema/util"                          iv                          xmlns:jee            ="http://www.springframework.org /schema/jee"                          xmlns:tx            ="http://www.springframework.org/schema/tx"                          5                          xmlns:jpa            ="http://www.springframework.org/schema/data/jpa"                          xmlns:mvc            ="http://www.springframework.org/schema/mvc"                          six                          xsi:schemaLocation            ="                                      7                          http://world wide web.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd                                      8                          http://world wide web.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd                                      9                          http://www.springframework.org/schema/util http://www.springframework.org/schema/util/jump-util-3.ii.xsd                        10                          http://world wide web.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd                        11                          http://world wide web.springframework.org/schema/tx http://www.springframework.org/schema/tx/bound-tx-3.2.xsd                        12                          http://www.springframework.org/schema/data/jpa http://world wide web.springframework.org/schema/information/jpa/spring-jpa-ane.three.xsd                        13                          http://world wide web.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"            >            xiv            15            <!--                          配置组件扫描                        -->            16            <            context:component-browse                        base-package            ="Web"            ></            context:component-scan            >            17            <!--                          添加注解驱动                        -->            18            <            mvc:annotation-driven            ></            mvc:annotation-driven            >            nineteen            twenty            <!--                          配置文件上载解析器MultipartResolver                        -->            21            <            edible bean                        id            ="multipartResolver"                          class            ="org.springframework.web.multipart.commons.CommonsMultipartResolver"            >            22            <!--                          i of the properties bachelor; the maximum file size in bytes                        -->            23            <!--                          通过set方法设置以下两个父类属性,父类为CommonsFileUploadSupport.class                        -->            24            <            property                        name            ="maxUploadSize"                          value            ="10000000"            />            <!--            10M大小                        -->            25            <            property                        name            ="defaultEncoding"                          value            ="UTF-8"            />            <!--                          文件名编码,可以适用于中文名                        -->            26            27            <!--                          <property name="maxInMemorySize" value="10000000" />            -->            28            <!--                          上传文件大小默认小于10kib时,会将文件写入硬盘前保存在内存中,否则就不会保存在内存中                        -->            29            </            bean            >            30            31            32            </            beans            >          

下面简单测试下代码和配置对上传结果的影响:

(one)保留transferTo()代码,并对maxInMemorySize配置10M大小

(2)保留transferTo()代码,对maxInMemorySize不进行配置

(3)注释transferTo()代码,对maxInMemorySize不进行配置

保留transferTo()代码,并对maxInMemorySize配置10M大小

测试结果:可以上传,并且两张图片大小都25KB以上

保留transferTo()代码,对maxInMemorySize不进行配置

测试结果:服务端报错"File has been moved - cannot be read again" ,页面显示和文件查看表明没有上传成功。

注释transferTo()代码,对maxInMemorySize不进行配置

 测试结果:

可以上传,并且两张图片大小都25KB以上

问题分析

从测试结果如下:

(1)当maxInMemorySize配置足够大时,就算有transferTo()方法执行也能正常上传

(2)当maxInMemorySize不配置,当文件比较大时,有transferTo()方法执行会报错

(iii)当maxInMemorySize不配置,没有transferTo()方法执行,将正常上传文件

影响报错的主要原因为transferTo()方法和maxInMemorySize两者,因此需要查看transferTo方法的源码。

源码查看

transferTo()方法是MultipartFile接口方法,需要查看其实现类方法具体实现,查看发现其实现类为CommonsMultipartFile,查看其具体实现方法,发现其需要确认isAvailable()方法返回的结果,根据其抛出报警内容,发现刚好是项目抛出的异常内容,因此需要继续查看isAvailable()方法的执行。

                          1                          @Override                                      2            public            void            transferTo(File dest)            throws                          IOException, IllegalStateException {                                      3            if            (!isAvailable()) {                                      4            throw            new            IllegalStateException("File has already been moved - cannot be transferred once more");                                      5                          }                                      half-dozen                          7            if            (dest.exists() && !dest.delete()) {                                      8            throw            new                          IOException(                                      9            "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");                        ten                          }                        11            12            endeavor                          {                        xiii            this            .fileItem.write(dest);                        fourteen            if                          (logger.isDebugEnabled()) {                        15            Cord action = "transferred";                        16            if            (!this            .fileItem.isInMemory()) {                        17            action = isAvailable() ? "copied" : "moved";                        xviii                          }                        xix            logger.debug("Multipart file '" + getName() + "' with original filename [" +            20            getOriginalFilename() + "], stored " + getStorageDescription() + ": " +            21            activity + " to [" + dest.getAbsolutePath() + "]");                        22                          }                        23                          }                        24            catch                          (FileUploadException ex) {                        25            throw            new                          IllegalStateException(ex.getMessage());                        26                          }                        27            grab                          (IOException ex) {                        28            throw                          ex;                        29                          }                        thirty            catch                          (Exception ex) {                        31            logger.error("Could not transfer to file", ex);                        32            throw            new            IOException("Could not transfer to file: " +              ex.getMessage());                        33                          }                        34            }

isAvailable()方法的代码,发现其需要检查上传的文件是否在内存中,当只有在内存中时,才返回true,否则返回false后抛出异常,因此继续查看isInMemory()方法。

                          1            /**                          ii                          * Determine whether the multipart content is even so bachelor.                                      3                          * If a temporary file has been moved, the content is no longer available.                                      4            */                          five            protected            boolean                          isAvailable() {                                      vi            //                          If in memory, it's available.                          7            if            (this            .fileItem.isInMemory()) {                                      viii            return            true            ;                                      9                          }                        10            //                          Bank check actual beingness of temporary file.            11            if            (this.fileItem            instanceof                          DiskFileItem) {                        12            return            ((DiskFileItem)            this            .fileItem).getStoreLocation().exists();                        xiii                          }                        14            //                          Check whether electric current file size is different than original i.            15            return            (this.fileItem.getSize() ==            this            .size);                        16            }

查看发现isInMemory()方法是commons-fileupload.jar包下接口FileItem中定义的,因此继续查看接口实现类,发现为DiskFileItem,并且查看实现类,发现其首先需要检查缓存文件是否存在,如果不存在调用DeferredFileOutputStream的isInMemory方法继续查询。

                          i            /**                          2                          * Provides a hint as to whether or not the file contents will exist read                                      three                          * from memory.                                      four                          *                                      v                          *                        @return                          <code>true</code> if the file contents volition be read                                      6                          *         from memory; <code>imitation</code> otherwise.                                      7            */                          viii            public            boolean                          isInMemory() {                                      ix            if            (cachedContent !=            null            ) {                        10            return            truthful            ;                        11                          }                        12            return                          dfos.isInMemory();                        13            }

isInMemory方法还会继续调用DeferredFileOutputStream对象dfos的isInMemory方法。

                          1            /**                          2                          * Determines whether or not the information for this output stream has been                                      iii                          * retained in retentiveness.                                      iv                          *                                      five                          *                        @return                          <code>true</code> if the data is available in memory;                                      half dozen                          *         <code>fake</code> otherwise.                                      7            */                          8            public            boolean                          isInMemory()                                      9                          {                        x            return            !isThresholdExceeded();                        11            }

最后发现调用了ThresholdingOutputStream的isThresholdExceeded()方法,具体代码如下,其会检查准备写出到输出流的文件大小,是否超过设定的阈值,这个阈值通过debug发现,就是我们前面配置的参数maxInMemorySize,其默认是10Kib。在本项目中,由于上传的图片都在10Kib大小以上,其都超过了阈值,方法执行返回为true,参数传入到isInMemory方法后,返回false,最终传入到最上层会返回false,从而抛出本次记录的异常。后面将maxInMemorySize设置为10M后,就算有transferTo()方法执行,因上传文件大小分别为20多Kib均为超过阈值,所以能正常上传。

                          one            /**                          2                          * Determines whether or not the configured threshold has been exceeded for                                      iii                          * this output stream.                                      four                          *                                      5                          *                        @return                          <lawmaking>truthful</code> if the threshold has been reached;                                      6                          *         <code>false</lawmaking> otherwise.                                      7            */                          8            public            boolean                          isThresholdExceeded()                                      9                          {                        x            render            written >              threshold;                        11            }

再次验证

为了验证上面的结论,准备了两个文件大小在10Kib以下的文件进行文件上传测试,并且测试不配置maxInMemorySize,同时执行transferTo()方法。测试结果如下:

总结

(1)如果上传文件准备将文件写入到文件夹,抛出异常"java.lang.IllegalStateException: File has been moved - cannot be read again",很有可能跟解析器MultipartResolver的属性maxInMemorySize配置太小有关,由于其默认配置只有10Kib,当上传文件足够大,并且使用了MultipartFile的transferTo()方法写入文件到文件夹时,就会抛出异常。

(2)文件上传时,最好将maxInMemorySize属性配置更大一点。

millsfortaryto93.blogspot.com

Source: https://www.cnblogs.com/youngchaolin/p/10702457.html

0 Response to "Java lang illegalstateexception File Has Already Been Moved Cannot Be Transferred Again"

Postar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel