springMVC筆記系列(21)——springMVC自帶的上傳檔案功能實現
springMVC為我們提供了上傳檔案的內部支援,我們只需要一些配置,然後就可以藉助於sprinngMVC提供給我們的介面完成檔案上傳的工作。
首選找到與springMVC的前端控制器DispatchServlet相關的上下文,即\WEB-INF\configs\spring\mvc-dispatcher-servlet.xml
在DispatchServlet的上下文中配置一個型別為org.springframework.web.multipart.commons.CommonsMultipartResolver的bean。
<!--200*1024*1024即200M resolveLazily屬性啟用是為了推遲檔案解析,以便捕獲檔案大小異常 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="209715200"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="resolveLazily" value="true" />
</bean>
這個bean專門用來為我們解析上載的檔案。這個bean可以配置三個屬性:
maxUploadSize設定檔案上傳的大小上限,單位是Byte,所以200MB這種記得自己算好算數哦。
defaultEncoding是預設編碼,這裡我們用的是UTF-8。
resolveLazily是延遲載入,這個設定為true,主要是為了提高效能,在需要的時候再進行解析。
我們從這個類的型別名稱中帶有的Common字首可以看出,它依賴了apache的jar包,是的,事實就是如此。因此,我們需要在POM.xml中加入jar。
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
順便提一下,這個jar包所依賴的commons-io的jar包也會自動載入,無須在pom中顯示引入commons-io座標。
然後我們現在設計一個上傳案例:
我們設計請求localhost:8080/mvc/courses/upload
因此控制器類中新增一個方法:
@RequestMapping(value="/upload", method=RequestMethod.GET)
public String showUploadPage(){
return "course_admin/file";
}
然後,我們新增對應的JSP檔案\WEB-INF\jsps\course_admin\file.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>HappyBKs Upload Page</title>
<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
</head>
<body>
<div align="center">
<h1>上傳附件</h1>
<form method="post" action="/courses/doUpload" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit"/>
</form>
</div>
</body>
</html>
該頁面的表單中包含了一個file型別的input和一個submit按鈕,另外一個值得注意的是,form本身與一般的表單相比在這裡須要額外新增一個屬性enctype=”multipart/form-data”,這是我們在檔案上傳時所必須顯示指明的屬性,沒有這個屬性將無法完成檔案上傳工作。
這個表單提交後請求的url是localhost:8080/mvc/courses/doUpload,方法當然是POST。
因此,我們須要繼續為這個請求再建立一個控制器方法來響應這個URL請求。在之前就說了,springMVC在進行相應的配置之後,就可以通過springMVC提供的一些介面來完成上載檔案功能的程式設計實踐:
@RequestMapping(value="/doUpload", method=RequestMethod.POST)
public String doUploadFile(@RequestParam("file") MultipartFile file) throws IOException {
if(!file.isEmpty()){
log.info("Process file(getOriginalFilename): {}", file.getOriginalFilename());
log.info("Process file(getName): {}", file.getName());
log.info("Process file(getContentType): {}", file.getContentType());
log.info("Process file(getSize): {}", file.getSize());
FileUtils.copyInputStreamToFile(file.getInputStream(), new File("c:\\temp\\happyBKs\\", System.currentTimeMillis()+ file.getOriginalFilename()));
}
return "success";
}
這個介面是一個類——org.springframework.web.multipart.MultipartFile。
springMVC的原始碼中的介面定義介面如下:
package org.springframework.web.multipart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.core.io.InputStreamSource;
/**
* A representation of an uploaded file received in a multipart request.
*
* <p>The file contents are either stored in memory or temporarily on disk.
* In either case, the user is responsible for copying file contents to a
* session-level or persistent store as and if desired. The temporary storages
* will be cleared at the end of request processing.
*
* @author Juergen Hoeller
* @author Trevor D. Cook
* @since 29.09.2003
* @see org.springframework.web.multipart.MultipartHttpServletRequest
* @see org.springframework.web.multipart.MultipartResolver
*/
public interface MultipartFile extends InputStreamSource {
/**
* Return the name of the parameter in the multipart form.
* @return the name of the parameter (never {@code null} or empty)
*/
String getName();
/**
* Return the original filename in the client's filesystem.
* <p>This may contain path information depending on the browser used,
* but it typically will not with any other than Opera.
* @return the original filename, or the empty String if no file
* has been chosen in the multipart form, or {@code null}
* if not defined or not available
*/
String getOriginalFilename();
/**
* Return the content type of the file.
* @return the content type, or {@code null} if not defined
* (or no file has been chosen in the multipart form)
*/
String getContentType();
/**
* Return whether the uploaded file is empty, that is, either no file has
* been chosen in the multipart form or the chosen file has no content.
*/
boolean isEmpty();
/**
* Return the size of the file in bytes.
* @return the size of the file, or 0 if empty
*/
long getSize();
/**
* Return the contents of the file as an array of bytes.
* @return the contents of the file as bytes, or an empty byte array if empty
* @throws IOException in case of access errors (if the temporary store fails)
*/
byte[] getBytes() throws IOException;
/**
* Return an InputStream to read the contents of the file from.
* The user is responsible for closing the stream.
* @return the contents of the file as stream, or an empty stream if empty
* @throws IOException in case of access errors (if the temporary store fails)
*/
@Override
InputStream getInputStream() throws IOException;
/**
* Transfer the received file to the given destination file.
* <p>This may either move the file in the filesystem, copy the file in the
* filesystem, or save memory-held contents to the destination file.
* If the destination file already exists, it will be deleted first.
* <p>If the file has been moved in the filesystem, this operation cannot
* be invoked again. Therefore, call this method just once to be able to
* work with any storage mechanism.
* <p><strong>Note:</strong> when using Servlet 3.0 multipart support you
* need to configure the location relative to which files will be copied
* as explained in {@link javax.servlet.http.Part#write}.
* @param dest the destination file
* @throws IOException in case of reading or writing errors
* @throws IllegalStateException if the file has already been moved
* in the filesystem and is not available anymore for another transfer
*/
void transferTo(File dest) throws IOException, IllegalStateException;
}
這個介面實現的類物件可以獲得檔案的本地檔名、內容型別、大小等資訊。
那麼控制器的方法引數MultipartFile如何與請求提交的表單元素相關聯呢?POST請求的表單元素是一種請求引數,建立請求引數與控制器方法引數之間的對映關聯,當然用的是註解@RequestParam,如上面的控制器方法程式碼所示。這裡@RequestParam(“file”)的值“file”與請求提交的表單中form的中的name=”file”名稱對應,這樣表單中name是file的元素的值就繫結到了控制器方法引數上,它的型別會被轉換成MultipartFile,整個複雜的轉換過程都由springMVC我們完成,我們自己要做的僅僅是使用這個留給我們的介面MultipartFile。
這裡,我們還將MultipartFile的各個屬性也一併輸出到日誌。
值得注意的是,實際的檔案處理用了FileUtils來處理檔案的相關操作,這是commons-io中十分有用的工具類,來完成檔案的拷貝、檔案流的操作等。
public static void copyInputStreamToFile(InputStream source, File destination) throws IOException
FileUtils.copyInputStreamToFile方法用於拷貝檔案,第一個引數是拷貝檔案源的流,正好MultipartFile有一個獲取輸入流的方法file.getInputStream()。輸出的檔案流,新建一個本地伺服器的檔案流,本地檔名是一個時間戳加上MultipartFile的原本的檔名。
我們繼續為上傳成功後轉發的頁面進行新增\WEB-INF\jsps\success.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div align="center">
<h1>Success!</h1>
</div>
</body>
</html>
接下來,看看實際的執行效果:
提交之後,POST請求帶著表單資料請求:
通過這個日誌輸出可以看到:
getOriginalFilename是得到檔案自己的名字
getName是得到請求提交的表單中的元素名字
getContentType得到的是檔案的型別
getSize得到檔案的大小
之後控制器將請求處理之後,轉向\WEB-INF\jsps\success.jsp
本地伺服器儲存的位置也出現了相應的圖片檔案,命名方法就是我們自己定義的方式,帶時間戳哦。
好,假如我現在上傳個超大檔案,比如dota2的壓縮安裝檔案:
提交之後,服務就崩潰了:
控制檯輸出以下錯誤:
654431 [http-apr-8080-exec-5] WARN org.springframework.web.multipart.commons.CommonsMultipartResolver - Failed to perform multipart cleanup for servlet request
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 209715200 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (9686027054) exceeds the configured maximum (209715200)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
at org.springframework.web.multipart.commons.CommonsMultipartResolver$1.initializeMultipart(CommonsMultipartResolver.java:134)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultipartFiles(AbstractMultipartHttpServletRequest.java:126)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultiFileMap(AbstractMultipartHttpServletRequest.java:106)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.cleanupMultipart(CommonsMultipartResolver.java:191)
at org.springframework.web.servlet.DispatcherServlet.cleanupMultipart(DispatcherServlet.java:1107)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2476)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2465)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (9686027054) exceeds the configured maximum (209715200)
at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:965)
at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:310)
at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:334)
at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:115)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:158)
... 33 more
654431 [http-apr-8080-exec-5] WARN org.springframework.web.multipart.commons.CommonsMultipartResolver - Failed to perform multipart cleanup for servlet request
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 209715200 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (9686027054) exceeds the configured maximum (209715200)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
at org.springframework.web.multipart.commons.CommonsMultipartResolver$1.initializeMultipart(CommonsMultipartResolver.java:134)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultipartFiles(AbstractMultipartHttpServletRequest.java:126)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultiFileMap(AbstractMultipartHttpServletRequest.java:106)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.cleanupMultipart(CommonsMultipartResolver.java:191)
at org.springframework.web.servlet.DispatcherServlet.cleanupMultipart(DispatcherServlet.java:1107)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2476)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2465)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (9686027054) exceeds the configured maximum (209715200)
at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:965)
at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:310)
at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:334)
at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:115)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:158)
... 33 more
654439 [http-apr-8080-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - Could not complete request
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 209715200 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (9686027054) exceeds the configured maximum (209715200)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
at org.springframework.web.multipart.commons.CommonsMultipartResolver$1.initializeMultipart(CommonsMultipartResolver.java:134)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultipartFiles(AbstractMultipartHttpServletRequest.java:126)
at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getFile(AbstractMultipartHttpServletRequest.java:85)
at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.resolveName(RequestParamMethodArgumentResolver.java:169)
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:90)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:99)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
at javax.servlet.http.HttpSe