使用SPRING上传文件偶尔遇到报错:
java.lang.IllegalStateException: File has been moved - cannot be read again
at org.springframework.web.multipart.commons.CommonsMultipartFile.getInputStream(CommonsMultipartFile.java:123)
注意该问题是偶尔发生,并非每次都能重现。
第一感这应该是一个多线程问题,因为不是每次能重现很有可能是资源竞争。同时代码中也确实用了多线程.在我的controller中:
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST)
public String uploadExcel(DataSetUploadBean dataSetUploadBean, HttpServletRequest request, Model model) {
...
final MultipartFile file = dataSetUploadBean.getFile();
...
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(((CommonsMultipartFile)file).getStorageDescription());
dataSetUploadImpl.process(dto, file, userId);
} catch (Exception e) {
e.printStackTrace();
dto.setIsRun(1);
dto.setHasError(1);
dto.setErrorMassage("异常原因:"+e.getMessage());
}
}
}).start();
...
}
这里只帖上了关键的代码。上传文件的处理可能是很耗时的,所以新建了一个线程去做处理工作。MultipartFile 上传文件对象由spring mvc绑定在DataSetUploadBean对象中,每次取出上传文件对象,并新建线程调用dataSetUploadImpl service进行处理。
而网上的资料均指向一个问题:
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--The limitation size of file is 500m。the value -1 means there is no limitation-->
<beans:property name="maxUploadSize" value="500000000"/>
<beans:property name="maxInMemorySize" value="500000000"/>
</beans:bean>
在配置spring MultipartResolver时不仅要配置maxUploadSize,还需要配置maxInMemorySize。但原因都没说的很清楚。只是简单说maxInMemorySize的默认值为1024 bytes(待确认),超出这个大小的文件上传spring会先将上传文件记录到临时文件中。临时文件会被删除。
还有说如果要在系统中读文件两次,而文件不在内存中,就会导致该问题。(已证明其实可以多次读文件,只要是单线程即可)
我将配置maxInMemorySize加上后确实解决了问题。但网上原理明显无法解释我的问题。我的问题是“偶尔”出现,如果确实因为maxInMemorySize的问题,那么问题一定是每次都出现的。所以加大内存将上传文件保存在内存中只是一种临时解决方案,恰好解决了我的问题。特别对于对内存非常在意的系统随意增加内存更是不可取的。
-----------------------------------------------------------------------
多次review代码不存在读文件两次的问题(已证明其实可以多次读文件,只要是单线程即可)
将多线程改为单线程后,该问题不能重现。第一感是正确的还是多线程的原因。但从代码可以看到每次运行均将上传文件获取后进行处理,并没有数据共享,照理应该没有多线程问题。那问题应该是“临时文件会被删除”。
所以估计是spring MultipartResolver有多线程问题,只能放大招跟踪spring源码。
期间有同事估计是spring保存的临时文件重名,导致前一个线程处理完直接删除了后一个线程的文件,虽然觉得spring应该不会犯这种低级错误,但想想也不是没有这个可能。读源码可以看到临时文件是用了uuid来作为文件名的,排除该可能。
源码:
DiskFileItem.java
protected File getTempFile() {
if(this.tempFile == null) {
File tempDir = this.repository;
if(tempDir == null) {
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp";
this.tempFile = new File(tempDir, tempFileName);
}
return this.tempFile;
}
------------------------------------------------------------------------
源码链:
CommonsMultipartResolver.java
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
...
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
... }
spring 使用了 apache fileUpload 包
ServletFileUpload.java
public List parseRequest(HttpServletRequest request) throws FileUploadException {
return this.parseRequest(new ServletRequestContext(request));
}
FileUploadBase.java
public List /* FileItem */ parseRequest(RequestContext ctx){
...
FileItem fileItem = fac.createItem(item.getFieldName(),
item.getContentType(), item.isFormField(),
fileName);
...
}
DiskFileItemFactory.java
public FileItem createItem(String fieldName, String contentType, boolean isFormField, String fileName) {
DiskFileItem result = new DiskFileItem(fieldName, contentType, isFormField, fileName, this.sizeThreshold, this.repository);
FileCleaningTracker tracker = this.getFileCleaningTracker();
if(tracker != null) {
tracker.track(result.getTempFile(), this);
}
return result;
}
Tracker即用来删除临时文件。理解它的工作机制就会理解临时文件是怎么被删除的,那么就能知道,我们程序为什么读不到文件了。
fileupload的官网地址:
https://commons.apache.org/proper/commons-fileupload/
找到Resource cleanup一段
temporary files are deleted automatically, if they are no longer used (more precisely, if the corresponding instance of java.io.File is garbage collected. This is done silently by the org.apache.commons.io.FileCleaner class, which starts a reaper thread.
相信大家都明白了。
如果文件没有被引用,被GC回收那么文件被清理。
如果读tracker的源码可以发现其实apache使用了PhantomReference虚引用的方式来跟踪文件是否被回收,如果回收则删除文件。但apache创建虚引用的对象并非TempFile本身。在debug模式下也能看到其实TempFile文件的引用是一直存在的。由于apache fileupload的部分代码未下载到源码,无法知晓创建虚引用的对象到底是谁。但能推理出该对象一定存在于tomcat的一个处理线程中的某一个临时变量中。所以当tomcat的处理线程完成后,GC回收了该对象,通过虚引用apache接收到回收的消息删除了临时文件
分享到:
相关推荐
win10 codeblocks安装mingw-w64时出现the file has been downloaded incorrectly问题解决-附件资源
网友分享的能够解决fatal error C1083: Cannot open include file: 'config-win.h': No such file or directory问题的程序。MySQL-python-1.2.3.win32-py2.7.exe-32位MySQL-python-1.2.3.win-amd64-py...
ERR-FILE-READ-FAILED(解决方案).md
Caused by: java.util.jar.JarException: file:/opt/code/signal-Server-master/target/TextSecureServer-1.87.jar has unsigned entries - org/whispersystems/dispatch/DispatchManager$4.class at javax.crypto....
在vc6上运行程序的时候提示 NMSQL.DLL This required file cannot be loaded. Please re-install Microsoft Visual C++ 压缩包中有解决办法
Windows 95, 98, ME and XP should be supported, but has not been tested. Linux and Kylix are not supported. There are *NO* plans to port the library to Kylix. The drag and drop protocols available on ...
在进行Android应用开发时,尤其是使用Eclipse作为集成开发环境(IDE)时,开发者可能会遇到一个常见的错误:“The import android cannot be resolved”。这个错误通常发生在尝试导入或打开一个已存在的Android项目...
https://www.cnblogs.com/pingjie/p/4146727.html 感谢这位大哥写的这篇文章。
在编程过程中,我们时常会遇到各种编译错误,其中一种常见的问题是"The import com.loopj cannot be resolved"。这个错误信息表明你的项目无法找到`com.loopj`包,这通常是由于缺少必要的库文件导致的。`com.loopj`...
MySQL服务器在运行时可能由于配置了`--read-only`选项,导致无法执行写入操作,从而出现"1209 - The MySQL server is running with the --read-only option so it cannot execute this statement"的错误。...
A file that is required cannot be installed because the cabinet file C:\ProgramData\Package Cache\{C57Al78F-F1CF-3FE0-91BA-27A1EB2C5492}v2.2.0.0\sfx x64. cab has an invalid digital sianature.This may ...
在Java编程环境中,我们经常会遇到编译错误提示“XXX cannot be resolved”,这通常意味着系统无法找到指定的类或方法。在这种情况下,"org.apache.commons.lang.exception.NestableRuntimeException cannot be ...
read-excel-file 在浏览器或 Node.js 中读取中小型*.xlsx文件。 使用严格的模式解析为 JSON。 另请查看以编写简单的*.xlsx文件。 安装 npm install read - excel - file -- save 如果您不使用捆绑程序,请使用的...
Cannot find or open the PDB file in VS2010 Cannot find or open the PDB file是一个常见的错误信息,在Visual Studio 2010(VS2010)中经常出现。PDB文件是Program Database文件,用于调试和符号化程序。该错误...
资源不能选择0分下载了吗?可怕。这是内涵FileProvider的android-support-v4包,版本在安卓5.0 sdk 内。
prerequisite patches in an unstable way, which has been updated to compute in a way that is compatible with "git patch-id --stable". * The "git log" command by default behaves as if the --mailmap ...
- A hang on startup has been corrected. A 2 minute timeout has been added to the collection of system information. - Video playback, Hard disk and CD/DVD test 'no operations' error reporting ...
// 和这个代码是相同的在返回 File 方法之后将会释放 fileStream 但是在 asp dotnet core 返回给客户端的信息是在 Downloa
-l, --one-file-system stay in local file system when creating archive -K, --starting-file=NAME begin at file NAME in the archive -N, --newer=DATE only store files newer than DATE --newer-mtime ...