RestTemplate请求时报错No HttpMessageConverter "application/x-www-form-urlencoded"

8,850 阅读1分钟

问题

没有找到对应的HttpMessageConverter

org.springframework.web.client.RestClientException: No HttpMessageConverter for java.util.Map and content type "application/x-www-form-urlencoded"
	at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:1000)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:774)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:602)

报错位置

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

寻错

  1. 查看RestTemplate配置, 使用RestTemplateBuilder进行创建的, 查看build方法,其中执行了new RestTemplate()
RestTemplateBuilder builder = new RestTemplateBuilder();
StringHttpMessageConverter m = new StringHttpMessageConverter();
RestTemplate restTemplate = builder
                .additionalMessageConverters(m)
                .build();
// 其中build方法中
public RestTemplate build() {
    return configure(new RestTemplate());
}
  1. RestTemplate的转换器在new RestTemplate()时就添加了AllEncompassingFormHttpMessageConverter转换器,按理说可以处理content type为"application/x-www-form-urlencoded"
// RestTemplate()构造函数中
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

content type为"application/x-www-form-urlencoded"对应的转换器都继承自FormHttpMessageConverter, 其子类有AllEncompassingFormHttpMessageConverter

  1. 查看configure方法, 此处替换了原来的MessageConverters
if (!CollectionUtils.isEmpty(this.messageConverters)) {
    restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters));
}
  1. 因为builder中只添加了一个转换器,所以RestTemplate中也只有一个,而这个转换器并不能处理content type为"application/x-www-form-urlencoded"

  2. 修改后还是报类似的错, 查看FormHttpMessageConverter中的canWrite方法原来body必须要MultiValueMap类型的

public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
        // 此处
	if (!MultiValueMap.class.isAssignableFrom(clazz)) {
		return false;
	}
	if (mediaType == null || MediaType.ALL.equals(mediaType)) {
		return true;
	}
	for (MediaType supportedMediaType : getSupportedMediaTypes()) {
		if (supportedMediaType.isCompatibleWith(mediaType)) {
			return true;
		}
	}
	return false;
}

原因

  1. 使用RestTemplateBuilder创建RestTemplate时,移除了所有默认的转换器,只保留了自定义配置的转换器(前提: 添加了转换器到builder中)
  2. content type为"application/x-www-form-urlencoded"时body的类型必须是MultiValueMap类型

解决

RestTempalte配置转换器

// 直接new, 不使用builder
RestTemplate restTemplate = new RestTemplate();

body的类型由Map变成MultiValueMap

HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(datam, reqHeaders);
ResponseEntity<JsonNode> responseEntity = restTemplate.exchange(initUrl, HttpMethod.POST, requestEntity, JsonNode.class);