一、上传文件功能的实现:
前端JSP代码:
form 表单提交,enctype为multipart/form-data,请求方式POST
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
pageContext.setAttribute("ctx",request.getContextPath());
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${ctx}/fileupload" enctype="multipart/form-data" method="post">
描述:<input type="text" name="desc"><br><br>
文件:<input type="file" name="file"><br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
FileUploadController 的 fileupload方法:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
public class FileUploadController {
@RequestMapping("fileupload")
public String upload(MultipartFile file, HttpServletRequest request,String desc) throws IOException {
System.out.println(desc);
if (file.isEmpty()){
return "false";
}
String path = request.getServletContext().getRealPath("/WEB-INF/file");
String fileName = file.getOriginalFilename();
File filePath = new File(path,fileName);
if (!filePath.getParentFile().exists()){
filePath.getParentFile().mkdir();
}
file.transferTo(filePath);
return "success";
}
}
二、上传文件使用的组件
MultipartResolver
MultipartResolver用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,可以直接调用getFile方法来获取File,如果上传多个文件,可以调用getFileMap来处理。
public interface MultipartResolver {
/**
* 判断是否是上传请求
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将request请求包装成MultipartHttpServletRequest
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 处理完成之后清理上传过程中产生的临时资源
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
类继承关系,需要实现上面三个方法:
CommonsMultipartResolver
<a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>
依赖的一个apache的组件jar包
初始化九大内置组件的时候,没有对上传组件设置默认值,所以要提前交给spring生成。
放在SpringMVC的配置文件里,等待加载完成。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="17367648787"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
三、上传文件执行流程
1.检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
确定请求是否包含多部分内容的实用程序方法。注意:在FileUpload 1.1发布之后,这个方法将被移动到ServletFileUpload类中。不幸的是,由于此方法是静态的,因此在删除此方法之前不可能提供其替代品。
MULTIPART和enctype=“multipart/form-data” 对应,证明是上传文件的请求。
this.multipartResolver.isMultipart(request)返回true,
进入
// 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
return this.multipartResolver.resolveMultipart(request);
CommonsMultipartResolver#resolveMultipart
// 处理懒加载,如果为true的话,会将解析请求的操作放到DefaultMultipartHttpServletRequest的initializeMultipart方法中,只有在实际调用的时候才会被调用
// 如果值为false的话,那么会先调用parseRequest方法来处理request请求,然后将处理的结果放到DefaultMultipartHttpServletRequest
private boolean resolveLazily = false;
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());
}
}
MultipartParsingResult parsingResult = parseRequest(request);
/**
* 对请求进行处理,转成MultipartParsingResult对象
*
* 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对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
FileUpload fileUpload = prepareFileUpload(encoding);
try {
// 对请求中的multipart文件进行具体的处理
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);
}
}
// 对请求中的multipart文件进行具体的处理
List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
public List<FileItem> parseRequest(RequestContext ctx)
throws FileUploadException {
List<FileItem> items = new ArrayList<FileItem>();
boolean successful = false;
try {
FileItemIterator iter = getItemIterator(ctx);
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(format("Processing of %s request failed. %s",
MULTIPART_FORM_DATA, e.getMessage()), e);
}
final FileItemHeaders fih = item.getHeaders();
fileItem.setHeaders(fih);
}
successful = true;
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
} finally {
if (!successful) {
for (FileItem fileItem : items) {
try {
fileItem.delete();
} catch (Exception ignored) {
// ignored TODO perhaps add to tracker delete failure list somehow?
}
}
}
}
}
获取到传递得文件
/**
* 对请求进行处理,转成MultipartParsingResult对象
*
* 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对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
FileUpload fileUpload = prepareFileUpload(encoding);
try {
// 对请求中的multipart文件进行具体的处理
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);
}
}
parseFileItems
/**
* Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
* containing Spring MultipartFile instances and a Map of multipart parameter.
* @param fileItems the Commons FileItems to parse
* @param encoding the encoding to use for form fields
* @return the Spring MultipartParsingResult
* @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
*/
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
// 保存上传的文件
MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
// 保存参数
Map<String, String[]> multipartParameters = new HashMap<>();
// 保存参数的contentType
Map<String, String> multipartParameterContentTypes = new HashMap<>();
// Extract multipart files and multipart parameters.
// 将fileItems分为文件和参数两类,并设置到对应的map
for (FileItem fileItem : fileItems) {
// 如果是参数类型
if (fileItem.isFormField()) {
String value;
String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
try {
value = fileItem.getString(partEncoding);
}
catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
"' with encoding '" + partEncoding + "': using platform default");
}
value = fileItem.getString();
}
String[] curParam = multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
// simple form field
// 单个参数
multipartParameters.put(fileItem.getFieldName(), new String[] {value});
}
else {
// array of simple form fields
// 数组参数
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
// 保存参数的contentType
multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
}
else {
// multipart file field
// 如果是文件类型
CommonsMultipartFile file = createMultipartFile(fileItem);
multipartFiles.add(file.getName(), file);
LogFormatUtils.traceDebug(logger, traceOn ->
"Part '" + file.getName() + "', size " + file.getSize() +
" bytes, filename='" + file.getOriginalFilename() + "'" +
(traceOn ? ", storage=" + file.getStorageDescription() : "")
);
}
}
return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}
parseFileItems得调用堆栈:
parseFileItems:301, CommonsFileUploadSupport (org.springframework.web.multipart.commons)
parseRequest:168, CommonsMultipartResolver (org.springframework.web.multipart.commons)
resolveMultipart:145, CommonsMultipartResolver (org.springframework.web.multipart.commons)
checkMultipart:1286, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1088, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doPost:971, FrameworkServlet (org.springframework.web.servlet)
service:681, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core