栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

《SpringMVC系列》第八章:文件上传

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《SpringMVC系列》第八章:文件上传

一、文件上传 1.测试 表单

接口
	@ResponseBody
    @PostMapping(value = "/upload")
    public String upload(@RequestPart("file") MultipartFile file, // 接收单个文件上传
                         @RequestPart("images") MultipartFile[] files){ // 多个文件上传
        System.out.println(file);
        System.out.println(files.length);
        return "成功";
    }
2.springboot配置项
配置描述默认值
spring.servlet.multipart.enabled是否启用对分段上传的支持true
spring.servlet.multipart.file-size-threshold文件写入磁盘的阈值。0B
spring.servlet.multipart.max-file-size最大文件大小。1MB
spring.servlet.multipart.max-request-sizex最大请求大小。10MB
3.上传源码 doDispatch()
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
                 // 检查是否为上传请求,如果是文件上传的请求,那么会将请求包装一层
				processedRequest = checkMultipart(request);
                 // 不相等 则代表被包装了一层  是上传请求s
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
请求检查
	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;
	}
isMultipart() 判断是否为文件上传请求

判断请求是否为文件上传请求,判断方式也比较简单,就是判断请求的内容类型是否以multipart开头,这也就对应了我们在上传文件时候的表单必须设置一个enctype="multipart/form-data",其实这是在后台有对应的判断的

	@Override
	public boolean isMultipart(HttpServletRequest request) {
		return StringUtils.startsWithIgnoreCase(request.getContentType(),
				(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
	}
resolveMultipart() 请求封装

创建一个新的对象MultipartHttpServletRequest,将request包装起来

@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
	return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
解析文件封装进Map

从请求中解析出文件,将其添加到MultiValueMap中

	// 上面的方法创建了一个此对象
	public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
			throws MultipartException {

		super(request);
		if (!lazyParsing) {
			parseRequest(request);
		}
	}

    // 转换请求
	private void parseRequest(HttpServletRequest request) {
		try {
             // 从请求获取到文件信息
			Collection parts = request.getParts();
             // 文件参数名集合
			this.multipartParameterNames = new LinkedHashSet<>(parts.size());
			MultiValueMap files = new LinkedMultiValueMap<>(parts.size());
             // 遍历处理文件
			for (Part part : parts) {
                  // 获取请求头值 例如:form-data; name="file"; filename="converters.png"
				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());
				}
			}
             // 设置文件,该方法为父类的方法
             // 如果请求为文件上传的请求,那么该请求会被封装为AbstractMultipartHttpServletRequest
             // 同时将解析后的文件设置为其中的属性
			setMultipartFiles(files);
		}
		catch (Throwable ex) {
			handleParseFailure(ex);
		}
	}
参数解析

如果为文件上传请求,那么上面已经把文件解析好了,那么接一下看如何将参数赋值

@RequestPart
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean required() default true;
}
RequestPartMethodArgumentResolver

参数解析器,根据配置的注解信息,从上面设置的Map里面获取对应的结果

public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

	// 判断是否可以应用此参数处理器
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return true;
		}
		else {
			if (parameter.hasParameterAnnotation(RequestParam.class)) {
				return false;
			}
			return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
		}
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
         // 获取原生的请求
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");
         
         // 获取注解信息
		RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
         // 是否必填
		boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());

         // 参数名
		String name = getPartName(parameter, requestPart);
		parameter = parameter.nestedIfOptional();
		Object arg = null;

         // 解析上传参数
		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
         // 如果不相等  代表获取到了
		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			arg = mpArg;
		}
		else {
			try {
				HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
				arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
				if (binderFactory != null) {
					WebDataBinder binder = binderFactory.createBinder(request, arg, name);
					if (arg != null) {
						validateIfApplicable(binder, parameter);
						if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
							throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
						}
					}
					if (mavContainer != null) {
						mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
					}
				}
			}
			catch (MissingServletRequestPartException | MultipartException ex) {
				if (isRequired) {
					throw ex;
				}
			}
		}

         // 异常判断
		if (arg == null && isRequired) {
			if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
				throw new MultipartException("Current request is not a multipart request");
			}
			else {
				throw new MissingServletRequestPartException(name);
			}
		}
         // 判断是否包装成Optional
		return adaptArgumentIfNecessary(arg, parameter);
	}
}
resolveMultipartArgument()

该方法根据形参的类型,从Map里面获取结果,大体上就是一个if / else判断

参数可以为MultipartFile、Part类型,类型可以单个对象,集合,数组,这样 2 x 3 = 6,就对应下面的6个判断

当判断通过以后,就是从Map里面获取集合

	@Nullable
	public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
			throws Exception {
     
		MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
         // 再次判断是否为文件上传请求
		boolean isMultipart = (multipartRequest != null || isMultipartContent(request));

         // 形参类型为MultipartFile.class
		if (MultipartFile.class == parameter.getNestedParameterType()) {
			if (!isMultipart) {
				return null;
			}
			if (multipartRequest == null) {
				multipartRequest = new StandardMultipartHttpServletRequest(request);
			}
             // 这里底层是从上面创建的MultiValueMap里面获取
			return multipartRequest.getFile(name);
		}
		else if (isMultipartFileCollection(parameter)) {// 判断是否为Multipart集合
			if (!isMultipart) {
				return null;
			}
			if (multipartRequest == null) {
				multipartRequest = new StandardMultipartHttpServletRequest(request);
			}
			List files = multipartRequest.getFiles(name);
			return (!files.isEmpty() ? files : null);
		}
		else if (isMultipartFileArray(parameter)) {// 判断为Multipart数组
			if (!isMultipart) {
				return null;
			}
			if (multipartRequest == null) {
				multipartRequest = new StandardMultipartHttpServletRequest(request);
			}
			List files = multipartRequest.getFiles(name);
			return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
		}
		else if (Part.class == parameter.getNestedParameterType()) {// 判断为Part.class
			if (!isMultipart) {
				return null;
			}
			return request.getPart(name);
		}
		else if (isPartCollection(parameter)) {// 判断为Part集合
			if (!isMultipart) {
				return null;
			}
			List parts = resolvePartList(request, name);
			return (!parts.isEmpty() ? parts : null);
		}
		else if (isPartArray(parameter)) {// 判断为Part数组
			if (!isMultipart) {
				return null;
			}
			List parts = resolvePartList(request, name);
			return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);
		}
		else {
			return UNRESOLVABLE;
		}
	}
4.总结
  1. 如果request为文件上传请求,那么会将该请求包装成StandardMultipartHttpServletRequest
  2. 在包装请求的时候,会把文件参数都解析好,然后添加到一个Map里面
  3. 接收请求可以通过@RequestParam、@RequestPart标注参数
  4. 上面两个注解对应的参数解析器里面,底层其实都是从上面创建的Map里面获取,然后直接返回
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/874291.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号