SpringMVC源码-文件上传
一、环境配置
IndexController.java
@GetMapping("/file")
public String file() {
return "fileUpload";
}
@PostMapping("/fileUpload")
public void fileUpload(MultipartFile file) {
File file1 = new File(filePath + file.getName()); // filePath文件保存路径
try {
file1.createNewFile();
file.transferTo(file1);
} catch (IOException e) {
e.printStackTrace();
}
}
fileUpload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML 表单提交数据(POST)</title>
<form action="fileUpload" method="post" enctype="multipart/form-data">
选择文件: <input type="file" name="file">
<input type="submit" value="提交" />
</form>
mvc.xml
新增
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--配置上传文件的最大尺寸为10M-->
<property name="maxUploadSize" value="10485760"/>
</bean>
build.gradle
dependencies下新增
compile("commons-fileupload:commons-fileupload:1.3.1")
compile("commons-io:commons-io:2.4")
二、实例化CommonsMultipartResolver
调用CommonsMultipartResolver的构造函数CommonsMultipartResolver()
public CommonsMultipartResolver() {
super();
}
CommonsFileUploadSupport
public CommonsFileUploadSupport() {
this.fileItemFactory = newFileItemFactory();
this.fileUpload = newFileUpload(getFileItemFactory());
}
protected DiskFileItemFactory newFileItemFactory() {
return new DiskFileItemFactory();
}
CommonsFileUploadSupport.newFileUpload(FileItemFactory fileItemFactory)
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
return new ServletFileUpload(fileItemFactory);
}
CommonsMultipartResolver实现了ServletContextAware接口,在实例化CommonsMultipartResolver后调用AbstractAutowireCapableBeanFactory.initializeBean执行applyBeanPostProcessorsBeforeInitialization方法,为实现ServletContextAware接口的bean调用setServletContext方法。
CommonsMultipartResolver.setServletContext(ServletContext servletContext)
public void setServletContext(ServletContext servletContext) {
if (!isUploadTempDirSpecified()) { // 默认是false
getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
}
}
WebUtils.getTempDir
public static File getTempDir(ServletContext servletContext) {
Assert.notNull(servletContext, "ServletContext must not be null");
return (File) servletContext.getAttribute(TEMP_DIR_CONTEXT_ATTRIBUTE);
}
获取临时路径并设置到getFileItemFactory()的repository属性。上传的文件首先保存到临时路径。
三、DispatcherServlet解析文件上传
DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)调用checkMultipart(request)检查请求是否是多部分分发请求。
DispatcherServlet.checkMultipart(HttpServletRequest request)
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
判断multipartResolver非空且multipartResolver是否支持request,若是调用resolveMultipart.resolveMultipart解析request.
CommonsMultipartResolver.isMultipart(HttpServletRequest request)
public boolean isMultipart(HttpServletRequest request) {
return ServletFileUpload.isMultipartContent(request);
}
ServletFileUpload.isMultipartContent(HttpServletRequest request)
public static final boolean isMultipartContent(
HttpServletRequest request) {
if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
return false;
}
return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}
1、如果request的请求方法不是POST,返回false。文件上传是能用POST方法。
2、调用FileUploadBase.isMultipartContent
FileUploadBase.isMultipartContent
public static final boolean isMultipartContent(RequestContext ctx) {
String contentType = ctx.getContentType();
if (contentType == null) {
return false;
}
if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
return true;
}
return false;
}
判断request的ContentType是否以multipart/开头,若是返回true否则返回false。所以前端form表单必须设置enctype="multipart/form-data"。
CommonsMultipartResolver.resolveMultipart(final HttpServletRequest request)
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) { // resolveLazily默认是false
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());
}
}
1、resolveLazily默认是false,调用parseRequest,解析multipart元素
2、创建MultipartParsingResult和DefaultMultipartHttpServletRequest
CommonsMultipartResolver.parseRequest(HttpServletRequest request)
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);
}
}
1、determineEncoding确定request的Encoding
2、prepareFileUpload创建FileUpload
3、调用ServletFileUpload.parseRequest解析上传的文件为DiskFileItem集合。DiskFileItem保存上传的文件信息。
4、parseFileItems解析DiskFileItem集合
CommonsMultipartResolver.prepareFileUpload(@Nullable String encoding)
protected FileUpload prepareFileUpload(@Nullable String encoding) {
FileUpload fileUpload = getFileUpload();
FileUpload actualFileUpload = fileUpload;
// Use new temporary FileUpload instance if the request specifies
// its own encoding that does not match the default encoding.
if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {
actualFileUpload = newFileUpload(getFileItemFactory());
actualFileUpload.setSizeMax(fileUpload.getSizeMax());
actualFileUpload.setFileSizeMax(fileUpload.getFileSizeMax());
actualFileUpload.setHeaderEncoding(encoding);
}
return actualFileUpload;
}
1、获取FileUpload
2、判断encoding与FileUpload的编码是否相同,若不相同则新建FileUpload并将原来FileUploadsizeMax,fileSizeMax属性设置到新的FileUpload,设置新的FileUpload的encoding
ServletFileUpload.parseRequest(HttpServletRequest request)
public List<FileItem> parseRequest(HttpServletRequest request)
throws FileUploadException {
return parseRequest(new ServletRequestContext(request));
}
FileUploadBase.parseRequest(RequestContext ctx)获取FileItemIterator:
FileItemIterator iter = getItemIterator(ctx);
FileUploadBase.getItemIterator(RequestContext ctx)
public FileItemIterator getItemIterator(RequestContext ctx)
throws FileUploadException, IOException {
try {
return new FileItemIteratorImpl(ctx);
} catch (FileUploadIOException e) {
// unwrap encapsulated SizeException
throw (FileUploadException) e.getCause();
}
}
FileUploadBase.FileItemIteratorImpl(RequestContext ctx)
FileItemIteratorImpl(RequestContext ctx)
throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
String contentType = ctx.getContentType();
if ((null == contentType)
|| (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
throw new InvalidContentTypeException(
format("the request doesn't contain a %s or %s stream, content type header is %s",
MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
}
@SuppressWarnings("deprecation") // still has to be backward compatible
final int contentLengthInt = ctx.getContentLength();
final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
// Inline conditional is OK here CHECKSTYLE:OFF
? ((UploadContext) ctx).contentLength()
: contentLengthInt;
// CHECKSTYLE:ON
InputStream input; // N.B. this is eventually closed in MultipartStream processing
if (sizeMax >= 0) {
if (requestSize != -1 && requestSize > sizeMax) {
throw new SizeLimitExceededException(
format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(requestSize), Long.valueOf(sizeMax)),
requestSize, sizeMax);
}
// N.B. this is eventually closed in MultipartStream processing
input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex = new SizeLimitExceededException(
format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(pCount), Long.valueOf(pSizeMax)),
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
input = ctx.getInputStream();
}
String charEncoding = headerEncoding;
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}
boundary = getBoundary(contentType);
if (boundary == null) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new FileUploadException("the request was rejected because no multipart boundary was found");
}
notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
try {
multi = new MultipartStream(input, boundary, notifier);
} catch (IllegalArgumentException iae) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new InvalidContentTypeException(
format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
}
multi.setHeaderEncoding(charEncoding);
skipPreamble = true;
findNextItem();
}
判断上传的文件大小是否超出文件上传限制的最大大小,若是则抛出异常。
CommonsFileUploadSupport.parseFileItems(List
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
Map<String, String[]> multipartParameters = new HashMap<>();
Map<String, String> multipartParameterContentTypes = new HashMap<>();
// Extract multipart files and multipart parameters.
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);
}
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);
}
遍历fileItems集合,判断FileItem是否是simple form fields,若是处理否则创建CommonsMultipartFile,添加到multipartFiles集合。
CommonsFileUploadSupport.createMultipartFile(FileItem fileItem)
protected CommonsMultipartFile createMultipartFile(FileItem fileItem) {
CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);
multipartFile.setPreserveFilename(this.preserveFilename);
return multipartFile;
}
创建CommonsMultipartFile并设置preserveFilename属性。
DefaultMultipartHttpServletRequest.(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes)
public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {
super(request);
setMultipartFiles(mpFiles);
setMultipartParameters(mpParams);
setMultipartParameterContentTypes(mpParamContentTypes);
}
1、调用父类构造函数
2、setMultipartFiles设置MultipartFile参数
3、setMultipartParameters设置multipartParameters
4、setMultipartParameterContentTypes设置multipartParameterContentTypes
InvocableHandlerMethod.getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs)解析handle方法参数,即@Controller类的处理request的方法的参数:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
1、获取所有方法参数
2、遍历参数,调用resolvers.supportsParameter判断HandlerMethodArgumentResolver是否支持解析参数,如果支持则resolvers.resolveArgument解析参数。
在上面第二步调用resolvers.supportsParameter时遍历调用resolvers.supportsParameter判断HandlerMethodArgumentResolver是否支持解析参数。遍历集合可知RequestParamMethodArgumentResolver解析MultipartFile类型的参数。
RequestParamMethodArgumentResolver.supportsParameter(MethodParameter parameter)
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
1、判断参数是否有@RequestParam,若有则返回true
2、判断是否有@RequestPart,若有返回false。
3、MultipartResolutionDelegate.isMultipartArgument判断参数是否是MultipartArgument,若是则返回true
4、判断useDefaultResolution是否为true,若是返回参数是否是简单类型
5、返回false
MultipartResolutionDelegate.isMultipartArgument(MethodParameter parameter)
public static boolean isMultipartArgument(MethodParameter parameter) {
Class<?> paramType = parameter.getNestedParameterType();
return (MultipartFile.class == paramType ||
isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
(Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
}
以下情况返回true1:
1、参数类型是MultipartFile
2、参数类型是MultipartFile集合
3、参数类型是MultipartFile数组
4、参数类型是Part
5、参数类型是Part集合
6、参数类型是Part数组
AbstractNamedValueMethodArgumentResolver.resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
1、获取参数名称并封装成NamedValueInfo
2、resolveEmbeddedValuesAndExpressions解析参数名称,参数名称可能包含${}或表达式。
3、resolveName通过参数名称从request中解析出参数值,MultipartFile类型参数通过MultipartHttpServletRequest.getFile(参数名)获取参数值的
4、如果参数为null进行其他处理
5、binderFactory不为null,WebDataBinder处理参数值
6、handleResolvedValue处理解析后的参数值,空方法