<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="white-space:pre"> </span>在项目中,客户端向服务端传送一组json数据,这组数据随着时间的推移可能会越来越大,可能会受到服务器对参数大小的限制的影响,因此,想直接将数据塞进request的body体中,继而发送给服务端,服务端直接将request中body 以流的方式读出来,并持久化。</span>
HttpServletRequest类中有两个方法 getInputStream() 和request.getReader() ,本以为可以直接快速搞定,但是运行程序时报异常:getInputStream() has already been called for this request ,经过查证,原来项目采用springmvc框架,框架内部已经读过request中流,所以当再次读取时,就读不到了,所以直接获取走不通了。
在网上问了问度娘,springmvc 中有一个注解@RequestBody 可以获取request中数据,部分代码如下:
@RequestMapping("/save")
public ModelAndView save(HttpServletRequest request,@RequestBody String s){
//具体操作 省略
}
运行过程中,还是有问题,但至少有进了一步,上述的String对象依然无法获取数据,这就有些纠结了,又开始问度娘和下载找spring的源码,了解@RequestBody的实现细节,发现获取body spring用了一系列的HttpMessageConverter 来实现,如:ByteArrayHttpMessageConverter,stringHttpMessageConverter ,spring自个实现的转换器,com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter【国产的json转换器,该转换器需要在配置文件中配置】,默认的转换器的初始化工作在RequestMappingHandlerAdapter这个类中实现
private List<HttpMessageConverter<?>> messageConverters;//转换器的集合
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}//初始化工作
找到这发现byte ,String的转换器已经初始化了,但是为啥还是拿不到String类型的数据呢,接着找关于converters相关的代码,有个setMessageConverters方法
/**
* Provide the converters to use in argument resolvers and return value
* handlers that support reading and/or writing to the body of the
* request and response.
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
spring在解析xml过程中会调用该方法 ,从而将用到的converters注入进去,因此明白项目在启动的过程中重新设置了HttpMessageConverter ,而我需要的converter并没有注入进去,因此在xml中配置如下
<mvc:annotation-driven >
<mvc:message-converters>
<ref bean="fastJsonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8" index="0"></constructor-arg>
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
<bean id="byteHttpMessageConverter"
class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
</bean>
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
<property name="features">
<list>
<!-- <value>WriteMapNullValue</value> -->
<value>QuoteFieldNames</value>
<value>WriteDateUseDateFormat</value>
</list>
</property>
</bean>
这时启动项目,可以拿到数据了。。。
因此,在遇到问题的时候,先按照自己的思路尝试着去解决,可能有时候有些问题确实不知道如何解决的时候,可以先查查网上有没有相关问题的解决方案,选择合适的方案,自然问题就迎刃而解了,但是个别的时候,会遇到相当棘手的问题,通过一系列方案都无法完美解决的时候,可以尝试去查阅源码,同过阅读源码,一会理解框架的开发者的设计思路,只要顺着他们的思路,我们的问题自然就不是问题了。
最后,多读读优秀的开源项目的源码。
附:遍历转换器的代码
AbstractMessageConverterMethodArgumentResolver
/**
* Creates the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.
*
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param methodParam the method argument
* @param targetType the type of object to create, not necessarily the same as
* the method parameter type (e.g. for {@code HttpEntity<String>} method
* parameter the target type is String)
* @return the created method argument value
* @throws IOException if the reading from the request fails
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
*/
@SuppressWarnings("unchecked")
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
MediaType contentType = inputMessage.getHeaders().getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = methodParam.getDeclaringClass();
Map<TypeVariable, Type> map = GenericTypeResolver.getTypeVariableMap(contextClass);
Class<T> targetClass = (Class<T>) GenericTypeResolver.resolveType(targetType, map);
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(targetType, contextClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + targetType + "] as \"" +
contentType + "\" using [" + converter + "]");
}
return genericConverter.read(targetType, contextClass, inputMessage);
}
}
if (targetClass != null) {
if (converter.canRead(targetClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + targetClass.getName() + "] as \"" +
contentType + "\" using [" + converter + "]");
}
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
}
}
}
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
}