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方法从请求中读出请求参数,转换并绑定到参数上 在入口RequestResponseBodyMethodProcessor类中,handleReturnValue方法上一篇已经分析过了,是对处理方法返回值进行处理的策略接口。这次看一下HandlerMethodArgumentResolver方法,这是将请求报文绑定到处理方法形参的策略接口
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方法中配置自定义的拦截器
JSR303验证器:数据验证的规范,比如使用hibernate-validator实现校验