SpringMVC文件上传接口设计与实现

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

#1 前两篇文章的铺垫

#1.1 SpringMVC文件上传源码分析前言

#1.2 apache fileupload源码分析

#2 整体的包结构
首先看下整体的包的结构,如下图

在此输入图片描述

总共分成3大块,分别如下

##2.1 org.springframework.web.multipart

存放Spring定义的文件上传接口以及异常,如

##2.2 org.springframework.web.multipart.commons

用于整合apache fileupload的解析,对上述定义的接口进行实现,如

##2.3 org.springframework.web.multipart.support

用于整合j2ee自带的文件上传的解析,对上述定义的接口进行实现,如

接下来详细看看这些源码内容

#3 SpringMVC自己的接口设计

##3.1 MultipartResolver接口的内容:

public interface MultipartResolver {
	//判断当前的HttpServletRequest是否是文件上传类型
	boolean isMultipart(HttpServletRequest request);
	//将HttpServletRequest转化成MultipartHttpServletRequest
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
	//清除产生的临时文件等
	void cleanupMultipart(MultipartHttpServletRequest request);
}

##3.2 MultipartHttpServletRequest接口内容:

MultipartHttpServletRequest 继承了 HttpServletRequest 和 MultipartRequest,然后就具有了下面的两个主要功能

##3.3 整个处理流程

在SpringMVC的入口类DispatcherServlet中的doDispatch方法中,可以看到是如下的处理流程

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	boolean multipartRequestParsed = false;
	try {
		//略
		//步骤一
		processedRequest = checkMultipart(request);
		multipartRequestParsed = (processedRequest != request);
		//略
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	finally {
		//略
		// Clean up any resources used by a multipart request.
		//步骤二
		if (multipartRequestParsed) {
			cleanupMultipart(processedRequest);
		}
	}
}

可以看到这里主要有两个步骤

下面分别来说

###3.3.1 判断并解析HttpServletRequest成MultipartHttpServletRequest:

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		if (request instanceof MultipartHttpServletRequest) {
			logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
					"this typically results from an additional MultipartFilter in web.xml");
		}
		else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
			logger.debug("Multipart resolution failed for current request before - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		else {
			return this.multipartResolver.resolveMultipart(request);
		}
	}
	// If not returned before: return original request.
	return request;
}

###3.3.2 清理占用的资源,如临时文件

protected void cleanupMultipart(HttpServletRequest servletRequest) {
	MultipartHttpServletRequest req = WebUtils.getNativeRequest(
		servletRequest, MultipartHttpServletRequest.class);
	if (req != null) {
		this.multipartResolver.cleanupMultipart(req);
	}
}

这里其实就是调用MultipartResolver接口的void cleanupMultipart(MultipartHttpServletRequest request)方法

至此SpringMVC已经完成了自己的文件上传框架体系,即底层不管采用何种文件解析包都是走这样的一个流程。这样的一个流程其实就是对实际业务的抽象过程。我们在写代码的时候,经常就缺少抽象的能力,即很少抽象出各种业务逻辑的共同点。

#4 整合apache fileupload对文件上传的解析
刚才说了整个文件上传的处理流程,然后我们就来看下apache fileupload是如何整合进来的。即CommonsMultipartResolver是如何实现的

##4.1 判断一个request是否是multipart形式的

	@Override
	public boolean isMultipart(HttpServletRequest request) {
		return (request != null && ServletFileUpload.isMultipartContent(request));
	}

这里就是使用apache fileupload自己的ServletFileUpload.isMultipartContent判断方法,上一篇文章已经讲述了,这里不再说明了。

这里我们可以再多想一下,功能的职责划分问题(虽然问题很简单,主要是想引导大家在写代码的时候多去思考)。

因为目前判断一个request是否是multipart形式,都是一样的,不管你是哪种解析包,为什么SpringMVC不统一进行判断,而是采用解析包的判断?

如果SpringMVC自己进行统一的判断,似乎也没什么问题。站在apache fileupload的角度来说,判断request是否是multipart形式 的确应该是它的一个功能,而不是等待外界来判断。

SpringMVC既然采用第三方的解析包,就要遵守人家解析包的判断逻辑,而不是自行判断,虽然他们目前的判断逻辑是一样的。万一后来又出来一个解析包,判断逻辑不一样呢?如果流程体系还是采用SpringMVC自己的判断,可能就没法正常解析了

##4.2 将HttpServletRequest解析成DefaultMultipartHttpServletRequest

一旦上述判断通过了,则就需要执行解析过程(可以立即解析,也可以延迟解析),看下具体的解析过程

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());
	}
}

这里大致说下过程,详细的内容去看源代码。

这里有普通字段的处理和文件字段的处理。还记得上文讲的org.springframework.web.multipart.commons包的CommonsMultipartFile吗?可以看到通过new CommonsMultipartFile(fileItem),就将FileItem结果转化为了MultipartFile结果。

至此就将HttpServletRequest解析成了DefaultMultipartHttpServletRequest,所以我们在使用request时,它的类型其实就是DefaultMultipartHttpServletRequest类型,我们可以通过它来获取各种上传的文件信息。

##4.3 清理临时文件

其实就是对所有的CommonsMultipartFile中的FileItem进行删除临时文件的操作,这个删除操作是apache fileupload自己定义的,如下

protected void cleanupFileItems(MultiValueMap<String, MultipartFile> multipartFiles) {
	for (List<MultipartFile> files : multipartFiles.values()) {
		for (MultipartFile file : files) {
			if (file instanceof CommonsMultipartFile) {
				CommonsMultipartFile cmf = (CommonsMultipartFile) file;
				cmf.getFileItem().delete();
			}
		}
	}
}

至此,SpringMVC与apache fileupload的整合完成了,其他的整合也是类似的操作。

#5 整合j2ee自带的文件上传的解析

这个不再详细说明,主要引出来 javax.servlet.http.Part 这个对象是j2ee内置的文件上传解析结果,类似apache fileupload的FileItem解析结果,从Servlet3.0才加入进来的。

和apache fileupload一样的步骤,来看下具体源码内容:

##5.1 判断一个request是否是multipart形式的

@Override
public boolean isMultipart(HttpServletRequest request) {
	// Same check as in Commons FileUpload...
	if (!"post".equals(request.getMethod().toLowerCase())) {
		return false;
	}
	String contentType = request.getContentType();
	return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));
}

同样是这两个条件,post和"multipart/"开头。

##5.2 将HttpServletRequest解析成StandardMultipartHttpServletRequest

@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
	return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

在创建StandardMultipartHttpServletRequest的时候进行解析,解析过程和apache fileupload非常类似,只不过用Part替代了apache fileupload的FileItem,如下

private void parseRequest(HttpServletRequest request) {
	try {
		Collection<Part> parts = request.getParts();
		this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
		MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
		for (Part part : parts) {
			String filename = extractFilename(part.getHeader(CONTENT_DISPOSITION));
			if (filename != null) {
				files.add(part.getName(), new StandardMultipartFile(part, filename));
			}
			else {
				this.multipartParameterNames.add(part.getName());
			}
		}
		setMultipartFiles(files);
	}
	catch (Exception ex) {
		throw new MultipartException("Could not parse multipart servlet request", ex);
	}
}

遍历所有的Part,把每一个Part转化成StandardMultipartFile,而apache fileupload则是转化成CommonsMultipartFile。不再详细说明,具体的可以去看源码。

##5.3 遇到的一些问题
这里还有很多小插曲。

欢迎关注微信公众号:乒乓狂魔

微信公众号

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看