Spring
application/x-www-form-urlencoded
和multipart/form-data
编码的内容,提交方式GET、POST;该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded
和multipart/form-data
编码的内容,例如application/json
, application/xml
等;
它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。
因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded
的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api;
非application/x-www-form-urlencoded
和multipart/form-data
等协议时@RequestParam获取不到值的原因要追溯到tomcat的request请求处理中。
此处所用源码为apache-tomcat-9.0.2-src。
上面说了@RequestParam实际调用的是Request.getParameter()取值,在tomcat中执行的是
/**
* @return the value of the specified request parameter, if any; otherwise,
* return <code>null</code>. If there is more than one value defined,
* return only the first one.
*
* @param name Name of the desired request parameter
*/
@Override
public String getParameter(String name) {
if (!parametersParsed) {
parseParameters();
}
return coyoteRequest.getParameters().getParameter(name);
}
parametersParsed是一个为false的全局变量,由于是线程的问题。parseParameters()针对这个请求应该只会执行一次。这里关键代码有两个,其一如下:
String contentType = getContentType();
if (contentType == null) {
contentType = "";
}
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
} else {
contentType = contentType.trim();
}
if ("multipart/form-data".equals(contentType)) {
parseParts(false);
success = true;
return;
}
if (!("application/x-www-form-urlencoded".equals(contentType))) {
success = true;
return;
}
这段代码不难理解,首先会得到请求类型。从上述代码可以得出:
application/json;application/x-www-form-urlencoded
,最终是application/json
。application/x-www-form-urlencoded
,直接return掉了。也就是说 关键代码二不执行 了。multipart/form-data
时,parseParts()方法里使用的解析文件的框架是apache自带的fileupload。好了,以下贴出关键二的代码:
readPostBody(formData, len)
parameters.processParameters(formData, 0, len);
readChunkedPostBody方法,其实就是request.getInputStream().read()方法,将请求的数据通过流的方式读取出来。
processParameters()是在Parameters类里面的方法,做的工作就是对请求的数据,做key与value的拆分,然后存放进一个名叫paramHashValues的Map中。后续的request.getParameter取的就是paramHashValues里面的数据。
由于上述分析的contenttype不为form-data的和x-www-form-urlencoded的不会执行关键二的代码,所以对于请求类型为application/json通过request.getParameter得到的数据为空。
代码基于spring-webMVC 4.3.10.RELEASE。
对于添加了@RequestBody和@ResponseBody注解的后端端点,都会经历由HttpMessageConverter进行的数据转换的过程。
在AnnotationDrivenBeanDefinitionParser类中发现一个getMessageConverters方法,可以考虑参考。
HttpMessageConverter这个顶级接口:
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read (can be {@code null} if not specified);
* typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write (can be {@code null} if not specified);
* typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, MediaType mediaType);
/**
* Return the list of {@link MediaType} objects supported by this converter.
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
大致能看出Spring的处理思路。下面的流程图可以更好方便我们的理解:
媒体类型 | 含义 |
---|---|
text/html | HTML格式 |
text/plain | 纯文本格式 |
text/xml, application/xml | XML数据格式 |
application/json | JSON数据格式 |
image/gif | gif图片格式 |
image/png | png图片格式 |
application/octet-stream | 二进制流数据 |
application/ x-www-form-urlencoded | form表单数据 |
multipart/form-data | 含文件的form表单 |
其中有几个类型值得一说,web开发中我们常用的提交表单操作,其默认的媒体类型就是application/ x-www-form-urlencoded,而当表单中包含文件时,大家估计都踩过坑,需要将enctype=multipart/form-data设置在form参数中。text/html也就是常见的网页了,json与xml常用于数据交互,其他不再赘述。
而在JAVA中,提供了MediaType这样的抽象,来与http的媒体类型进行对应。‘/’之前的名词,如text,application被称为类型(type),‘/’之后被称为子类型(subType)。