(源码分析)RestTemplate POST 请求对方接收不到HashMap参数原因之一

本人运用RestTemplate联调一个上游接口,本地请求成功,放到测试环境就请求失败,报错是上游提示我这边没有传参。相同的代码,在不同的环境会报错,第一时间就是请求RestTemplate版本不一致。后来也去debug,结果如下:

//异常请求
14:32:04.685 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{proj_ids=["39***f"]}] using [org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@34f5090e]
{"body":"{\"retCode\":-1,\"errMsg\":\"缺少必要参数: proj_ids\"}}


//正常请求
14:34:58.477 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{proj_ids=["39****6f"]}] with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
{"body":"{\"retCode\":0,\"data\":{\"items\":[{\"id\":8,\"name\":\"1\"},{\"id\":9,\"name\":\"kkty团队\"}]}}"}

大家不难看出,其实debug结果已经给出答案。异常的报错是因为 RestTemplate Write的时候使用的是MappingJackson2XmlHttpMessageConverter,而正常MappingJackson2HttpMessageConverter,其实对应的是Content-Type问题,即:

Content-Type
MappingJackson2XmlHttpMessageConverterapplication/xml
MappingJackson2HttpMessageConverterapplication/json

到这里,我说明下,我联调正常请求的RestTemplate所JAR版本是 spring-web-5.2.10.RELEASE,而异常请求的是 spring-web-4.3.25.RELEASE。

下面就进入到源码看原因:

进过一系列debug,跟进到了HttpEntityRequestCallback.doWithRequest()方法

		@Override
		@SuppressWarnings("unchecked")
		public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
			super.doWithRequest(httpRequest);
			Object requestBody = this.requestEntity.getBody();
			if (requestBody == null) {//这里是判断如果请求体是空的
				HttpHeaders httpHeaders = httpRequest.getHeaders();
				HttpHeaders requestHeaders = this.requestEntity.getHeaders();
				if (!requestHeaders.isEmpty()) {
					requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
				}
				if (httpHeaders.getContentLength() < 0) {
					httpHeaders.setContentLength(0L);
				}
			}
			else {
				Class<?> requestBodyClass = requestBody.getClass();
				Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
						((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
				HttpHeaders httpHeaders = httpRequest.getHeaders();
				HttpHeaders requestHeaders = this.requestEntity.getHeaders();
				MediaType requestContentType = requestHeaders.getContentType();//这里是获取我们是否指定了Content-Type
                 /*重点是这里,getMessageConverters()方法是获取RestTemplate的转换器列表。
                   然后debug发现,spring-web-4.3.25.RELEASE版本首个遍历出来的是MappingJackson2XmlHttpMessageConverter,然后符合GenericHttpMessageConverter实例的对象。然后就走下面得逻辑去请求上游。而spring-web-5.2.10.RELEASE首个遍历出来的是MappingJackson2HttpMessageConverter。所以大家都懂了吧,接下来看下转换器如何添加进来这些转换器的。
                */
				for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
					if (messageConverter instanceof GenericHttpMessageConverter) {
						GenericHttpMessageConverter<Object> genericConverter =
								(GenericHttpMessageConverter<Object>) messageConverter;
						if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
							if (!requestHeaders.isEmpty()) {
								requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
							}
							logBody(requestBody, requestContentType, genericConverter);
							genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
							return;
						}
					}
					else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
						if (!requestHeaders.isEmpty()) {
							requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
						}
						logBody(requestBody, requestContentType, messageConverter);
						((HttpMessageConverter<Object>) messageConverter).write(
								requestBody, requestContentType, httpRequest);
						return;
					}
				}
				String message = "No HttpMessageConverter for " + requestBodyClass.getName();
				if (requestContentType != null) {
					message += " and content type \"" + requestContentType + "\"";
				}
				throw new RestClientException(message);
			}
		}


private void logBody(Object body, @Nullable MediaType mediaType, HttpMessageConverter<?> converter) {
			if (logger.isDebugEnabled()) {
				if (mediaType != null) {
					logger.debug("Writing [" + body + "] as \"" + mediaType + "\"");
				}
				else {
                    //这里是输出我们的参数是是使用了那种转换器
					logger.debug("Writing [" + body + "] with " + converter.getClass().getName());
				}
			}
		}

看到上面代码后,我们看下转换器如何添加的问题。我这边直接截取spring-web-5.2.10.RELEASE的RestTemplate源码分析。

	static {
		ClassLoader classLoader = RestTemplate.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);

        //spring-web-5.2.10.RELEASE以及spring-web-4.3.25.RELEASE jackson2Present 的值都是true
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
        /*spring-web-4.3.25.RELEASE jackson2XmlPresent 的值是false,spring-web-5.2.10.RELEASE jackson2XmlPresent 的值是true*/
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}

接下如截图所示,初始化 RestTemplate 的时候消息转换器messageConverters就会根据true/false将相关Content-Type转换器添加进去,遍历的时候就会将第一个加进去的Content-Type转换器遍历出来作为请求的Content-Type转换器类型。

 结合上来来看,说白了就是 不同的spring-web版本导致RestTemplate初始化的时候,将决定优先将哪个Content-Type转换器添加至消息转换器messageConverters。优先添加的那个Content-Type转换器就是本次请求的Content-Type转换器类型

解决办法:

就很简单了,直接RestTemplate执行请求的时候,指定Content-Type即可,而不是让系统默认。

其实debug的时候就知道问题了,但是还是多去看看源码,多了解下发生问题根本原因。今后开发或排查问题就会更有思路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值