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 fileItems, String encoding)

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处理解析后的参数值,空方法

posted @ 2022-11-06 21:50  shigp1  阅读(104)  评论(0编辑  收藏  举报