记录一次RestTemplate使用报错

记录一次RestTemplate使用报错

当时情况是这样的,我要用RestTemplate调用一个接口上传文件,报了一个错误,如下图所示。

模拟上传接口

报错截图

分析

报错了,那我们就要一步步来看看到底是怎么报的这个错。

上代码。

首先是RestTemplate类

public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
			super.doWithRequest(httpRequest);
			Object requestBody = this.requestEntity.getBody();
			if (requestBody == null) {
				/**
				* 省略
				*/
			}
			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) { // 这里上来就是一个FastJsonHttpMessageConverter
						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;
						}
					}
			}
			/**
			* 省略下面代码
			*/
		}

到这里,我们继续来看FastJsonHttpMessageConverter.write()都做了什么事

public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        super.write(o, contentType, outputMessage); //⚠️注意,这里contentType是null
    }

继续,org.springframework.http.converter.AbstractHttpMessageConverter#write

public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType); // 这里报错
		
		/**
		* 省略
		*/
	}

继续,org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders

protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throws IOException {
		if (headers.getContentType() == null) {
			MediaType contentTypeToUse = contentType;
			if (contentType == null || !contentType.isConcrete()) {
				contentTypeToUse = getDefaultContentType(t); // 由于我们没有设置Content-Type,所以这里会去获取默认的Content-Type
			}
			else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
				MediaType mediaType = getDefaultContentType(t);
				contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
			}
			if (contentTypeToUse != null) {
				if (contentTypeToUse.getCharset() == null) {
					Charset defaultCharset = getDefaultCharset();
					if (defaultCharset != null) {
						contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
					}
				}
				headers.setContentType(contentTypeToUse); //这里设置Content-Type就报错了
			}
		}
		/**
		* 省略
		*/
	}

那我们来看看,上面这里设置默认Content-Type出了什么问题。
org.springframework.http.converter.AbstractHttpMessageConverter#getDefaultContentType

protected MediaType getDefaultContentType(T t) throws IOException {
        List<MediaType> mediaTypes = this.getSupportedMediaTypes();
        return !mediaTypes.isEmpty() ? (MediaType)mediaTypes.get(0) : null; // 可以看到获取的是第一个支持的MediaType
    }

那么问题就来了,第一个支持的MediaType是什么呢?我们来看下我们的设置

HttpMessageConverters httpMessageConverter() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteBigDecimalAsPlain,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteMapNullValue
        );
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastConverter.setFastJsonConfig(fastJsonConfig);
        List<MediaType> mediaTypeList = new ArrayList<>();
        mediaTypeList.add(MediaType.ALL); // 只有一个,就是所有,“*/*”
        fastConverter.setSupportedMediaTypes(mediaTypeList);
        return new HttpMessageConverters(fastConverter);
    }

到这里我们知道了,上面获取默认MediaType获取的就是“*/*”。
好,那我们继续,拿到默认类型了,就要设置了。org.springframework.http.HttpHeaders#setContentType

public void setContentType(@Nullable MediaType mediaType) {
		if (mediaType != null) {
			Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'"); // 就是这里类型判断为“*”导致报错
			Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
			set(CONTENT_TYPE, mediaType.toString());
		}
		else {
			remove(CONTENT_TYPE);
		}
	}

呼,终于找到报错原因了,没想到竟然是FastJsonHttpMessageConverter设置偷懒设置了ALL导致的问题。

解决

问题找到了总要解决的,根据上面的分析,报错的原因就是Content-Type没有设置导致的,那么就设置一下好了。这里就有两种思路,第一种就是请求的时候设置Content-Type,也就不会出现后面获取默认的Conten-Type了,第二种就是修改FastJsonHttpMessageCenverter,不要使用ALL这种,而是使用具体的类型,但是这种永远都是取第一个类型,有点太僵硬,不推荐。

题外话

FastJsonHttpMessageConverter支持的Content-Type不要设置为ALL,比如这里的使用RestTemplate上传文件,即使加上了Content-Type为MultiPart_FORM_DATA,依然会出其他问题。最终还是要修改FastJsonMessageConverter的Content-Type。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值