從Spring官網上能夠看到MultipartResolver接口的定義信息:html
public interface MultipartResolver
There are two concrete implementations included in Spring, as of Spring 3.1:前端
CommonsMultipartResolver
for Apache Commons FileUploadStandardServletMultipartResolver
for the Servlet 3.0+ Part APIThere is no default resolver implementation used for Spring DispatcherServlets
, as an application might choose to parse its multipart requests itself. To define an implementation, create a bean with the id "multipartResolver" in a DispatcherServlet's
application context. Such a resolver gets applied to all requests handled by that DispatcherServlet
.java
If a DispatcherServlet
detects a multipart request, it will resolve it via the configured MultipartResolver
and pass on a wrapped HttpServletRequest
. Controllers can then cast their given request to the MultipartHttpServletRequest
interface, which allows for access to any MultipartFiles
. Note that this cast is only supported in case of an actual multipart request.c++
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; MultipartFile multipartFile = multipartRequest.getFile("image"); ... }
Instead of direct access, command or form controllers can register a ByteArrayMultipartFileEditor
or StringMultipartFileEditor
with their data binder, to automatically apply multipart content to form bean properties.web
As an alternative to using a MultipartResolver
with a DispatcherServlet
, a MultipartFilter
can be registered in web.xml
. It will delegate to a corresponding MultipartResolver
bean in the root application context. This is mainly intended for applications that do not use Spring's own web MVC framework.算法
Note: There is hardly ever a need to access the MultipartResolver
itself from application code. It will simply do its work behind the scenes, making MultipartHttpServletRequests
available to controllers.spring
關於MultipartResolver的接口文檔,請參考Spring5.2.x的官方文檔《https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/MultipartResolver.html》apache
public interface MultipartResolver { // 判斷request是否爲文件上傳請求 boolean isMultipart(HttpServletRequest request); // 將HttpServletRequest請求轉化爲MultipartHttpServletRequest MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; // 清空:入參是MultipartHttpServletRequest void cleanupMultipart(MultipartHttpServletRequest request); }
當用戶的請求到DispatcherServlet時,json
1)DispatcherServlet會先在webapplicationContext.xml中找一個名爲「multipartResolver」的bean.後端
2)若是有,則調用MultipartResolver的第一個方法(isMultipart(...)),該方法會返回這個請求是不是經過enctype=」multipart/form-data」方式提交的,若是是,則調用第二個方法(resolveMultipart(...))的,這個方法會把當前的HttpServletRequest換成MultipartHttpServletRequest,並傳給後面的Controller處理
3)若是沒有這個bean(也能夠修改DispatcherServlet的 MULTIPART_RESOLVER_BEAN_NAME屬性來修改查找的名字),或者第一個方法沒有返回true,則不作處理,繼續傳遞HttpServletRequest。
MultipartResolver是一個爲多文件上傳提供瞭解決方案的策略接口,將普通的HttpServletRequest封裝成MultipartHttpServletRequest,在Spring中常見的兩個實現方式,分別是:
1)StandardServletMultipartResolver:使用Servlet3.0標準上傳方式,將HttpServletRequest轉化爲StandardServletMultipartResolver,以tomcat爲例,從 Tomcat 7.0.x的版本開始就支持 Servlet 3.0了。
2)CommonsMultipartResolver:使用apache的common-fileupload,將HttpServletRequest轉化爲DefaultMultipartHttpServletRequest(須要依賴common-fileupload.jar,common-io.jar)。
在SpringMVC中MultipartResolver組件沒有提供默認值,實際上若是上傳文件不使用MultipartResovler組件封裝成MultipartHttpServletRequest,直接用原生HttpServletRequest request也是能夠的。
StandardServletMultipartResolver實現了MultipartResolver接口,resolveMultipart(HttpServletRequest request)方法中resolveLazily是判斷是否要延遲解析文件(經過XML能夠設置)
package org.springframework.web.multipart.support; public class StandardServletMultipartResolver implements MultipartResolver { // 是否當即解析 private boolean resolveLazily = false; public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } // 是否上傳文件 @Override public boolean isMultipart(HttpServletRequest request) { return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); } // 將HttpServletRequest解析爲MultipartHttpServlet(StandardMultipartHttpServletRequest) @Override public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); } // 清理 @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { // To be on the safe side: explicitly delete the parts, // but only actual file parts (for Resin compatibility) try { for (Part part : request.getParts()) { if (request.getFile(part.getName()) != null) { part.delete(); } } } catch (Throwable ex) { LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex); } } } }
1)resolveMultipart(HttpServletRequest request)方法:是對請求數據進行解析,並返回解析後的包裝類StandardMultipartHttpServletRequest對象,其中對request請求數據解析是在StandardMultipartHttpServletRequest的parseRequest(request)方法中執行:
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request);
// 若是不是懶解析,則當即解析 if (!lazyParsing) { parseRequest(request); } } // 對request請求數據進行解析 private void parseRequest(HttpServletRequest request) { try { Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); for (Part part : parts) { String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); ContentDisposition disposition = ContentDisposition.parse(headerValue); String filename = disposition.getFilename(); if (filename != null) { if (filename.startsWith("=?") && filename.endsWith("?=")) { filename = MimeDelegate.decode(filename); } files.add(part.getName(), new StandardMultipartFile(part, filename)); } else { this.multipartParameterNames.add(part.getName()); } } setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } }
2)StandardMultipartHttpServletRequest#parseRequest(HttpServletRequest request) 方法利用了 servlet3.0 的 request.getParts() 方法獲取上傳文件,並將其封裝到 MultipartFile 對象中。
3)該組件的一些配置信息能夠在web.xml的<servlet>標籤中配置:
<!-- StandardServletMultipartResolver 屬性配置 --> <multipart-config> <!--上傳到/tmp/upload 目錄--> <location>/tmp/upload</location> <!--文件大小爲2M--> <max-file-size>2097152</max-file-size> <!--整個請求不超過4M--> <max-request-size>4194304</max-request-size> <!--全部文件都要寫入磁盤--> <file-size-threshold>0</file-size-threshold> </multipart-config>
CommonsMultipartResolver實現了MultipartResolver接口,resolveMultipart(HttpServletRequest request)方法中resolveLazily是判斷是否要延遲解析文件(經過XML能夠設置)
package org.springframework.web.multipart.commons; public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { // 是否懶解析 private boolean resolveLazily = false; // public CommonsMultipartResolver() { super(); } // 構造函數 public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); } // 設置是否懶解析 public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } // 使用common-fileupload.jar進行文件 @Override protected FileUpload newFileUpload(FileItemFactory fileItemFactory) { return new ServletFileUpload(fileItemFactory); } // 設置ServletContext @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()); } } // 解析請求 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); } } // 文件編碼格式,能夠經過xml設置 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); } } } }
1)resolveMultipart(HttpServletRequest request)方法是對請求數據進行解析工做的方法:
1.1)當resolveLazily爲false時,會當即調用parseRequest(HttpServletRequest request)方法,對請求數據進行解析,而後將解析結果封裝到DefaultMultipartHttpServletRequest中;
1.2)當resolveLazily爲true時,會在DefaultMultipartHttpServletRequest的initializeMultipart()方法調用parseRequest()方法對請求數據進行解析,而initializeMultipart()方法有時被getMultipartFiles()方法調用,即當須要獲取文件信息時,纔會去解析請求數據,這種方式採用了懶加載的思想。
2)在CommonsMultipartResolver#parseRequest()方法中,首先調用了prepareFileUpload()方法來根據編碼類型肯定一個FIleUpload實例,而後利用這個FileUpload實例解析請求數據後獲得文件信息,而後將文件信息解析成CommonsMultipartFile(實現了MultipartFile接口)幷包裝到MultipartParsingResut對象中。
/** * 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); } }
3)resovleLazily屬性能夠經過xml配置;
4)determinedEncoding(HttpServletRequest request)方法中的=defaultEncoding能夠經過xml配置。
5)另外maxUploadSize屬性也能夠經過xml配置。
文件上傳處理過程當中國使用的是MultipartHttpRequestServlet,它繼承自接口MultipartRequest。
SpringMvc提供了兩種對MultipartRequest接口的實現類:StandardMultipartHttpServletRequest
和DefaultMultipartHttpServletRequest,它們都繼承自AbstractMultipartHttpServletRequest,MultipartHttpServletRequest接口定義的方法基本都在AbstractMultipartHttpServletRequest中實現。
MultipartRequest接口:
package org.springframework.web.multipart; public interface MultipartRequest {
// 返回表單參數名稱(而文件原名)列表 Iterator<String> getFileNames(); // 根據表單文件參數名稱返回MultipartFile對象 @Nullable MultipartFile getFile(String name); // 根據表單參數文件名稱返回MultipartFile列表對象 List<MultipartFile> getFiles(String name); // 獲取{文件參數名稱:MultipartFile對象}集合 Map<String, MultipartFile> getFileMap(); // 獲取{文件參數名稱:MultipartFile對象}集合 MultiValueMap<String, MultipartFile> getMultiFileMap(); // 根據參數或文件名稱獲取內容類型,不存在時,返回null @Nullable String getMultipartContentType(String paramOrFileName); }
MultipartHttpServleRequest接口:
package org.springframework.web.multipart; public interface MultipartHttpServletRequest extends HttpServletRequest, MultipartRequest { // 返回請參數類型實例 @Nullable HttpMethod getRequestMethod(); // 返回請求的HttpHeaders實例 HttpHeaders getRequestHeaders(); // 返回包含contentType的HttpHeaders實例 @Nullable HttpHeaders getMultipartHeaders(String paramOrFileName); }
AbstractMultipartHttpServletRequest接口:
package org.springframework.web.multipart.support; public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest { //注意: MultiValueMap 等價於 Map<String ,List<MultipartFile>> @Nullable private MultiValueMap<String, MultipartFile> multipartFiles; // 包裝給定的 HttpServletRequest 爲MultipartHttpServletRequest protected AbstractMultipartHttpServletRequest(HttpServletRequest request) { super(request); } // 返回前原始HttpServletRequest對象 @Override public HttpServletRequest getRequest() { return (HttpServletRequest) super.getRequest(); } // 返回請求類型實例 @Override public HttpMethod getRequestMethod() { return HttpMethod.resolve(getRequest().getMethod()); } // 返回請求中header中全部信息 @Override public HttpHeaders getRequestHeaders() { HttpHeaders headers = new HttpHeaders(); Enumeration<String> headerNames = getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, Collections.list(getHeaders(headerName))); } return headers; } // 上傳時:file文件對應form表單的key(不是file自身的屬性name) @Override public Iterator<String> getFileNames() { return getMultipartFiles().keySet().iterator(); } // 因爲可能上傳多個文件, 這裏範湖first @Override public MultipartFile getFile(String name) { return getMultipartFiles().getFirst(name); } // 根據form表單key, 獲取文件列表 @Override public List<MultipartFile> getFiles(String name) { List<MultipartFile> multipartFiles = getMultipartFiles().get(name); if (multipartFiles != null) { return multipartFiles; } else { return Collections.emptyList(); } } // 上傳時,返回key Vs MultipartFile對象 @Override public Map<String, MultipartFile> getFileMap() { return getMultipartFiles().toSingleValueMap(); } // 上傳時,返回key Vs MultipartFile @Override public MultiValueMap<String, MultipartFile> getMultiFileMap() { return getMultipartFiles(); } // 是否懶處理 public boolean isResolved() { return (this.multipartFiles != null); } // 初始化時,set該屬性 protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles)); } protected MultiValueMap<String, MultipartFile> getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } return this.multipartFiles; } protected void initializeMultipart() { throw new IllegalStateException("Multipart request not initialized"); } }
package org.springframework.web.multipart.support; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; /** * Default implementation of the * {@link org.springframework.web.multipart.MultipartHttpServletRequest} * interface. Provides management of pre-generated parameter values. * * <p>Used by {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}. * * @author Trevor D. Cook * @author Juergen Hoeller * @author Arjen Poutsma * @since 29.09.2003 * @see org.springframework.web.multipart.MultipartResolver */ public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { private static final String CONTENT_TYPE = "Content-Type"; @Nullable private Map<String, String[]> multipartParameters; @Nullable private Map<String, String> multipartParameterContentTypes; /** * Wrap the given HttpServletRequest in a MultipartHttpServletRequest. * @param request the servlet request to wrap * @param mpFiles a map of the multipart files * @param mpParams a map of the parameters to expose, * with Strings as keys and String arrays as values */ public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) { super(request); setMultipartFiles(mpFiles); setMultipartParameters(mpParams); setMultipartParameterContentTypes(mpParamContentTypes); } /** * Wrap the given HttpServletRequest in a MultipartHttpServletRequest. * @param request the servlet request to wrap */ public DefaultMultipartHttpServletRequest(HttpServletRequest request) { super(request); } @Override @Nullable public String getParameter(String name) { String[] values = getMultipartParameters().get(name); if (values != null) { return (values.length > 0 ? values[0] : null); } return super.getParameter(name); } @Override public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); String[] mpValues = getMultipartParameters().get(name); if (mpValues == null) { return parameterValues; } if (parameterValues == null || getQueryString() == null) { return mpValues; } else { String[] result = new String[mpValues.length + parameterValues.length]; System.arraycopy(mpValues, 0, result, 0, mpValues.length); System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length); return result; } } @Override public Enumeration<String> getParameterNames() { Map<String, String[]> multipartParameters = getMultipartParameters(); if (multipartParameters.isEmpty()) { return super.getParameterNames(); } Set<String> paramNames = new LinkedHashSet<>(); paramNames.addAll(Collections.list(super.getParameterNames())); paramNames.addAll(multipartParameters.keySet()); return Collections.enumeration(paramNames); } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> result = new LinkedHashMap<>(); Enumeration<String> names = getParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); result.put(name, getParameterValues(name)); } return result; } @Override public String getMultipartContentType(String paramOrFileName) { MultipartFile file = getFile(paramOrFileName); if (file != null) { return file.getContentType(); } else { return getMultipartParameterContentTypes().get(paramOrFileName); } } @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { String contentType = getMultipartContentType(paramOrFileName); if (contentType != null) { HttpHeaders headers = new HttpHeaders(); headers.add(CONTENT_TYPE, contentType); return headers; } else { return null; } } /** * Set a Map with parameter names as keys and String array objects as values. * To be invoked by subclasses on initialization. */ protected final void setMultipartParameters(Map<String, String[]> multipartParameters) { this.multipartParameters = multipartParameters; } /** * Obtain the multipart parameter Map for retrieval, * lazily initializing it if necessary. * @see #initializeMultipart() */ protected Map<String, String[]> getMultipartParameters() { if (this.multipartParameters == null) { initializeMultipart(); } return this.multipartParameters; } /** * Set a Map with parameter names as keys and content type Strings as values. * To be invoked by subclasses on initialization. */ protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) { this.multipartParameterContentTypes = multipartParameterContentTypes; } /** * Obtain the multipart parameter content type Map for retrieval, * lazily initializing it if necessary. * @see #initializeMultipart() */ protected Map<String, String> getMultipartParameterContentTypes() { if (this.multipartParameterContentTypes == null) { initializeMultipart(); } return this.multipartParameterContentTypes; } }
該類包含了幾個重要屬性:
1)集成基類AbstractMultipartHttpServletRequest的multipartFiles屬性:用來存儲上傳文件的集合;
2)multipartParameterNames屬性:用來存儲表單中key,value的集合;
3)multipartParameterContentTypes屬性:表單key,contentType的集合;
4)request屬性:來自MultipartHttpServletRequest基類HttpServletRequest屬性request。
package org.springframework.web.multipart.support; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.mail.internet.MimeUtility; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; /** * Spring MultipartHttpServletRequest adapter, wrapping a Servlet 3.0 HttpServletRequest * and its Part objects. Parameters get exposed through the native request's getParameter * methods - without any custom processing on our side. * * @author Juergen Hoeller * @author Rossen Stoyanchev * @since 3.1 * @see StandardServletMultipartResolver */ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { @Nullable private Set<String> multipartParameterNames; /** * Create a new StandardMultipartHttpServletRequest wrapper for the given request, * immediately parsing the multipart content. * @param request the servlet request to wrap * @throws MultipartException if parsing failed */ public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException { this(request, false); } /** * Create a new StandardMultipartHttpServletRequest wrapper for the given request. * @param request the servlet request to wrap * @param lazyParsing whether multipart parsing should be triggered lazily on * first access of multipart files or parameters * @throws MultipartException if an immediate parsing attempt failed * @since 3.2.9 */ public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); if (!lazyParsing) { parseRequest(request); } } private void parseRequest(HttpServletRequest request) { try { Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); for (Part part : parts) { String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); ContentDisposition disposition = ContentDisposition.parse(headerValue); String filename = disposition.getFilename(); if (filename != null) { if (filename.startsWith("=?") && filename.endsWith("?=")) { filename = MimeDelegate.decode(filename); } files.add(part.getName(), new StandardMultipartFile(part, filename)); } else { this.multipartParameterNames.add(part.getName()); } } setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } } protected void handleParseFailure(Throwable ex) { String msg = ex.getMessage(); if (msg != null && msg.contains("size") && msg.contains("exceed")) { throw new MaxUploadSizeExceededException(-1, ex); } throw new MultipartException("Failed to parse multipart servlet request", ex); } @Override protected void initializeMultipart() { parseRequest(getRequest()); } @Override public Enumeration<String> getParameterNames() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterNames(); } // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Set<String> paramNames = new LinkedHashSet<>(); Enumeration<String> paramEnum = super.getParameterNames(); while (paramEnum.hasMoreElements()) { paramNames.add(paramEnum.nextElement()); } paramNames.addAll(this.multipartParameterNames); return Collections.enumeration(paramNames); } @Override public Map<String, String[]> getParameterMap() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterMap(); } // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Map<String, String[]> paramMap = new LinkedHashMap<>(super.getParameterMap()); for (String paramName : this.multipartParameterNames) { if (!paramMap.containsKey(paramName)) { paramMap.put(paramName, getParameterValues(paramName)); } } return paramMap; } @Override public String getMultipartContentType(String paramOrFileName) { try { Part part = getPart(paramOrFileName); return (part != null ? part.getContentType() : null); } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } } @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { try { Part part = getPart(paramOrFileName); if (part != null) { HttpHeaders headers = new HttpHeaders(); for (String headerName : part.getHeaderNames()) { headers.put(headerName, new ArrayList<>(part.getHeaders(headerName))); } return headers; } else { return null; } } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } } /** * Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object. */ @SuppressWarnings("serial") private static class StandardMultipartFile implements MultipartFile, Serializable { private final Part part; private final String filename; public StandardMultipartFile(Part part, String filename) { this.part = part; this.filename = filename; } @Override public String getName() { return this.part.getName(); } @Override public String getOriginalFilename() { return this.filename; } @Override public String getContentType() { return this.part.getContentType(); } @Override public boolean isEmpty() { return (this.part.getSize() == 0); } @Override public long getSize() { return this.part.getSize(); } @Override public byte[] getBytes() throws IOException { return FileCopyUtils.copyToByteArray(this.part.getInputStream()); } @Override public InputStream getInputStream() throws IOException { return this.part.getInputStream(); } @Override public void transferTo(File dest) throws IOException, IllegalStateException { this.part.write(dest.getPath()); if (dest.isAbsolute() && !dest.exists()) { // Servlet 3.0 Part.write is not guaranteed to support absolute file paths: // may translate the given path to a relative location within a temp dir // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths). // At least we offloaded the file from memory storage; it'll get deleted // from the temp dir eventually in any case. And for our user's purposes, // we can manually copy it to the requested location as a fallback. FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath())); } } @Override public void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest)); } } /** * Inner class to avoid a hard dependency on the JavaMail API. */ private static class MimeDelegate { public static String decode(String value) { try { return MimeUtility.decodeText(value); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } } }
該類包含了幾個重要屬性:
1)集成基類AbstractMultipartHttpServletRequest的multipartFiles屬性:用來存儲上傳文件的集合;
2)multipartParameterNames屬性:用來存儲表單中key,value的集合;
3)request屬性:來自MultipartHttpServletRequest基類HttpServletRequest屬性request。
在SpringMvc中默認並未給ResolveMultipart實現,默認爲null,此時文件上傳使用HttpServletRequest攜帶上傳文件到服務端。另外就是能夠經過ResovlerMultipart組件實現文件上傳。ResolverMultipart組件實現上傳包含兩種實現:StandardServletMultipartResolver/CommonsMultipartResolver。
須要注意事項:
1)不配置ResovleMultipart方案(採用HttpServletRequest進行傳遞提交數據),後端接口中請求對象必須是HttpServletRequest;
2)配置ResolveMultipart爲StandardServletMultipartResolver方案,後端接口中定義請求對象可使MultipartFile、MultipartFile[]、HttpServletRequest、MultipartHttpServletRequest、StandardServletMultipartResolver。可是幾種用法有區別:
2.1)MultipartFile是用來接收單個文件上傳使用;多個<input type="file" ...>時,接收第一個<input type="file" ...>文件;
好比:
2.1.1)多標籤
<input type="file" name="file1"/> <input type="file" name="file2"/>
此時,接收第一個標籤:name="file1"的上傳文件。
2.1.2)一組標籤
上傳文件1:<input type="file" id="file1" name="file1"/> 上傳文件2:<input type="file" id="file2" name="file1"/> 上傳文件3:<input type="file" id="file3" name="file2"/>
此時只接收第一組標籤的第一個標籤:<input type="file" id="file1" name="file1" />的這一個上傳文件。
2.2)MultipartFile[]接收第一組標籤
2.2.1)多標籤
<input type="file" name="file1"/> <input type="file" name="file2"/>
此時,接收第一個標籤:name="file1"的上傳文件。
2.1.2)一組標籤
上傳文件1:<input type="file" id="file1" name="file1"/> 上傳文件2:<input type="file" id="file2" name="file1"/> 上傳文件3:<input type="file" id="file3" name="file2"/>
此時只接收第一組標籤:<input type="file" id="file1" name="file1" />和<input type="file" id="file2" name="file1" />的這一個上傳文件。
2.3)HttpServletRequest、MultipartHttpServletRequest、StandardServletMultipartResolver接收
其實上邊這幾種都是StandardServletMultipartResolver類型。
3)配置ResolveMultipart爲CommonsMultipartResolver方案,後端接口中定義請求對象可使MultipartFile、MultipartFile[]、HttpServletRequest、MultipartHttpServletRequest、CommonsMultipartResolver。具體區別與上邊‘StandardServletMultipartResolver方案’大體一致。
下邊就對這幾種實現上傳文件的方案進行講解如何使用。
/WEB-INF/applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--掃描全部的 spring包下的文件;--> <!--固然須要在spring配置文件裏面配置一下自動掃描範圍 <context:component-scan base-package="*"/> *表明你想要掃描的那些包的目錄所在位置。Spring 在容器初始化時將自動掃描 base-package 指定的包及其子包下的全部的.class文件, 全部標註了 @Repository 的類都將被註冊爲 Spring Bean。 --> <context:component-scan base-package="com.dx.test"/> <!--新增長的兩個配置,這個是解決406問題的關鍵--> <!--mvc註解驅動(可代替註解適配器與註解映射器的配置),默認加載不少參數綁定方法(實際開發時使用)--> <context:annotation-config/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> </bean> <!--本身後加的,該BeanPostProcessor將自動對標註@Autowired的bean進行注入--> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <!--<ref bean="stringHttpMessageConverter"/>--> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/> <!-- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> --> </list> </property> </bean> </beans>
web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <multipart-config> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <max-file-size>2097152</max-file-size> <!--整個請求不超過4M--> <max-request-size>4194304</max-request-size> <!--全部文件都要寫入磁盤--> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
此時上傳使用的是HttpServletRequest,所以須要:
1)在web.xml的<servlet>下配置<multipart-config>配置項;
2)在pom.xml引入servlet依賴。
<!-- servlet相關 <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency>
測試表單:
<h2>Post包含上傳文件提交:</h2> <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data"> Id:<form:hidden path="id"/> Title: <form:input path="title" style="width:200px;"/><br/> Content: <form:input path="content" style="width:200px;"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form:form>
後臺接口:
@RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException { System.out.println(article); Collection<Part> parts = request.getParts(); for (Part part : parts) { System.out.println(part.getName() + "->"+part.getContentType()); } String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
斷點查看parts變量屬性以下:
後臺打印信息以下:
[DEBUG] POST "/article/update_with_post_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, HttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}
id->null
title->null
content->null
files->application/octet-stream
files->application/octet-stream
files->image/svg+xml
execelFile->application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1,算法與數據結構--綜合提高篇(c++版),文章內容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK
此時,只須要基於上邊的實現方案稍做調整便可:
1)put表單:
<h2>Put包含上傳文件提交:</h2> <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data"> <input type="hidden" name="_method" value="PUT"/> Id:<input name="id" id="id" value="${article.id}"/><br/> Title:<input name="title" id="title" value="${article.title}"/><br/> Content:<input name="content" id="content" value="${article.content}"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form>
2)web.xml引入hiddenHttpFilter,applicationContext.xml不作任何調整:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!-- 全局初始化數據,spring的監聽器讀取此配置文件,多個配置文件用分號分隔 若是在web.xml中不寫任何參數配置信息,默認的路徑是/WEB-INF/applicationContext.xml,在WEB-INF目錄下建立的xml文件的名稱必須是applicationContext.xml; 若是是要自定義文件名能夠在web.xml里加入contextConfigLocation這個context參數: --> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> --> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器能夠處理http請求中的文件,被該過濾器過濾後會用post方法提交,form表單需設爲enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器以前 --> <!--spring中配置的id爲multipartResolver的解析器--> <!-- <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name>--> <!--<url-pattern>/*</url-pattern>--> <!--<servlet-name>myAppServletName</servlet-name>--> <!--<url-pattern>/*</url-pattern> </filter-mapping>--> <!-- 注意:HiddenHttpMethodFilter必須做用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 做用:能夠過濾全部請求,並能夠分爲四種 使用該過濾器須要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是否是put 或者 delete,若是不是 則默認post請求 --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <multipart-config> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <max-file-size>2097152</max-file-size> <!--整個請求不超過4M--> <max-request-size>4194304</max-request-size> <!--全部文件都要寫入磁盤--> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
3)後端接口實現:
@RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException { System.out.println(article); Collection<Part> parts=request.getParts(); for(Part part:parts){ System.out.println(part.getName()+"->"+part.getContentType()); } String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
此時測試後端打印信息以下:
[DEBUG] PUT "/article/update_with_put_file", parameters={masked} [DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, HttpServletRequest) ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'} _method->null id->null title->null content->null files->application/octet-stream files->application/octet-stream files->image/svg+xml execelFile->application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 1,算法與數據結構--綜合提高篇(c++版),文章內容 [DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors} [DEBUG] Forwarding to [/WEB-INF/views/index.jsp] [DEBUG] Completed 200 OK
注意:此時在web.xml中用不用MultipartFilter都行,使用了也能正常運行:
<filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping>
1)在web.xml中不使用MultipartFilter時,DispatcherServlet會直接讀取web.xml中<servlet><init-param>下的applicationContext.xml中multipartResolver Bean;
2)在web.xml中 使用MultipartFilter時,會先走Tomcat doFilter->執行org.springframework.web.filter.OncePerRequestFilter.doFilter()
以後會執行MultipartFilter#doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { MultipartResolver multipartResolver = lookupMultipartResolver(request); HttpServletRequest processedRequest = request; if (multipartResolver.isMultipart(processedRequest)) { if (logger.isTraceEnabled()) { logger.trace("Resolving multipart request"); } //若是當前上傳文件,則使用multipartResolver#resolveMultipart(request)對request進行解析: //1)若是multipartResovler是StandardServletMultipartResolver,則執行函數會將request解析爲:StandardMultipartHttpServletRequest //2)若是multipartResovler是CommonsMultipartResolver,則執行函數會將request解析爲:DefaultMultipartHttpServletRequest processedRequest = multipartResolver.resolveMultipart(processedRequest); } else { // A regular request... if (logger.isTraceEnabled()) { logger.trace("Not a multipart request"); } } try { filterChain.doFilter(processedRequest, response); } finally { if (processedRequest instanceof MultipartHttpServletRequest) { multipartResolver.cleanupMultipart((MultipartHttpServletRequest) processedRequest); } } }
1)web.xml須要配置在<servlet>下配置上傳配置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!-- 全局初始化數據,spring的監聽器讀取此配置文件,多個配置文件用分號分隔 若是在web.xml中不寫任何參數配置信息,默認的路徑是/WEB-INF/applicationContext.xml,在WEB-INF目錄下建立的xml文件的名稱必須是applicationContext.xml; 若是是要自定義文件名能夠在web.xml里加入contextConfigLocation這個context參數: --> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> --> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器能夠處理http請求中的文件,被該過濾器過濾後會用post方法提交,form表單需設爲enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器以前 --> <!--spring中配置的id爲multipartResolver的解析器--> <!-- <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name>--> <!--<url-pattern>/*</url-pattern>--> <!--<servlet-name>myAppServletName</servlet-name>--> <!--<url-pattern>/*</url-pattern> </filter-mapping>--> <!-- 注意:HiddenHttpMethodFilter必須做用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 做用:能夠過濾全部請求,並能夠分爲四種 使用該過濾器須要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是否是put 或者 delete,若是不是 則默認post請求 --> <!-- <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping> --> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <multipart-config> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <max-file-size>2097152</max-file-size> <!--整個請求不超過4M--> <max-request-size>4194304</max-request-size> <!--全部文件都要寫入磁盤--> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2)applicationContext.xml中要配置resoveMultipart爲StandardServletMultipartResolver
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--掃描全部的 spring包下的文件;--> <!--固然須要在spring配置文件裏面配置一下自動掃描範圍 <context:component-scan base-package="*"/> *表明你想要掃描的那些包的目錄所在位置。Spring 在容器初始化時將自動掃描 base-package 指定的包及其子包下的全部的.class文件, 全部標註了 @Repository 的類都將被註冊爲 Spring Bean。 --> <context:component-scan base-package="com.dx.test"/> <!--新增長的兩個配置,這個是解決406問題的關鍵--> <!--mvc註解驅動(可代替註解適配器與註解映射器的配置),默認加載不少參數綁定方法(實際開發時使用)--> <context:annotation-config/> <mvc:annotation-driven/> <!-- <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.dx.test.interceptors.LogInterceptor"/> </mvc:interceptor> </mvc:interceptors> --> <!--end--> <!-- <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> </bean> <!-- 配置文件上傳解析器 enctype="multipart/form-data" --> <!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">--> <!-- 設定默認編碼 --> <!--<property name="defaultEncoding" value="UTF-8" />--> <!-- 設定文件上傳的最大值爲5MB,5*1024*1024 --> <!--<property name="maxUploadSize" value="5242880" />--> <!-- 設定文件上傳時寫入內存的最大值,若是小於這個參數不會生成臨時文件,默認爲10240,40*1024 --> <!--<property name="maxInMemorySize" value="40960" />--> <!-- 上傳文件的臨時路徑 fileUpload/temp--> <!--<property name="uploadTempDir" value="/" />--> <!-- 延遲文件解析 --> <!--<property name="resolveLazily" value="true"/>--> <!--</bean>--> <!-- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <property name="maxUploadSize" value="5242880" /> <property name="maxInMemorySize" value="40960" /> <property name="uploadTempDir" value="fileUpload/temp" /> <property name="resolveLazily" value="true"/> </bean> --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /> <!--本身後加的,該BeanPostProcessor將自動對標註@Autowired的bean進行注入--> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <!--<ref bean="stringHttpMessageConverter"/>--> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/> <!-- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> --> </list> </property> </bean> </beans>
3)提交頁面爲:
<h2>Post包含上傳文件提交:</h2> <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data"> Id:<form:hidden path="id"/> Title: <form:input path="title" style="width:200px;"/><br/> Content: <form:input path="content" style="width:200px;"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form:form>
4)後端接口
@RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article,/*HttpServletRequest request*//*MultipartHttpServletRequest request*/StandardMultipartHttpServletRequest request) throws IOException, ServletException { System.out.println(article); MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap(); System.out.println("「"); for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) { StringBuilder builder = new StringBuilder(); part.getValue().forEach(s -> { builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],"); }); builder.delete(builder.length() - 1, builder.length()); System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}"); } System.out.println("」"); String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
提交打印日誌:
[DEBUG] POST "/article/update_with_post_file", parameters={masked} [DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, StandardMultipartHttpServletRequest) ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'} 「 files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,test.svg,test.svg]} execelFile->{execelFile,[execelFile,數據接口.xlsx,數據接口.xlsx]} 」 1,算法與數據結構--綜合提高篇(c++版),文章內容 [DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors} [DEBUG] Forwarding to [/WEB-INF/views/index.jsp] [DEBUG] Completed 200 OK
基於post方式,put方式只須要修改三處:
1)applicationContext.xml不須要修改,web.xml引入hiddenHttpMethodFilter
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!-- 全局初始化數據,spring的監聽器讀取此配置文件,多個配置文件用分號分隔 若是在web.xml中不寫任何參數配置信息,默認的路徑是/WEB-INF/applicationContext.xml,在WEB-INF目錄下建立的xml文件的名稱必須是applicationContext.xml; 若是是要自定義文件名能夠在web.xml里加入contextConfigLocation這個context參數: --> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> --> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器能夠處理http請求中的文件,被該過濾器過濾後會用post方法提交,form表單需設爲enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器以前 --> <!--spring中配置的id爲multipartResolver的解析器--> <!-- <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name>--> <!--<url-pattern>/*</url-pattern>--> <!--<servlet-name>myAppServletName</servlet-name>--> <!--<url-pattern>/*</url-pattern> </filter-mapping>--> <!-- 注意:HiddenHttpMethodFilter必須做用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 做用:能夠過濾全部請求,並能夠分爲四種 使用該過濾器須要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是否是put 或者 delete,若是不是 則默認post請求 --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <multipart-config> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <max-file-size>2097152</max-file-size> <!--整個請求不超過4M--> <max-request-size>4194304</max-request-size> <!--全部文件都要寫入磁盤--> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2)提交頁面中引入<input type="hidden" name="_method" value="put"/>
<h2>Put包含上傳文件提交:</h2> <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data"> <input type="hidden" name="_method" value="PUT"/> Id:<input name="id" id="id" value="${article.id}"/><br/> Title:<input name="title" id="title" value="${article.title}"/><br/> Content:<input name="content" id="content" value="${article.content}"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form>
3)後端接口須要以put方式接收
@RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, /*HttpServletRequest request*//*MultipartHttpServletRequest request*/StandardMultipartHttpServletRequest request) throws IOException, ServletException { System.out.println(article); MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap(); System.out.println("「"); for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) { StringBuilder builder = new StringBuilder(); part.getValue().forEach(s -> { builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],"); }); builder.delete(builder.length() - 1, builder.length()); System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}"); } System.out.println("」"); String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
此過後臺打印信息:
[DEBUG] PUT "/article/update_with_put_file", parameters={masked} [DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, StandardMultipartHttpServletRequest) ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'} 「 files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,SpringBoot-Converter,SpringBoot-Converter]} execelFile->{execelFile,[execelFile,數據接口.xlsx,數據接口.xlsx]} 」 1,算法與數據結構--綜合提高篇(c++版),文章內容 [DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors} [DEBUG] Forwarding to [/WEB-INF/views/index.jsp] [DEBUG] Completed 200 OK
因該方案內部採用common-uploadfile,所以須要在pom.xml中引入依賴:
<!--form 設置爲enctype="multipart/form-data",多文件上傳,在applicationContext.xml中配置了bean Commons multipartResolver時,須要依賴該包。--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency>
1)/WEB-INF/applicationContext.xml須要引入resolveMultipart爲:CommonsMultipartResolver
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--掃描全部的 spring包下的文件;--> <!--固然須要在spring配置文件裏面配置一下自動掃描範圍 <context:component-scan base-package="*"/> *表明你想要掃描的那些包的目錄所在位置。Spring 在容器初始化時將自動掃描 base-package 指定的包及其子包下的全部的.class文件, 全部標註了 @Repository 的類都將被註冊爲 Spring Bean。 --> <context:component-scan base-package="com.dx.test"/> <!--新增長的兩個配置,這個是解決406問題的關鍵--> <!--mvc註解驅動(可代替註解適配器與註解映射器的配置),默認加載不少參數綁定方法(實際開發時使用)--> <context:annotation-config/> <mvc:annotation-driven/> <!-- <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.dx.test.interceptors.LogInterceptor"/> </mvc:interceptor> </mvc:interceptors> --> <!--end--> <!-- <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> </bean> <!-- 配置文件上傳解析器 enctype="multipart/form-data" --> <!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">--> <!-- 設定默認編碼 --> <!--<property name="defaultEncoding" value="UTF-8" />--> <!-- 設定文件上傳的最大值爲5MB,5*1024*1024 --> <!--<property name="maxUploadSize" value="5242880" />--> <!-- 設定文件上傳時寫入內存的最大值,若是小於這個參數不會生成臨時文件,默認爲10240,40*1024 --> <!--<property name="maxInMemorySize" value="40960" />--> <!-- 上傳文件的臨時路徑 fileUpload/temp--> <!--<property name="uploadTempDir" value="/" />--> <!-- 延遲文件解析 --> <!--<property name="resolveLazily" value="true"/>--> <!--</bean>--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <property name="maxUploadSize" value="5242880" /> <property name="maxInMemorySize" value="40960" /> <property name="uploadTempDir" value="fileUpload/temp" /> <property name="resolveLazily" value="true"/> </bean> <!-- <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /> --> <!--本身後加的,該BeanPostProcessor將自動對標註@Autowired的bean進行注入--> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <!--<ref bean="stringHttpMessageConverter"/>--> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/> <!-- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> --> </list> </property> </bean> </beans>
2)/WEB-INF/web.xml引入ContextLoaderListener,和MultipartFilter
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!-- 全局初始化數據,spring的監聽器讀取此配置文件,多個配置文件用分號分隔 若是在web.xml中不寫任何參數配置信息,默認的路徑是/WEB-INF/applicationContext.xml,在WEB-INF目錄下建立的xml文件的名稱必須是applicationContext.xml; 若是是要自定義文件名能夠在web.xml里加入contextConfigLocation這個context參數: --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器能夠處理http請求中的文件,被該過濾器過濾後會用post方法提交,form表單需設爲enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器以前 --> <!--spring中配置的id爲multipartResolver的解析器--> <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name>--> <!--<url-pattern>/*</url-pattern>--> <servlet-name>myAppServletName</servlet-name> </filter-mapping> <!-- 注意:HiddenHttpMethodFilter必須做用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 做用:能夠過濾全部請求,並能夠分爲四種 使用該過濾器須要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是否是put 或者 delete,若是不是 則默認post請求 --> <!-- <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping> --> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <!--<multipart-config>--> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <!--<max-file-size>2097152</max-file-size>--> <!--整個請求不超過4M--> <!--<max-request-size>4194304</max-request-size>--> <!--全部文件都要寫入磁盤--> <!--<file-size-threshold>0</file-size-threshold>--> <!--</multipart-config>--> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
3)form表單
<h2>Post包含上傳文件提交:</h2> <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data"> Id:<form:hidden path="id"/> Title: <form:input path="title" style="width:200px;"/><br/> Content: <form:input path="content" style="width:200px;"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form:form>
4)後臺接口
@RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article,/*HttpServletRequest request*//*MultipartHttpServletRequest request*/DefaultMultipartHttpServletRequest request) throws IOException, ServletException { System.out.println(article); MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap(); System.out.println("「"); for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) { StringBuilder builder = new StringBuilder(); part.getValue().forEach(s -> { builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],"); }); builder.delete(builder.length() - 1, builder.length()); System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}"); } System.out.println("」"); String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
後臺打印信息:
[DEBUG] Using MultipartResolver 'multipartResolver' for MultipartFilter [DEBUG] Part 'files', size 1181 bytes, filename='ImageVO.java' [DEBUG] Part 'files', size 1732 bytes, filename='UploadImgParam.java' [DEBUG] Part 'files', size 9 bytes, filename='test.svg' [DEBUG] Part 'execelFile', size 11375 bytes, filename='數據接口.xlsx' [DEBUG] POST "/article/update_with_post_file", parameters={masked} [DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, DefaultMultipartHttpServletRequest) ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'} 「 files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,test.svg,test.svg]} execelFile->{execelFile,[execelFile,數據接口.xlsx,數據接口.xlsx]} 」 1,算法與數據結構--綜合提高篇(c++版),文章內容 [DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors} [DEBUG] Forwarding to [/WEB-INF/views/index.jsp] [DEBUG] Completed 200 OK [DEBUG] Cleaning up part 'files', filename 'ImageVO.java' [DEBUG] Cleaning up part 'files', filename 'UploadImgParam.java' [DEBUG] Cleaning up part 'files', filename 'test.svg' [DEBUG] Cleaning up part 'execelFile', filename '數據接口.xlsx'
只須要基於post方式,作如下調整便可:
1)/WEB-INF/web.xml中引入hiddenHttpMethodFilter(applicationContext.xml)不須要修改
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springmvcdemo</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!-- 全局初始化數據,spring的監聽器讀取此配置文件,多個配置文件用分號分隔 若是在web.xml中不寫任何參數配置信息,默認的路徑是/WEB-INF/applicationContext.xml,在WEB-INF目錄下建立的xml文件的名稱必須是applicationContext.xml; 若是是要自定義文件名能夠在web.xml里加入contextConfigLocation這個context參數: --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器能夠處理http請求中的文件,被該過濾器過濾後會用post方法提交,form表單需設爲enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器以前 --> <!--spring中配置的id爲multipartResolver的解析器--> <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>MultipartFilter</filter-name>--> <!--<url-pattern>/*</url-pattern>--> <servlet-name>myAppServletName</servlet-name> </filter-mapping> <!-- 注意:HiddenHttpMethodFilter必須做用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 做用:能夠過濾全部請求,並能夠分爲四種 使用該過濾器須要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是否是put 或者 delete,若是不是 則默認post請求 --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>myAppServletName</servlet-name> </filter-mapping> <!--結束後端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> <!--能夠經過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>myAppServletName</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- StandardServletMultipartResolver 屬性配置 --> <!--<multipart-config>--> <!--上傳到/tmp/upload 目錄,若是配置爲/使用HttpServletRequest上傳時,可能會拋出異常/無權限操做--> <!--<location>/</location>--> <!--文件大小爲2M--> <!--<max-file-size>2097152</max-file-size>--> <!--整個請求不超過4M--> <!--<max-request-size>4194304</max-request-size>--> <!--全部文件都要寫入磁盤--> <!--<file-size-threshold>0</file-size-threshold>--> <!--</multipart-config>--> </servlet> <servlet-mapping> <servlet-name>myAppServletName</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2)post表單中引入<input type="hidden" name="_method" value="put" />標籤
<h2>Put包含上傳文件提交:</h2> <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data"> <input type="hidden" name="_method" value="PUT"/> Id:<input name="id" id="id" value="${article.id}"/><br/> Title:<input name="title" id="title" value="${article.title}"/><br/> Content:<input name="content" id="content" value="${article.content}"/><br/> yourfile: <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> <input type="file" name="files"/><br/> yourfile2: <input type="file" name="execelFile"/><br/> <input type="submit" value="Submit" /> </form>
3)後臺接口修改put方式接收請求
@RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, /*HttpServletRequest request*//*MultipartHttpServletRequest request*/DefaultMultipartHttpServletRequest request) throws IOException, ServletException { System.out.println(article); MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap(); System.out.println("「"); for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) { StringBuilder builder = new StringBuilder(); part.getValue().forEach(s -> { builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],"); }); builder.delete(builder.length() - 1, builder.length()); System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}"); } System.out.println("」"); String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index"; }
後臺打印信息:
[DEBUG] Using MultipartResolver 'multipartResolver' for MultipartFilter [DEBUG] Part 'files', size 1181 bytes, filename='ImageVO.java' [DEBUG] Part 'files', size 1732 bytes, filename='UploadImgParam.java' [DEBUG] Part 'files', size 13872 bytes, filename='SpringBoot-Converter' [DEBUG] Part 'execelFile', size 11375 bytes, filename='數據接口.xlsx' [DEBUG] PUT "/article/update_with_put_file", parameters={masked} [DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, DefaultMultipartHttpServletRequest) ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'} 「 files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,SpringBoot-Converter,SpringBoot-Converter]} execelFile->{execelFile,[execelFile,數據接口.xlsx,數據接口.xlsx]} 」 1,算法與數據結構--綜合提高篇(c++版),文章內容 [DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法與數據結構--綜合提高篇(c++版)', content='文章內容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors} [DEBUG] Forwarding to [/WEB-INF/views/index.jsp] [DEBUG] Completed 200 OK [DEBUG] Cleaning up part 'files', filename 'ImageVO.java' [DEBUG] Cleaning up part 'files', filename 'UploadImgParam.java' [DEBUG] Cleaning up part 'files', filename 'SpringBoot-Converter' [DEBUG] Cleaning up part 'execelFile', filename '數據接口.xlsx'