SpringMVC文件上传原理

文件上传原理 入口首先查看MultipartAutoConfiguration类,该类中定义SpringBoot已经帮我们配置好的一系列文件上传相关的操作

实验Controller

    @ResponseBody
    @PostMapping("/upload")
    public String upload(@RequestPart MultipartFile file) {
        try {
            FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(new File("E:\\java_project\\integration-project\\spring-tutorial\\src\\main\\resources\\test\\" + file.getOriginalFilename())));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "update success";
    }

在该自动配置类中核心配置了StandardServletMultipartResolver 来处理文件上传逻辑,文件上传相关的配置MultipartProperties这个配置类中,对应yaml文件中的spring.servlet.multipart配置项

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

	private final MultipartProperties multipartProperties;

	public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
		this.multipartProperties = multipartProperties;
	}

	@Bean
	@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
	public MultipartConfigElement multipartConfigElement() {
		return this.multipartProperties.createMultipartConfig();
	}

	@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
	@ConditionalOnMissingBean(MultipartResolver.class)
	public StandardServletMultipartResolver multipartResolver() {
		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
		multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
		return multipartResolver;
	}

}

文件上传原理需要分析StandardServletMultipartResolver

原理步骤

  1. 进入DispatcherServlet#doDispatch方法,检查是否是Multipart请求

    image-20210207101059686

  2. 底层调用了multipartResolver的isMultipart方法判断是不是文件上传请求 isMultipart方法逻辑也很简单 就是判断一下请求头中的ContentType是不是以multipart/开头

    	@Override
    	public boolean isMultipart(HttpServletRequest request) {
    		return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
    	}
    
    
  3. 如果是multipart上传请求 调用 resolver的 resolveMultipart方法将原生的Request包装成一个上传的Request

    public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
       return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
    }
    
  4. 底层StandardMultipartHttpServletRequest 构造器里面有一个parseRequest方法 里面封装了将request域中的数据封装到一个Map中

    	public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
    			throws MultipartException {
    
    		super(request);
            // 是否懒解析 默认false 进入parseRequest方法核心
    		if (!lazyParsing) {
    			parseRequest(request);
    		}
    	}
    
    	private void parseRequest(HttpServletRequest request) {
    		try {
                // 原生request的getParts方法
    			Collection<Part> parts = request.getParts();
                // 保存所有的文件名
    			this.multipartParameterNames = new LinkedHashSet<>(parts.size());
                // 一个Map保存了 文件名为K  内容为V的数据
    			MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
                // 遍历parts中的对象 将所有对象封装到该 map中
    			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());
    				}
    			}
                // 循环完毕之后 将这个Map 设置到该对象multipartFiles属性中,供后面的方法可以使用
    			setMultipartFiles(files);
    		}
    		catch (Throwable ex) {
    			handleParseFailure(ex);
    		}
    	}
    
  5. 经过上面的步骤将Part文件包装到了MultipleRequest的包装类中的Map中,然后进入handlerAdapter的handler方法跟之前逻辑类似,先找所有的argumentResolvers,调用supportsParameter判断是否支持该类型的参数解析器,我们找到RequestPartMethodArgumentResolver来处理被@RequestPart标记的方法

    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;
    			}
                // 遍历所有argumentsResolver 获取能处理解析器
    			if (!this.resolvers.supportsParameter(parameter)) {
    				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    			}
    			try {
                    // 找到 RequestPartMethodArgumentResolver 处理
    				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;
    	}
    
  6. 进去该参数解析器中的resolveArgument方法来解析参数, 核心在于调用了MultipartResolutionDelegate.resolveMultipartArgument方法来解析文件上传请求

    	@Override
    	@Nullable
    	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 拿到原生Request请求
    		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    		Assert.state(servletRequest != null, "No HttpServletRequest");
    // 拿到@RequestPart上面注解信息 解析是不是必须要解析
    		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;
    		// 核心在于这段代码 调用了MultipartResolutionDelegate代理类的resolveMultipartArgument方法
    		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);
    			}
    		}
    		return adaptArgumentIfNecessary(arg, parameter);
    	}
    
  7. 查看MultipartResolutionDelegate#resolveMultipartArgument方法 各种判断不同类型不同的处理方式

    	@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));
    
    		if (MultipartFile.class == parameter.getNestedParameterType()) {
    			if (multipartRequest == null && isMultipart) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
                // 因为之前new StandardMultipartHttpServletRequest 已经做了parse操作 此时这个multipartRequest中的file已经封装了文件相关内容,只需要去内部的Map中拿执行的文件名曲取即可
    			return (multipartRequest != null ? multipartRequest.getFile(name) : null);
    		}
    		else if (isMultipartFileCollection(parameter)) {
    			if (multipartRequest == null && isMultipart) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
    			return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
    		}
    		else if (isMultipartFileArray(parameter)) {
    			if (multipartRequest == null && isMultipart) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
    			if (multipartRequest != null) {
    				List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
    				return multipartFiles.toArray(new MultipartFile[0]);
    			}
    			else {
    				return null;
    			}
    		}
    		else if (Part.class == parameter.getNestedParameterType()) {
    			return (isMultipart ? request.getPart(name): null);
    		}
    		else if (isPartCollection(parameter)) {
    			return (isMultipart ? resolvePartList(request, name) : null);
    		}
    		else if (isPartArray(parameter)) {
    			return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null);
    		}
    		else {
    			return UNRESOLVABLE;
    		}
    	}
    
  8. 此时文件上传的请求参数封装就完毕了,后面就继续调用 doInvoke方法,进入目标方法调用逻辑继续往下执行

  9. 如果没有使用@RequestPart注解 会使用RequestParamMethodArgumentResolver来处理,底层还是调用了MultipartResolutionDelegate#resolveMultipartArugment

    
    	@Override
    	@Nullable
    	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    
    		if (servletRequest != null) {
                // 底层逻辑还是一直
    			Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    			if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
    				return mpArg;
    			}
    		}
    
    		Object arg = null;
    		MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    		if (multipartRequest != null) {
    			List<MultipartFile> files = multipartRequest.getFiles(name);
    			if (!files.isEmpty()) {
    				arg = (files.size() == 1 ? files.get(0) : files);
    			}
    		}
    		if (arg == null) {
    			String[] paramValues = request.getParameterValues(name);
    			if (paramValues != null) {
    				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
    			}
    		}
    		return arg;
    	}
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值