SpringMVC(四) 文件上传与下载

Posted by ZhouJ000 on May 5, 2019

SpringMVC(一) 初始化
SpringMVC(二) 请求调度
SpringMVC(三) 消息转换器
SpringMVC(四) 文件上传与下载

quickStart

上传

首先设置提交表单的enctype:

<form name="updateFile" action="/fileUpload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="upload"/>
</form>

然后在项目中配置Resolver:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <property name="maxUploadSize" value="104857600" />
     <property name="maxInMemorySize" value="4096" />
     <property name="defaultEncoding" value="UTF-8"></property>
</bean>

最后在controller中设置相应方法获取:

@RequestMapping("fileUpload")
// 						 @RequestBody MultipartFile file
public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException {
	// ...
}

// 手动转
@RequestMapping("springUpload")
public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
	if(multipartResolver.isMultipart(request)) {
		MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest)request;
		Iterator iter=multiRequest.getFileNames();
		while(iter.hasNext()) {
			MultipartFile file = multiRequest.getFile(iter.next().toString());
			// ...
		}
	}
}

下载

传统方式

public void download(String fileName, HttpServletResponse response) { 
	// get file data[] by fileName
	// ...
	response.reset();  
	response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");  
	response.addHeader("Content-Length", "" + fileLength);
	// response.setContentType("application/x-msdownload;");
	response.setContentType("application/octet-stream;charset=UTF-8");  
	OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
	// 1.通过byte[]数组
	outputStream.write(data);  
	// 2.也可以通过输入流
	/* BufferedInputStream inputStream = BufferedInputStream(new FileInputStream(file));
	byte[] buff = new byte[2048];  
	int bytesRead;  
	while (-1 != (bytesRead = inputStream.read(buff, 0, buff.length))) {  
		outputStream.write(buff, 0, bytesRead);  
	} */
	outputStream.flush();  
	outputStream.close();  
}

使用ResponseEntity

public void download(String filename, HttpServletResponse response) { 
	// get file by filename
	// ...
	//http头信息
	HttpHeaders headers = new HttpHeaders();
	headers.setContentDispositionFormData("attachment", filename); 
	// MediaType:互联网媒介类型; contentType:具体请求中的媒体类型信息
	headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);   
	return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
}

源码解析

MultipartHttpServletRequest

在doDispatch方法中

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;
	// ...
	try {
		// ...
		processedRequest = checkMultipart(request);
		multipartRequestParsed = (processedRequest != request);
		// ...
	} finally {
		// ...
		// Clean up any resources used by a multipart request.
		if (multipartRequestParsed) {
			cleanupMultipart(processedRequest);
		}
	}
}

尝试转换request

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	// 1.之前设置的CommonsMultipartResolver,判断multipart
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		// 已经是MultipartHttpServletRequest
		if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
					"this typically results from an additional MultipartFilter in web.xml");
		}
		else if (hasMultipartException(request) ) {
			logger.debug("Multipart resolution failed for current request before - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		else {
			try {
				// 2.转换request
				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;
}

判断contentType

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

public static final boolean isMultipartContent(HttpServletRequest request) {
	// 需要是POST请求
	if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
		return false;
	}
	return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}

public static final boolean isMultipartContent(RequestContext ctx) {
	String contentType = ctx.getContentType();
	if (contentType == null) {
		return false;
	}
	// 以multipart/开头
	if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
		return true;
	}
	return false;
}

转换为MultipartHttpServletRequest

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);
		// 包装成DefaultMultipartHttpServletRequest返回
		return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
				parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
	}
}

解析上传请求

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
	// 获取编码: characterEncoding > fileUpload.headerEncoding > ISO-8859-1
	String encoding = determineEncoding(request);
	// 1.准备FileUpload
	FileUpload fileUpload = prepareFileUpload(encoding);
	try {
		// 2.解析出FileItem列表
		List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
		// 3.转换成MultipartParsingResult
		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、准备FileUpload

protected FileUpload prepareFileUpload(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;
}

这里看到获取自己的FileUpload,下面设置fileSizeMax好像似曾相识,在配置文件中配置过。由于CommonsMultipartResolver没有继承什么在spring初始化过程中会被调用的类,因此初始化逻辑在其构造函数中

public CommonsMultipartResolver() {
	super();
}

// CommonsFileUploadSupport
public CommonsFileUploadSupport() {
	this.fileItemFactory = newFileItemFactory();
	this.fileUpload = newFileUpload(getFileItemFactory());
}
// setter
public void setMaxUploadSize(long maxUploadSize) {
	this.fileUpload.setSizeMax(maxUploadSize);
}
public void setMaxUploadSizePerFile(long maxUploadSizePerFile) {
	this.fileUpload.setFileSizeMax(maxUploadSizePerFile);
}
public void setMaxInMemorySize(int maxInMemorySize) {
	this.fileItemFactory.setSizeThreshold(maxInMemorySize);
}
public void setDefaultEncoding(String defaultEncoding) {
	this.fileUpload.setHeaderEncoding(defaultEncoding);
}

2、解析出FileItem列表

// ServletFileUpload
public List<FileItem> parseRequest(HttpServletRequest request) throws FileUploadException {
	return parseRequest(new ServletRequestContext(request));
}

public List<FileItem> parseRequest(RequestContext ctx)
		throws FileUploadException {
	List<FileItem> items = new ArrayList<FileItem>();
	boolean successful = false;
	try {
		// 处理符合RFC 1867协议的multipart/form-data流(输入流、size、Encoding)
		FileItemIterator iter = getItemIterator(ctx);
		FileItemFactory fac = getFileItemFactory();
		if (fac == null) {
			throw new NullPointerException("No FileItemFactory has been set.");
		}
		// 创建FileItem
		while (iter.hasNext()) {
			final FileItemStream item = iter.next();
			// Don't use getName() here to prevent an InvalidFileNameException.
			final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
			FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
											   item.isFormField(), fileName);
			items.add(fileItem);
			try {
				Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
			} catch (FileUploadIOException e) {
				throw (FileUploadException) e.getCause();
			} catch (IOException e) {
				throw new IOFileUploadException(format("Processing of %s request failed. %s",
													   MULTIPART_FORM_DATA, e.getMessage()), e);
			}
			final FileItemHeaders fih = item.getHeaders();
			fileItem.setHeaders(fih);
		}
		successful = true;
		return items;
	} catch (FileUploadIOException e) {
		throw (FileUploadException) e.getCause();
	} catch (IOException e) {
		throw new FileUploadException(e.getMessage(), e);
	} finally {
		if (!successful) {
			for (FileItem fileItem : items) {
				try {
					fileItem.delete();
				} catch (Throwable e) {
					// ignore it
				}
			}
		}
	}
}

// FileUploadBase
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));
	}

	InputStream input = ctx.getInputStream();

	@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

	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);
		}
		input = new LimitedInputStream(input, 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);
			}
		};
	}

	String charEncoding = headerEncoding;
	if (charEncoding == null) {
		charEncoding = ctx.getCharacterEncoding();
	}

	boundary = getBoundary(contentType);
	if (boundary == null) {
		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) {
		throw new InvalidContentTypeException(
				format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
	}
	multi.setHeaderEncoding(charEncoding);

	skipPreamble = true;
	findNextItem();
}

3、转换成MultipartParsingResult

protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
	MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<String, MultipartFile>();
	Map<String, String[]> multipartParameters = new HashMap<String, String[]>();
	Map<String, String> multipartParameterContentTypes = new HashMap<String, String>();

	// Extract multipart files and multipart parameters.
	for (FileItem fileItem : fileItems) {
		// 表单字段
		if (fileItem.isFormField()) {
			String value;
			String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
			if (partEncoding != null) {
				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();
				}
			}
			else {
				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);
			if (logger.isDebugEnabled()) {
				logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
						" bytes with original filename [" + file.getOriginalFilename() + "], stored " +
						file.getStorageDescription());
			}
		}
	}
	return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}

回到CommonsMultipartResolver.resolveMultipart方法,包装为DefaultMultipartHttpServletRequest

return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
					parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());

public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
		Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {
	super(request);
	setMultipartFiles(mpFiles);
	setMultipartParameters(mpParams);
	setMultipartParameterContentTypes(mpParamContentTypes);
}

到这里,就知道前面手动转的controller处理是如何拿到上传文件并处理的,而我们其实可以直接用MultipartFile或CommonsMultipartFile(多文件CommonsMultipartFile[])作为入参进行接收。那么SpringMVC是怎么帮我们做到的呢?

转换参数

上一篇讲了HttpMessageConverter消息转换器机制,其中分析了@ResponseBody,从上面的例子中可以看到入参前有@RequestBody进行修饰,那是不是可以从这个点着手进行探究呢?先回顾一下转换器:

public interface HttpMessageConverter<T> {
	boolean canRead(Class<?> clazz, MediaType mediaType);
	boolean canWrite(Class<?> clazz, MediaType mediaType);
	List<MediaType> getSupportedMediaTypes();
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}

HttpInputMessage是SpringMVC对一次Http请求报文的抽象,而HttpOutputMessage则是对一次Http响应报文的抽象,消息转换器会根据不同规则进行转换。在SpringMVC进入controller方法时,会根据@RequestBody选择恰当的转换器实现类来解析入参变量,其恰当的实现类的canRead方法会返回true,然后通过read方法从请求中读出请求参数,转换并绑定到参数上 httpMessageConverter 在入口RequestResponseBodyMethodProcessor类中,handleReturnValue方法上一篇已经分析过了,是对处理方法返回值进行处理的策略接口。这次看一下HandlerMethodArgumentResolver方法,这是将请求报文绑定到处理方法形参的策略接口 requestResponseBodyMethodProcessor

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
	Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
	String name = Conventions.getVariableNameForParameter(parameter);

	WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
	if (arg != null) {
		validateIfApplicable(binder, parameter);
		if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
			throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
		}
	}
	mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

	return adaptArgumentIfNecessary(arg, parameter);
}

那是不是在上传过程中在这里进行处理呢?然而在实际debug中,发现并没有去这个RequestResponseBodyMethodProcessor

// DispatcherServlet.doDispatch -->
// AbstractHandlerMethodAdapter.handle -->
// RequestMappingHandlerAdapter.handleInternal --> invokeHandlerMethod --> 
// ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	// ---> 处理参数,然后调用doInvoke
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	// ...
	// ---> 上一篇分析过
	this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	// ...
}

// InvocableHandlerMethod.invokeForRequest --> getMethodArgumentValues
// HandlerMethodArgumentResolverComposite
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	if (resolver == null) {
		throw new IllegalArgumentException(
				"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
						" supportsParameter should be called first.");
	}
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

结果进入的是RequestParamMethodArgumentResolver

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// ...
	Object resolvedName = resolveStringValue(namedValueInfo.name);
	// ...
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	// ...
	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		// ...
	}
	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

	return arg;
}

// RequestParamMethodArgumentResolver
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;
		}
	}
	// ...
}

// MultipartResolutionDelegate
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);
		}
		// 返回file
		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;
	}
}

显然在resolveMultipartArgument方法中获取了File并进行返回,那么问题来了,RequestParamMethodArgumentResolver从名字上看像是@RequestParam修饰的入参的处理类,而RequestResponseBodyMethodProcessor显然是 @RequestBody和@ResponseBody的处理类,它们都实现了HandlerMethodArgumentResolver接口,然而无论使用@RequestParam修饰还是使用@RequestBody修饰,最后都进入了RequestParamMethodArgumentResolver进行处理,这是为什么呢?这2个注解又有什么区别呢?

在前面的HandlerMethodArgumentResolverComposite.resolveArgument方法中,会去获取HandlerMethodArgumentResolver,看一下是怎么获取的

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
			if (methodArgumentResolver.supportsParameter(parameter)) {
				result = methodArgumentResolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

然后查看argumentResolvers可以发现,RequestParamMethodArgumentResolver在RequestResponseBodyMethodProcessor前面(它们在RequestMappingHandlerAdapter的afterPropertiesSet方法中初始化),那么看一下它的supportsParameter方法

public boolean supportsParameter(MethodParameter parameter) {
	// 当然@RequestParam的会在这里处理
	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();
		// MultipartFile的也可以处理
		if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
			return true;
		}
		else if (this.useDefaultResolution) {
			return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
		}
		else {
			return false;
		}
	}
}

因此在RequestResponseBodyMethodProcessor之前,RequestParamMethodArgumentResolver就属于恰当的处理类去处理了,因此就算用@RequestBody修饰,依旧会通过它去处理转换

@RequestBody和@RequestParam

@RequestParam:可以接受简单类型的属性,也可以接受对象类型,接收ContentType为x-www-form-urlencoded的请求参数(即默认)。接收的参数是来自于requestHeader中,实质是将Request.getParameter()中的Key-Value参数Map利用Spring的转化机制ConversionService配置,转化成参数接收对象的字段

@RequestBody:处理HttpEntity传递过来的数据,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用HandlerAdapter配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上,因此要有对应的解析器来解析请求体内容,比如application/json可以用jackson来解析

由于在GET请求中没有HttpEntity,因此不能使用@RequestBody;在POST请求中,可以使用@RequestBody和@RequestParam,但是如果使用@RequestBody,对于参数转化的配置必须统一

其他补充

@InitBinder:被此注解的方法可以对WebDataBinder初始化,WebDataBinder是用于数据绑定和格式化的,可以用于解绑、注册已有的编辑器、按前缀绑定等,比如对Date类型的转换

@ModelAttribute:在方法定义上使用,Spring MVC会在调用目标处理方法前,逐个调用在方法级上标注了@ModelAttribute的方法;在方法的入参前使用,可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数绑定到对象中,再传入入参将方法入参对象添加到模型中

@ExceptionHandler:注解到方法上,出现异常时会执行该方法

@ControllerAdvice:使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常

@PathVariable:绑定URL占位符到入参

自定义拦截器:实现HandlerInterceptor接口,然后在<mvc:interceptors>或WebMvcConfigurer实现类中的addInterceptors方法中配置自定义的拦截器 interceptor

JSR303验证器:数据验证的规范,比如使用hibernate-validator实现校验

mvc