SpringMVC(十五):Dispatcher的重要组件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartResolver)的用法
MultipartResolver组件
从Spring官网上可以看到MultipartResolver接口的定义信息:
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 API
There 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
.
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.
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.
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.
关于MultipartResolver的接口文档,请参考Spring5.2.x的官方文档《https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/MultipartResolver.html》
文件上传策略接口MultipartResolver的实现
public interface MultipartResolver { // 判断request是否为文件上传请求 boolean isMultipart(HttpServletRequest request); // 将HttpServletRequest请求转化为MultipartHttpServletRequest MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; // 清空:入参是MultipartHttpServletRequest void cleanupMultipart(MultipartHttpServletRequest request); }
当用户的请求到DispatcherServlet时,
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类
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类
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配置。
文件上传请求接口MultipartRequest的实现
文件上传处理过程中国使用的是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"); } }
DefaultMultipartHttpServletRequest
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。
StandardMultipartHttpServletRequst
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。
ResolveMultipart组件上传文件的用法
在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方案’大致一致。
下边就对这几种实现上传文件的方案进行讲解如何使用。
使用HttpServletRequest实现(不配置ResolveMultipart组件)
Post方式:
/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
Put方式:
此时,只需要基于上边的实现方案稍作调整即可:
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
使用StandardServletMultipartResolver实现
注意:此时在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); } } }
Post方式:
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
Put方式:
基于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
使用CommonsMultipartResolver实现
因该方案内部采用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>
Post方式:
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'
Put方式:
只需要基于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'
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。