Servlet技術出現以前,文件上傳的編程仍然是一項很困難的任務,它涉及在服務器端解析原始的HTTP響應。為了減輕編程的痛苦,開發人員借助於商業的文件上傳組件。值得慶幸的是,2003年,Apache Software Foundation發布了開源的Commons FileUpload組件,它很快成為了Java Web應用程序員的利器。
經過很多年,Servlet的設計人員才意識到文件文件上傳的重要性,並終於成為Servlet 3.0的內置特性。Servlet 3.0的開發人員不再需要將Commons FileUpload組件導入到他們的項目中去。
為此,在Spring MVC中處理文件上傳有兩種情況:
- 在Servlet 3.0版本以下,使用Apache Commons FileUpload組件;
- 在Servlet 30.版本以上,利用Servlet 3.0及其更高版本的內置支持。
無論使用哪個版本的Servlet,都要利用相同的API來處理已經上傳的文件。本篇博客將會介紹如何在需要支持文件上傳的Spring MVC應用中使用Commons FileUpload和Servlet 3.0文件上傳特性。
一 前端編程
為了上傳文件,必須將HTML表格enctype屬性值設置為multipart/form-data,像下面這樣:
<form action="action" method="post" enctype="multipart/form-data"> select a file <input type="file" name="fieldName"/> <input type="submit" value="Upload"/> </form>
表格中必須包含類型為file的一個input元素,它會顯示成一個按鈕,單擊時,它會打開一個對話框,用來選擇文件。
在HTML 5之前,如果想要上傳多個文件,必須使用多個類型為file的input元素。但是在HTML 5中,通過在input元素中引入multiple屬性,使得多個文件的上傳變得更加簡單。在HTML 5中編寫以下任意一行代碼,便可以生成一個按鈕來選擇多個文件:
<input type="file" name="fieldName" multiple/> <input type="file" name="fieldName" multiple="multiple"/> <input type="file" name="fieldName" multiple=""/>
二 MultipartFile接口
在Spring MVC中處理已經上傳的文件十分容易。上傳到Spring MVC應用程序中的文件會被包含在一個MultipartFile對象中。我們唯一的任務就是,用類型MultipartFile的屬性編寫一個domain類。
org.springframework.web.multipart.MultipartFile接口源代碼如下:

/* * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.multipart; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import org.springframework.core.io.InputStreamSource; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; import org.springframework.util.FileCopyUtils; /** * 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 storage * 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 * @see org.apache.commons.fileupload.FileItem#getName() * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename */ @Nullable 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) */ @Nullable 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. * <p>The user is responsible for closing the returned 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; /** * Return a Resource representation of this MultipartFile. This can be used * as input to the {@code RestTemplate} or the {@code WebClient} to expose * content length and the filename along with the InputStream. * @return this MultipartFile adapted to the Resource contract * @since 5.1 */ default Resource getResource() { return new MultipartFileResource(this); } /** * 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 target file has been moved in the filesystem, this operation * cannot be invoked again afterwards. Therefore, call this method just once * in order to work with any storage mechanism. * <p><b>NOTE:</b> Depending on the underlying provider, temporary storage * may be container-dependent, including the base directory for relative * destinations specified here (e.g. with Servlet 3.0 multipart handling). * For absolute destinations, the target file may get renamed/moved from its * temporary location or newly copied, even if a temporary copy already exists. * @param dest the destination file (typically absolute) * @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 * @see org.apache.commons.fileupload.FileItem#write(File) * @see javax.servlet.http.Part#write(String) */ void transferTo(File dest) throws IOException, IllegalStateException; /** * Transfer the received file to the given destination file. * <p>The default implementation simply copies the file input stream. * @since 5.1 * @see #getInputStream() * @see #transferTo(File) */ default void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest)); } }
該接口具有以下方法:
/** * 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 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) */ @Nullable String getContentType();
它返回文件的內容類型。
/** * Return an InputStream to read the contents of the file from. * <p>The user is responsible for closing the returned 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;
它返回一個InputStream ,從中讀取文件的內容。
/** * 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 * @see org.apache.commons.fileupload.FileItem#getName() * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename */ @Nullable String getOriginalFilename();
它返回客戶端文件系統中文件的原始文件名稱。
/** * Return the size of the file in bytes. * @return the size of the file, or 0 if empty */ long getSize();
它以字節為單位,返回文件的大小。
/** * 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 target file has been moved in the filesystem, this operation * cannot be invoked again afterwards. Therefore, call this method just once * in order to work with any storage mechanism. * <p><b>NOTE:</b> Depending on the underlying provider, temporary storage * may be container-dependent, including the base directory for relative * destinations specified here (e.g. with Servlet 3.0 multipart handling). * For absolute destinations, the target file may get renamed/moved from its * temporary location or newly copied, even if a temporary copy already exists. * @param dest the destination file (typically absolute) * @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 * @see org.apache.commons.fileupload.FileItem#write(File) * @see javax.servlet.http.Part#write(String) */ void transferTo(File dest) throws IOException, IllegalStateException;
它將上傳的文件保存到目標目錄下。
/** * 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();
它表示被上傳的文件是否為空(沒有上傳文件、或者文件內容為空)。
三 使用Commons Fileupload組件上傳文件
只有實現了Servlet 3.0及其更高版本規范的Servlet容器,才支持文件上傳。對於版本低於Servlet 3.0的容器,則需要Apache Commons Fileupload組件,commons-fileupload.jar包的下載路徑如下:https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload。
這是一個開源項目,因此是免費的,它會提供了源代碼。為了讓Commons Fileupload能夠運行,還需要一個Apache Commins組件commons-io.jar,commons-io.jar包的下載路徑如下:https://mvnrepository.com/artifact/commons-io/commons-io。
下載完這兩個JAR包,我們還需要做以下工作:
- 將這兩個JAR文件復制到應用程序的/WEB-INF/lib路徑下;
- 在Spring MVC配置文件中定義multipartResolver bean;
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5000000000"/> </bean>
CommonsMultipartResolver類,實際上就是將org.apache.commons.fileupload.servlet.ServletFileUpload類和org.apache.commons.fileupload.disk.DiskFileItemFactory的功能進行了整合,具體代碼如下:

/* * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.multipart.commons; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.springframework.util.Assert; import org.springframework.web.context.ServletContextAware; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest; import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest; import org.springframework.web.util.WebUtils; /** * Servlet-based {@link MultipartResolver} implementation for * <a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a> * 1.2 or above. * * <p>Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as * bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding * ServletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold", * "headerEncoding") for details in terms of defaults and accepted values. * * <p>Saves temporary files to the servlet container's temporary directory. * Needs to be initialized <i>either</i> by an application context <i>or</i> * via the constructor that takes a ServletContext (for standalone usage). * * @author Trevor D. Cook * @author Juergen Hoeller * @since 29.09.2003 * @see #CommonsMultipartResolver(ServletContext) * @see #setResolveLazily * @see org.apache.commons.fileupload.servlet.ServletFileUpload * @see org.apache.commons.fileupload.disk.DiskFileItemFactory */ public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { private boolean resolveLazily = false; /** * Constructor for use as bean. Determines the servlet container's * temporary directory via the ServletContext passed in as through the * ServletContextAware interface (typically by a WebApplicationContext). * @see #setServletContext * @see org.springframework.web.context.ServletContextAware * @see org.springframework.web.context.WebApplicationContext */ public CommonsMultipartResolver() { super(); } /** * Constructor for standalone usage. Determines the servlet container's * temporary directory via the given ServletContext. * @param servletContext the ServletContext to use */ public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); } /** * Set whether to resolve the multipart request lazily at the time of * file or parameter access. * <p>Default is "false", resolving the multipart elements immediately, throwing * corresponding exceptions at the time of the {@link #resolveMultipart} call. * Switch this to "true" for lazy multipart parsing, throwing parse exceptions * once the application attempts to obtain multipart files or parameters. */ public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } /** * Initialize the underlying {@code org.apache.commons.fileupload.servlet.ServletFileUpload} * instance. Can be overridden to use a custom subclass, e.g. for testing purposes. * @param fileItemFactory the Commons FileItemFactory to use * @return the new ServletFileUpload instance */ @Override protected FileUpload newFileUpload(FileItemFactory fileItemFactory) { return new ServletFileUpload(fileItemFactory); } @Override public void setServletContext(ServletContext servletContext) { if (!isUploadTempDirSpecified()) { getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext)); } } @Override public boolean isMultipart(HttpServletRequest request) { return ServletFileUpload.isMultipartContent(request); } @Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } } /** * Parse the given servlet request, resolving its multipart elements. * @param request the request to parse * @return the parsing result * @throws MultipartException if multipart resolution failed. */ protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); try { List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } } /** * Determine the encoding for the given request. * Can be overridden in subclasses. * <p>The default implementation checks the request encoding, * falling back to the default encoding specified for this resolver. * @param request current HTTP request * @return the encoding for the request (never {@code null}) * @see javax.servlet.ServletRequest#getCharacterEncoding * @see #setDefaultEncoding */ protected String determineEncoding(HttpServletRequest request) { String encoding = request.getCharacterEncoding(); if (encoding == null) { encoding = getDefaultEncoding(); } return encoding; } @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { try { cleanupFileItems(request.getMultiFileMap()); } catch (Throwable ex) { logger.warn("Failed to perform multipart cleanup for servlet request", ex); } } } }
multipartResolver 對象則通過配置property元素來調用setter方法以設置屬性值。我們可以通過setter方式注入的屬性有:
- maxUploadSize:控制上傳單個文件的大小,單位是字節;
- maxInMemorySize:設置上傳文件時用到的臨時文件的大小,單位是字節;
- defaultEncoding:請求參數的默認編碼方式。
這些屬性被用來對上傳文件進行設置。
此外,CommonsMultipartResolver類的還有一個非常重要的函數:
/** * Parse the given servlet request, resolving its multipart elements. * @param request the request to parse * @return the parsing result * @throws MultipartException if multipart resolution failed. */ protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); try { List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } }
通過parseRequest()函數解析form中的所有請求字段,並保存到List<FileItem>集合中,然后將集合轉換為MultipartParsingResult類型返回:

/** * Holder for a Map of Spring MultipartFiles and a Map of * multipart parameters. */ protected static class MultipartParsingResult { private final MultiValueMap<String, MultipartFile> multipartFiles; private final Map<String, String[]> multipartParameters; private final Map<String, String> multipartParameterContentTypes; public MultipartParsingResult(MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) { this.multipartFiles = mpFiles; this.multipartParameters = mpParams; this.multipartParameterContentTypes = mpParamContentTypes; } public MultiValueMap<String, MultipartFile> getMultipartFiles() { return this.multipartFiles; } public Map<String, String[]> getMultipartParameters() { return this.multipartParameters; } public Map<String, String> getMultipartParameterContentTypes() { return this.multipartParameterContentTypes; } }
MultipartParsingResult類有個重要的屬性:
private final MultiValueMap<String, MultipartFile> multipartFiles;
該Map的鍵值為String類型,保存的是表單類型為file的input元素的name屬性值,值為MultipartFile接口類型,該類型保存了該input元素對應的上傳文件。
四 Servlet 3.0以下版本文件上傳示例
范例upload1展示了如何利用Apache Commons FileUpload處理已經上傳的文件。這個范例在Servlet 3.0容器中也是有效的。upload1有一個domain包,包含Procudt類,它包含了一個MultipartFile對象列表。該示例介紹了如何進行產品圖片的上傳。
1、目錄結構
下面展示upload1應用的目錄結構:
注意:在lib中我們需要導入Apache Commons FileUpload組件。
2、Product類
Product類具有類型為List<MultipartFile>的imagea屬性,這個屬性用來保存上傳的產品圖片文件(可以是多個圖片文件):
package domain; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.springframework.web.multipart.MultipartFile; public class Product implements Serializable { private static final long serialVersionUID = 1; @NotNull @Size(min=1, max=10) private String name; private String description; private BigDecimal price; private List<MultipartFile> images; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public List<MultipartFile> getImages() { return images; } public void setImages(List<MultipartFile> images) { this.images = images; } }
3、控制器
package controller; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import domain.Product; @Controller public class ProductController { private static final Log logger = LogFactory .getLog(ProductController.class); //請求URL:/input-product @RequestMapping(value = "/input-product") public String inputProduct(Model model) { model.addAttribute("product", new Product()); return "ProductForm"; } //請求URL:/save-product @RequestMapping(value = "/save-product") public String saveProduct(HttpServletRequest servletRequest, @ModelAttribute Product product, BindingResult bindingResult, Model model) { //獲取上傳的圖片文件(可以多個文件) List<MultipartFile> files = product.getImages(); //用於保存所有文件名 List<String> fileNames = new ArrayList<String>(); //檢驗是否有文件? if (null != files && files.size() > 0) { //遍歷 for (MultipartFile multipartFile : files) { //獲取文件名 String fileName = multipartFile.getOriginalFilename(); fileNames.add(fileName); //獲取應用/image虛擬路徑在文件系統上對應的真實路徑 + 文件名 並創建File對象 File imageFile = new File(servletRequest.getServletContext() .getRealPath("/image"), fileName); try { //將上傳的文件保存到目標目錄下 multipartFile.transferTo(imageFile); } catch (IOException e) { e.printStackTrace(); } } } // save product here model.addAttribute("product", product); return "ProductDetails"; } }
ProductController類有inputProduct()和saveProduct()兩個請求處理方法。inputProduct()方法向瀏覽器發出一個產品表單,saveProduct()方法將已經上傳的圖片文件保存到應用程序的image目錄下,文件名不改變。
注意:必須先創建好image文件夾。
4、配置文件
下面給出springmvc-config.xml文件的所有內容:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="controller" /> <mvc:annotation-driven /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/*.html" location="/" /> <mvc:resources mapping="/image/**" location="/image/" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5000000000"/> </bean> </beans>
利用multipartResolver bean的maxUploadSize屬性,可以設置能夠接受的最大文件容量。如果沒有設置這個屬性,則沒有最大文件容量限制。沒有設置文件容量限制,並不意味着可以上傳任意大小的文件。上傳過大的文件時需要花費很長的時間,這樣會導致服務器超時,為了處理超大文件的問題,可以利用HTML 5 File API將文件切片,然后再分別上傳這些文件。
如果想對上傳的文件類型進行過濾,那么我們可以需要先獲取上傳文件的名稱,然后檢測其擴展名。此外,我們也可以在前端使用js代碼檢測上傳文件的擴展名。
部署描述符(web.xml文件):
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <!-- 配置編碼方式過濾器,注意一點:要配置在所有過濾器的前面 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
注意,web.xml文件中我們配置了編碼方式過濾器,將所有http請求的參數編碼為UTF-8方式,與jsp中頁面編碼一致。
5、視圖
用於上傳圖片文件的ProductForm.jsp頁面如下所示:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <form:form modelAttribute="product" action="save-product" method="post" enctype="multipart/form-data"> <fieldset> <legend>Add a product</legend> <p> <label for="name">Product Name: </label> <form:input id="name" path="name" cssErrorClass="error"/> <form:errors path="name" cssClass="error"/> </p> <p> <label for="description">Description: </label> <form:input id="description" path="description"/> </p> <p> <label for="price">Price: </label> <form:input id="price" path="price" cssErrorClass="error"/> </p> <p> <label for="image">Product Image: </label> <input type="file" name="images[0]"/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Product"> </p> </fieldset> </form:form> </div> </body> </html>
注意表單中類型為file的input元素,它將顯示為一個按鈕,用於選擇要上傳的文件。並且input元素的name屬性指定為"images[0]",即綁定到表單支持對象product的images屬性(List<MultipartFile>類型>)的第一個元素上。
如果想支持多個文件同時上傳,只需將 <input type="file" name="images[0]"/>替換成如下:
<input type="file" name="images" multiple/>
提交Product表單,將會調用saveProduct()方法,如果這個方法能夠順利執行,用戶將會跳轉到ProductDetails.jsp頁面:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>Save Product</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <h4>The product has been saved.</h4> <p> <h5>Details:</h5> Product Name: ${product.name}<br/> Description: ${product.description}<br/> Price: $${product.price} <p>Following files are uploaded successfully.</p> <ol> <c:forEach items="${product.images}" var="image"> <li>${image.originalFilename} <img width="100" src="<c:url value="/image/"/> ${image.originalFilename}"/> </li> </c:forEach> </ol> </p> </div> </body> </html>
該頁面將會顯示已經保存的Product的詳細信息及其圖片。
6、測試
將應用程序部署到tomcat服務器,並在網頁輸入以下URL:
http://localhost:8008/upload1/input-product
將會看到一個如圖所示的Add Product表單,試着輸入一些產品信息,並選擇一個要上傳的文件:
單擊"Add Product"按鈕,就可以看到如下所示的網頁:
同時我們可以在tomcat服務器,upload1應用下的image目錄下看到,已經上傳的文件:
如果將ProductForm.jsp中的:<input type="file" name="images[0]"/>更改為如下代碼:
<input type="file" name="images" multiple/>
那么就可以實現多個文件同時上傳:
五 Servlet 3.0及其更高版本上傳文件
有了Servlet 3.0就不需要Common FileUpload和Common IO JAR包了。在Servlet 3.0及其以上版本的容器進行服務器端文件上傳的編程,是圍繞着注解類型MultipartConfig和javax.servlet.http.Part接口進行的。處理已上傳文件的Servlets必須以@MultipartConfig進行注解。
下列是可能在MultipartConfig注解類型中出現的屬性,它們都是可選的:
- maxFileSize:單個上傳文件的最大容量,默認值是-1,表示沒有限制,大於指定容量的文件將會遭到拒絕;
- maxRequestSize:表示Multipart HTTP請求運行的最大容量,默認值為-1,表示沒有限制;
- location:表示在Part調用write()方法時,要將已上傳的文件保存到磁盤中的位置;
- fileSizeThreshod:設置上傳文件時用到的臨時文件的大小;
Spring MVC的DispatcherServlet處理大部分或者所有請求。但是遺憾的是,如果不修改源代碼,將無法對Servlet進行注解。但值得慶幸的是,Servlet 3.0中有一種比較容易的方法,能使一個Servlet變成一個MultipartConfig Servlet,即給部署描述符(web.xml)中的Servlet聲明賦值。以下代碼與用@MultipartConfig給DispatcherServlet進行注解的效果一樣:
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <multipart-config> <max-file-size>20848820</max-file-size> <max-request-size>418018841</max-request-size> <file-size-threshold>1048576</file-size-threshold> </multipart-config> </servlet>
此外,還需要在Spring MVC配置文件中使用一個StandardServletMultipartResolver,如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>
upload2應用程序展示了如何在Servlet 3.0以及更改版本的容器中處理文件上傳問題,這是從upload1中改寫過來的,upload2和upload1相似部分不再做詳細介紹。主要的區別在於,現在的web.xml文件中包含了一個multipart-config元素。upload2應用的目錄結構如下:
1、配置文件
下面是upload2應用的部署描述符(web.xml文件):
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <multipart-config> <max-file-size>20848820</max-file-size> <max-request-size>418018841</max-request-size> <file-size-threshold>1048576</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Spring MVC配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="controller" /> <mvc:annotation-driven /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/*.html" location="/" /> <mvc:resources mapping="/image/**" location="/image/" /> <mvc:resources mapping="/file/**" location="/file/" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean> </beans>
2、Produtct類
package domain; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.springframework.web.multipart.MultipartFile; public class Product implements Serializable { private static final long serialVersionUID = 78L; @NotNull @Size(min=1, max=10) private String name; private String description; private BigDecimal price; private List<MultipartFile> images; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public List<MultipartFile> getImages() { return images; } public void setImages(List<MultipartFile> images) { this.images = images; } }
3、ProductController類
package controller; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import domain.Product; @Controller public class ProductController { private static final Log logger = LogFactory.getLog(ProductController.class); //請求URL:/input-product @RequestMapping(value="/input-product") public String inputProduct(Model model) { model.addAttribute("product", new Product()); return "ProductForm"; } //請求URL:/save-product @RequestMapping(value = "/save-product") public String saveProduct(HttpServletRequest servletRequest, @ModelAttribute Product product, BindingResult bindingResult, Model model) { //獲取上傳的圖片文件(可以多個文件) List<MultipartFile> files = product.getImages(); //用於保存所有文件名 List<String> fileNames = new ArrayList<String>(); //檢驗是否有文件? if (null != files && files.size() > 0) { //遍歷 for (MultipartFile multipartFile : files) { //獲取文件名 String fileName = multipartFile.getOriginalFilename(); fileNames.add(fileName); //獲取應用/image虛擬路徑在文件系統上對應的真實路徑 + 文件名 並創建File對象 File imageFile = new File(servletRequest.getServletContext() .getRealPath("/image"), fileName); try { //將上傳的文件保存到目標目錄下 multipartFile.transferTo(imageFile); } catch (IOException e) { e.printStackTrace(); } } } // save product here model.addAttribute("product", product); return "ProductDetails"; } }
4、視圖
ProductForm.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <form:form modelAttribute="product" action="save-product" method="post" enctype="multipart/form-data"> <fieldset> <legend>Add a product</legend> <p> <label for="name">Product Name: </label> <form:input id="name" path="name" cssErrorClass="error"/> <form:errors path="name" cssClass="error"/> </p> <p> <label for="description">Description: </label> <form:input id="description" path="description"/> </p> <p> <label for="price">Price: </label> <form:input id="price" path="price" cssErrorClass="error"/> </p> <p> <label for="image">Product Image: </label> <!-- <input type="file" name="images[0]"/> --> <input type="file" name="images" multiple/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Product"> </p> </fieldset> </form:form> </div> </body> </html>
ProductDetails:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <title>Save Product</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <h4>The product has been saved.</h4> <p> <h5>Details:</h5> Product Name: ${product.name}<br/> Description: ${product.description}<br/> Price: $${product.price} <p>Following files are uploaded successfully.</p> <ol> <c:forEach items="${product.images}" var="image"> <li>${image.originalFilename} <img width="100" src="<c:url value="/image/"/>${image.originalFilename}"/> </li> </c:forEach> </ol> </p> </div> </body> </html>
main.css:

#global { text-align: left; border: 1px solid #dedede; background: #efefef; width: 560px; padding: 20px; margin: 30px auto; } form { font:100% verdana; min-width: 500px; max-width: 600px; width: 560px; } form fieldset { border-color: #bdbebf; border-width: 3px; margin: 0; } legend { font-size: 1.3em; } form label { width: 250px; display: block; float: left; text-align: right; padding: 2px; } #buttons { text-align: right; } #errors, li { color: red; } .error { color: red; font-size: 9pt; }
5、測試
將應用程序部署到tomcat服務器,並在網頁輸入以下URL:
http://localhost:8008/upload2/input-product
將會看到一個如圖所示的Add Product表單,試着輸入一些產品信息,並選擇一個要上傳的文件:
單擊"Add Product"按鈕,就可以看到如下所示的網頁:
六 upload2應用HTML 5進行文件上傳
雖然Servlet 3.0中的文件上傳特性使文件上傳變得十分容器,只需在服務器端編程即可,但是這對提升用戶體驗毫無幫助。單獨一個HTML表單並不能顯示進度條,或者顯示已經成功上傳的文件數量。開發人員采用了各種不同的技術來改善用戶界面,例如,單獨用一個瀏覽器線程對服務器發出請求,以便報告上傳進度,或者利用像Java applets、Adobe Flash、Microsoft Silverlight這樣的第三方技術。
這些第三方技術可以工作,但都在一定程度上存在限制。今天Java applets和Microsoft Silverlight幾乎過時了,Chrome不在允許Java applets和Microsoft Silverlight,Microsoft取代Internet Explorer的新瀏覽器Edge根本不需要插件。
我們仍然可以使用Flash、因為Chrome仍然可以運行它,Edge已經集成了它,然而,現在越來越多的人選擇使用HTML 5。
HTML 5在其DOM中添加了一個File API,它允許訪問本地文件。與Java applets、Adobe Flash、Microsoft Silverlight相比,HTML 5似乎是針對客戶端文件上傳局限性的最佳解決方案。
為了驗證HTML 5的性能,upload2中的html5頁面采用了JavaScript和HTML 5 File API來提供報告上傳進度的進度條。upload2應用程序中也創建了一個UploadedFile 類,用於在服務器中保存已上傳的文件。
1、UploadedFile類
upload2的UploadedFile類只包含一個屬性multipartFile,用來保存已經上傳的文件:
package domain; import java.io.Serializable; import org.springframework.web.multipart.MultipartFile; public class UploadedFile implements Serializable { private static final long serialVersionUID = 1L; //用來保存已經上傳的文件 private MultipartFile multipartFile; public MultipartFile getMultipartFile() { return multipartFile; } public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; } }
2、Html5FileUploadController類
upload2中的Html5FileUploadController類能夠將已經上傳的文件保存到應用程序的file目錄下:
package controller; import java.io.File; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import domain.UploadedFile; @Controller public class Html5FileUploadController { private static final Log logger = LogFactory .getLog(Html5FileUploadController.class); //請求URL:/html5 @RequestMapping(value = "/html5") public String inputProduct() { return "Html5"; } //請求URL:/upload-file @RequestMapping(value = "/upload-file") public void saveFile(HttpServletRequest servletRequest, @ModelAttribute UploadedFile uploadedFile, BindingResult bindingResult, Model model) { //獲取已經上傳的文件 MultipartFile multipartFile = uploadedFile.getMultipartFile(); //獲取上傳的文件名 String fileName = multipartFile.getOriginalFilename(); try { //獲取應用/file虛擬路徑在文件系統上對應的真實路徑 + 文件名 並創建File對象 File file = new File(servletRequest.getServletContext() .getRealPath("/file"), fileName); //將上傳的文件保存到目標目錄下 multipartFile.transferTo(file); } catch (IOException e) { e.printStackTrace(); } } }
3、html5.jsp頁面
<!DOCTYPE html> <html> <head> <script> var totalFileLength, totalUploaded, fileCount, filesUploaded; // show uoload file information use element of id="debug" function debug(s) { var debug = document.getElementById('debug'); if (debug) { debug.innerHTML = debug.innerHTML + '<br/>' + s; } } //load event of XMLHttpRequest object function onUploadComplete(e) { totalUploaded += document.getElementById('files'). files[filesUploaded].size; filesUploaded++; debug('complete ' + filesUploaded + " of " + fileCount); debug('totalUploaded: ' + totalUploaded); if (filesUploaded < fileCount) { uploadNext(); } else { var bar = document.getElementById('bar'); bar.style.width = '100%'; bar.innerHTML = '100% complete'; alert('Finished uploading file(s)'); } } //trigger when selecting file change function onFileSelect(e) { var files = e.target.files; // FileList object var output = []; //get upload file count fileCount = files.length; totalFileLength = 0; for (var i=0; i<fileCount; i++) { var file = files[i]; output.push(file.name, ' (', file.size, ' bytes, ', file.lastModifiedDate.toLocaleDateString(), ') ' ); output.push('<br/>'); debug('add ' + file.name + ' ('+ file.size + 'bytes' + ') ' ); totalFileLength += file.size; } //show selecting file information document.getElementById('selectedFiles').innerHTML = output.join(''); debug('totalFileLength: ' + totalFileLength + 'bytes'); } //progress event of XMLHttpRequest object function onUploadProgress(e) { if (e.lengthComputable) { var percentComplete = parseInt( (e.loaded + totalUploaded) * 100 / totalFileLength); var bar = document.getElementById('bar'); bar.style.width = percentComplete + '%'; bar.innerHTML = percentComplete + ' % complete'; } else { debug('unable to compute'); } } //error event of XMLHttpRequest object function onUploadFailed(e) { alert("Error uploading file"); } //upload next file function uploadNext() { var xhr = new XMLHttpRequest(); var fd = new FormData(); var file = document.getElementById('files'). files[filesUploaded]; fd.append("multipartFile", file); xhr.upload.addEventListener( "progress", onUploadProgress, false); xhr.addEventListener("load", onUploadComplete, false); xhr.addEventListener("error", onUploadFailed, false); xhr.open("POST", "upload-file"); debug('uploading ' + file.name); xhr.send(fd); } //trigger when click Upload button function startUpload() { totalUploaded = filesUploaded = 0; uploadNext(); } //trigger when window load window.onload = function() { document.getElementById('files').addEventListener( 'change', onFileSelect, false); document.getElementById('uploadButton'). addEventListener('click', startUpload, false); } </script> </head> <body> <h1>Multiple file uploads with progress bar</h1> <div id='progressBar' style='height:20px;border:2px solid green'> <div id='bar' style='height:100%;background:#33dd33;width:0%'> </div> </div> <form> <input type="file" id="files" multiple/> <br/> <output id="selectedFiles"></output> <input id="uploadButton" type="button" value="Upload"/> </form> <div id='debug' style='height:300px;border:2px solid green;overflow:auto'> </div> </body> </html>
html5.jsp頁面主要包含以下三部分:
- 一個id為progressBar的div元素:用於展示上傳進度;
- 一個表單:表單中有一個類型為file的input元素和一個按鈕,用於上傳文件;
- 一個id為debug的div元素,用來顯示調試信息,主要包括上傳文件信息;
這個表單有兩點需要注意:
<form> <input type="file" id="files" multiple/> <br/> <output id="selectedFiles"></output> <input id="uploadButton" type="button" value="Upload"/> </form>
- id為files的input元素,它有一個multiple屬性,用於支持多文件選擇;
- 這個按鈕不是一個提交按鈕,因此單擊它不會提交表單,事實上,腳本是利用XMLHttpRequest對象來上傳的;
下面來看JavaScript代碼。執行腳本時,它做的第一件事就是為這4個變量分配空間:
var totalFileLength, totalUploaded, fileCount, filesUploaded;
(1) totalFileLength變量保存要上傳的文件總長度;
(2) totalUploaded是指目前已經上傳的字節數;
(3) fileCount:要上傳的文件數量;
(4) filesUploaded:表示已經上傳的文件數量;
隨后,當html5.jsp頁面完全加載后,便觸發window.onload事件:
window.onload = function() { document.getElementById('files').addEventListener( 'change', onFileSelect, false); document.getElementById('uploadButton'). addEventListener('click', startUpload, false); }
這段代碼將id為files的input元素的change事件映射到onFileSelect()函數,將按鈕的click事件映射到startUpload()函數。
每當用戶從本地目錄中修改了不同的文件時,都會觸發change事件。與該事件相關的事件處理器onFileSelect()函數只是在一個id為selectedFiles的output元素中輸出已選中的文件的名稱和數量:
//trigger when selecting file change function onFileSelect(e) { var files = e.target.files; // FileList object var output = []; //get upload file count fileCount = files.length; totalFileLength = 0; for (var i=0; i<fileCount; i++) { var file = files[i]; output.push(file.name, ' (', file.size, ' bytes, ', file.lastModifiedDate.toLocaleDateString(), ') ' ); output.push('<br/>'); debug('add ' + file.name + ' ('+ file.size + 'bytes' + ') ' ); totalFileLength += file.size; } //show selecting file information document.getElementById('selectedFiles').innerHTML = output.join(''); debug('totalFileLength: ' + totalFileLength + 'bytes'); }
當用戶點擊Upload按鈕時,就會調用startUpload()函數:
//trigger when click Upload button function startUpload() { totalUploaded = filesUploaded = 0; uploadNext(); }
並隨着調用uploadNext()函數,uploadNext()函數上傳已選文件列表中的下一個文件。它首先創建一個XMLHttpRequest對象和一個FormData對象(表單對象),並將接下來通過document.getElementById('files')獲取一個FileList對象,並將要上傳的文件添加到屬性multipartFile上:
var xhr = new XMLHttpRequest(); var fd = new FormData(); var file = document.getElementById('files'). files[filesUploaded]; fd.append("multipartFile", file);
隨后,uploadNext()函數將XMLHttpRequest對象的progress事件綁定添加到onUploadProgress(),並將load事件和error時間分別添加到onUploadComplete()和onUploadFalied:
xhr.upload.addEventListener( "progress", onUploadProgress, false); xhr.addEventListener("load", onUploadComplete, false); xhr.addEventListener("error", onUploadFailed, false);
接下來,打開一個服務器連接,請求/upload-file頁面,並發出FormData:
xhr.open("POST", "upload-file"); debug('uploading ' + file.name); xhr.send(fd);
fd是一個表單對象,該對象的各個屬性會被綁定到/upload-file頁面對應的請求處理方法saveFile()的模型參數uploadedFile的各個屬性上:
public void saveFile(HttpServletRequest servletRequest, @ModelAttribute UploadedFile uploadedFile, BindingResult bindingResult, Model model)
在上傳期間,會重復的調用onUploadProgress()函數,讓它有機會更新進度條。更新包括計算已經上傳的總字節比率,計算已選擇文件的字節數,拓寬progressBar div元素里面的div元素:
//progress event of XMLHttpRequest object function onUploadProgress(e) { if (e.lengthComputable) { var percentComplete = parseInt( (e.loaded + totalUploaded) * 100 / totalFileLength); var bar = document.getElementById('bar'); bar.style.width = percentComplete + '%'; bar.innerHTML = percentComplete + ' % complete'; } else { debug('unable to compute'); } }
上傳完成時,調用onUploadComplete()函數。這個事件處理器會添加totalUploaded,即已經完成上傳的文件容量,並添加filesUploaded值。隨后,它會查看已經選中的所有文件是否都已經上傳,如果是,則會顯示一條消息,告訴用戶文件上傳已經成功完成,如果不是,則再次調用uploadNext():
//load event of XMLHttpRequest object function onUploadComplete(e) { totalUploaded += document.getElementById('files'). files[filesUploaded].size; filesUploaded++; debug('complete ' + filesUploaded + " of " + fileCount); debug('totalUploaded: ' + totalUploaded); if (filesUploaded < fileCount) { uploadNext(); } else { var bar = document.getElementById('bar'); bar.style.width = '100%'; bar.innerHTML = '100% complete'; alert('Finished uploading file(s)'); } }
如果上傳失敗,則會調用onUploadFailed()函數,並且顯示一條消息:
//error event of XMLHttpRequest object function onUploadFailed(e) { alert("Error uploading file"); }
4、測試
在瀏覽器中中輸入如下URL:
http://localhost:8008/upload2/html5
選中幾個文件,並單擊Upload按鈕,將會看到一個進度條、以及文件上傳的信息,如下:
注意:如果有中文,則有必要將整個應用的字符編碼設置為UTF-8。
參考文章
[2]Spring MVC學習指南
[3]FormData對象