We can quickly develop the REST interface through Spring Boot, and we may also need to call the internal and external REST interface through Spring Boot to complete business logic during the implementation of the interface.
In Spring Boot, there are two common ways to call REST APIs, which are used to implement service calls through the built-in RestTemplate or develop your own http client tool.
RestTemplate has very powerful basic functions, but in some special scenarios, we may be more accustomed to using our own encapsulated tool classes, such as uploading files to distributed file systems, processing https requests with certificates, etc.
This article uses RestTemplate as an example to record several problems and solutions found during the use of RestTemplate to call the interface.
1. Introduction to RestTemplate
1. What is RestTemplate
The HttpClient we encapsulate ourselves usually has some template code, such as establishing a connection, constructing a request header and a request body, then parsing the response information based on the response, and finally closing the connection.
RestTemplate is a re-encapsulation of HttpClient in Spring, which simplifies the process of initiating HTTP requests and processing responses, with higher abstraction levels, reducing consumer template code, and making less redundant code.
Actually, if you think about many XXXTemplate classes under Spring Boot, they also provide various template methods, but the abstraction level is higher and more details are hidden.
By the way, Spring Cloud has a declarative service call Feign, which is implemented based on Netflix Feign, integrates Spring Cloud Ribbon and Spring Cloud Hystrix, and implements a declarative web service client definition method.
In essence, Feign is to encapsulate it again on the basis of RestTemplate, which helps us define and implement the definition of dependent service interfaces.
2. Common methods for RestTemplate
There are many request methods for common REST services, such as GET, POST, PUT, DELETE, HEAD, OPTIONS, etc. RestTemplate implements the most common method, and the most commonly used are Get and Post. You can refer to the source code when calling the API. Here are a few method definitions (GET, POST, DELETE):
methods
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables)public void delete(String url, Object... uriVariables)public void delete(URI url)
At the same time, you should pay attention to two more "flexible" methods exchange and execute.
The exchange exposed by RestTemplate is different from other interfaces:
(1) Allow the caller to specify the method of HTTP request (GET, POST, DELETE, etc.)
(2) Body and header information can be added to the request, and its content is described by the parameter 'HttpEntity<?>requestEntity'
(3) Exchange supports 'type containing parameters' (i.e. generic class) as return type, and this feature is described by 'ParameterizedTypeReference<T>responseType'.
RestTemplate all GET, POST and other methods, and the final call of the execute method is the execute method. The internal implementation of the excute method is to convert the String-format URI into java.net.URI, and then the doExecute method is called. The implementation of the doExecute method is as follows:
doExecute
/** * Execute the given method on the provided URI. * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ @Nullable protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for /"" + resource + "/": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }The doExecute method encapsulates template methods, such as creating connections, processing requests and answers, closing connections, etc.
When most people see this, they probably think that encapsulating a RestClient is just like this, right?
3. Simple call
Take a POST call as an example:
GoodsServiceClient
package com.power.demo.restclient;import com.power.demo.common.AppConst;import com.power.demo.restclient.clientrequest.ClientGetGoodsByGoodsIdRequest;import com.power.demo.restclient.clientresponse.ClientGetGoodsByGoodsIdResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;/** * Product REST interface client (for demo test) **/@Componentpublic class GoodsServiceClient { //The interface URL called by the service consumer is as follows: http://localhost:9090 @Value("${spring.power.serviceurl}") private String _serviceUrl; @Autowired private RestTemplate restTemplate; public ClientGetGoodsByGoodsIdResponse getGoodsByGoodsId(ClientGetGoodsByGoodsIdRequest request) { String svcUrl = getGoodsSvcUrl() + "/getinfobyid"; ClientGetGoodsByGoodsIdResponse response = null; try { response = restTemplate.postForObject(svcUrl, request, ClientGetGoodsByGoodsIdResponse.class); } catch (Exception e) { e.printStackTrace(); response = new ClientGetGoodsByGoodsIdResponse(); response.setCode(AppConst.FAIL); response.setMessage(e.toString()); } return response; } private String getGoodsSvcUrl() { String url = ""; if (_serviceUrl == null) { _serviceUrl = ""; } if (_serviceUrl.length() == 0) { return url; } if (_serviceUrl.substring(_serviceUrl.length() - 1, _serviceUrl.length()) == "/") { url = String.format("%sapi/v1/goods", _serviceUrl); } else { url = String.format("%s/api/v1/goods", _serviceUrl); } return url; }}In the demo, the RestTemplate.postForObject method is called directly, and the deserialization entity is converted into these RestTemplate internal encapsulation.
2. Summary of problems
1. no suitable HttpMessageConverter found for request type exception
This problem usually occurs when an incoming object is passed in postForObject for calls.
Analyze the RestTemplate source code. In the doWithRequest method of the HttpEntityRequestCallback class, if the messageConverters (this field will continue to be mentioned later) does not meet the logic that returns jumped (that is, there is no matching HttpMessageConverter), the above exception is thrown:
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()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue())); } } 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 instance of GenericHttpMessageConverter) { GenericHttpMessageConverter<Object> genericConverter = (GenericHttpMessageConverter<Object>) messageConverter; if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue())); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as /" + requestContentType + "/" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]"); } } genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); return; } } else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue())); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as /" + requestContentType + "/" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "]"); } } ((HttpMessageConverter<Object>) messageConverter).write( requestBody, requestContentType, httpRequest); return; } } String message = "Could not write request: no suitable HttpMessageConverter found for request type [" + requestBodyClass.getName() + "]"; if (requestContentType != null) { message += " and content type [" + requestContentType + "]"; } throw new RestClientException(message); } } The easiest solution is to wrap the http request header and serialize the request object into a string to pass the parameters. The reference sample code is as follows:
postForObject
/* * Post request call* */ public static String postForObject(RestTemplate restTemplate, String url, Object params) { HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); String json = SerializeUtil.Serialize(params); HttpEntity<String> formEntity = new HttpEntity<String>(json, headers); String result = restTemplate.postForObject(url, formEntity, String.class); return result; }If we also want to directly return the object, we can directly deserialize the returned string:
postForObject
/* * Post request call* */ public static <T> T postForObject(RestTemplate restTemplate, String url, Object params, Class<T> clazz) { T response = null; String respStr = postForObject(restTemplate, url, params); response = SerializeUtil.DeSerialize(respStr, clazz); return response; }Among them, there are many tools for serialization and deserialization, such as fastjson, jackson and gson.
2. no suitable HttpMessageConverter found for response type exception
Just like an exception occurs when initiating a request, there will also be problems when handling the response.
Someone asked the same question on StackOverflow. The root cause is that the HTTP message converter HttpMessageConverter lacks MIME Type. That is to say, when HTTP transmits the output result to the client, the client must start the appropriate application to process the output document. This can be done through multiple MIME (Multifunctional Internet Mail Augmentation Protocol) Types.
For server-side responses, many HttpMessageConverter supports different media types (MIMETypes) by default. The default support for StringHttpMessageConverter is MediaType.TEXT_PLAIN, the default support for SourceHttpMessageConverter is MediaType.TEXT_XML, the default support for FormHttpMessageConverter is MediaType.APPLICATION_FORM_URLENCODED and MediaType.MULTIPART_FORM_DATA. In the REST service, the most we use is MappingJackson2HttpMessageConverter. , this is a relatively general converter (inherited from the GenericHttpMessageConverter interface). According to analysis, the MIMEType that it supports by default is MediaType.APPLICATION_JSON:
MappingJackson2HttpMessageConverter
/** * Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}. * You can use {@link Jackson2ObjectMapperBuilder} to build it easily. * @see Jackson2ObjectMapperBuilder#json() */ public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); }However, the default response of some application interfaces MIMEType is not application/json. For example, if we call an external weather forecast interface, if we use the default configuration of RestTemplate, it is no problem to directly return a string response:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=Shanghai";String result = restTemplate.getForObject(url, String.class);ClientWeatherResultVO vo = SerializeUtil.DeSerialize(result, ClientWeatherResultVO.class);
However, if we want to return an entity object directly:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=Shanghai";ClientWeatherResultVO weatherResultVO = restTemplate.getForObject(url, ClientWeatherResultVO.class);
Then report an exception directly:
Could not extract response: no suitable HttpMessageConverter found for response type [class ]
and content type [application/octet-stream]
Many people have encountered this problem. Most of them are confused when they first encounter it. Many interfaces are returned in json, xml or plain text format. What is application/octet-stream?
Check the RestTemplate source code and keep track of it all the way, you will find that the extractData method of the HttpMessageConverterExtractor class has a parsing response and deserialization logic. If it is not successful, the thrown exception information is the same as above:
HttpMessageConverterExtractor.extractData
@Override @SuppressWarnings({"unchecked", "rawtypes", "resource"}) public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } MediaType contentType = getContentType(responseWrapper); try { for (HttpMessageConverter<?> messageConverter : this.messageConverters) { if (messageConverter instance of GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter; if (genericMessageConverter.canRead(this.responseType, null, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseType + "] as /"" + contentType + "/" using [" + messageConverter + "]"); } return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); } } if (this.responseClass != null) { if (messageConverter.canRead(this.responseClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseClass.getName() + "] as /"" + contentType + "/" using [" + messageConverter + "]"); } return (T) messageConverter.read((Class) this.responseClass, responseWrapper); } } } } catch (IOException | HttpMessageNotReadableException ex) { throw new RestClientException("Error while extracting response for type [" + this.responseType + "] and content type [" + contentType + "]", ex); } throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " + "for response type [" + this.responseType + "] and content type [" + contentType + "]"); } The sample code of the solution on StackOverflow is acceptable, but it is not accurate. Common MIMETypes should be added. I will post the code I think is correct:
RestTemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.google.common.collect.Lists;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.Bean;import org.springframework.http.MediaType;import org.springframework.http.converter.*;import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;import org.springframework.http.converter.json.GsonHttpMessageConverter;import org.springframework.http.converter.json.JsonbHttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;import org.springframework.http.converter.xml.SourceHttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.util.ClassUtils;import org.springframework.web.client.RestTemplate;import java.util.Arrays;import java.util.List;@Componentpublic class RestTemplateConfig { private static final boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", RestTemplate .class.getClassLoader()); private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader()); private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader()); private static final boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", RestTemplate.class.getClassLoader()); private static final boolean jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", RestTemplate.class.getClassLoader()); private static final boolean jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", RestTemplate.class.getClassLoader()); private static final boolean gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader()); private static final boolean jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", RestTemplate.class.getClassLoader()); // Note when starting that since we inject RestTemplate into the service, we need to instantiate an instance of this class when starting @Autowired private RestTemplateBuilder builder; @Autowired private ObjectMapper objectMapper; // Use RestTemplateBuilder to instantiate the RestTemplate object. Spring has injected the RestTemplateBuilder instance by default @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); //Exception will appear without adding//Could not extract response: no suitable HttpMessageConverter found for response type [class ] MediaType[] mediaTypes = new MediaType[]{ MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON_UTF8, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_STREAM_JSON, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_PDF, }; converter.setSupportedMediaTypes(Arrays.asList(mediaTypes)); //messageConverters.add(converter); if (jackson2Present) { messageConverters.add(converter); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { messageConverters.add(new JsonbHttpMessageConverter()); } messageConverters.add(new FormHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new ResourceHttpMessageConverter(false)); messageConverters.add(new SourceHttpMessageConverter()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (jackson2XmlPresent) { messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2SmilePresent) { messageConverters.add(new MappingJackson2SmileHttpMessageConverter()); } if (jackson2CborPresent) { messageConverters.add(new MappingJackson2CborHttpMessageConverter()); } restTemplate.setMessageConverters(messageConverters); return restTemplate; }}After seeing the above code and comparing the internal implementation of RestTemplate, you will know that I have referred to the source code of RestTemplate. People who are obsessed with cleanliness may say that this piece of code is a bit verbose. The above bunch of static final variables and messageConverters fill data methods expose the implementation of RestTemplate. If RestTemplate is modified, it will also be modified here, which is very unfriendly and does not look OO at all.
After analysis, RestTemplateBuilder.build() constructs the RestTemplate object. Just modify the supported MediaType with the internal MappingJackson2HttpMessageConverter. Although the messageConverters field of RestTemplate is private final, we can still modify it through reflection. The improved code is as follows:
RestTemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.google.common.collect.Lists;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.Bean;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;import java.lang.reflect.Field;import java.util.Arrays;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;@Componentpublic class RestTemplateConfig { // Note when starting that because we inject RestTemplate into the service, we need to instantiate an instance of the class when starting @Autowired private RestTemplateBuilder builder; @Autowired private ObjectMapper objectMapper; // Use RestTemplateBuilder to instantiate the RestTemplate object. Spring has injected the RestTemplate instance by default @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); //Exception may occur if no addition//Could not extract response: no suitable HttpMessageConverter found for response type [class ] MediaType[] mediaTypes = new MediaType[]{ MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_STREAM_JSON, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_PDF, }; converter.setSupportedMediaTypes(Arrays.asList(mediaTypes)); try { //Set MessageConverters through reflection Field field = restTemplate.getClass().getDeclaredField("messageConverters"); field.setAccessible(true); List<HttpMessageConverter<?>> orgConverterList = (List<HttpMessageConverter<?>>) field.get(restTemplate); Optional<HttpMessageConverter<?>> opConverter = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName())) .findFirst(); if (opConverter.isPresent() == false) { return restTemplate; } messageConverters.add(converter);//Add MappingJackson2HttpMessageConverter //Add the original remaining HttpMessageConverter List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName()) == false) .collect(Collectors.toList()); messageConverters.addAll(leftConverters); System.out.println(String.format("【HttpMessageConverter】Original quantity: %s, reconstructed quantity: %s", orgConverterList.size(), messageConverters.size())); } catch (Exception e) { e.printStackTrace(); } restTemplate.setMessageConverters(messageConverters); return restTemplate; }}Apart from a messageConverters field, it seems that we no longer care about the external dependency packages and internal construction processes of RestTemplate. As expected, it is much clean and concise and easy to maintain.
3. Garbage code problem
This is also a very classic question. The solution is very simple, find HttpMessageConverter and take a look at the default supported Charset. AbstractJackson2HttpMessageConverter is a base class for many HttpMessageConverter, and the default encoding is UTF-8:
AbstractJackson2HttpMessageConverter
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;}StringHttpMessageConverter is quite special. Some people have reported that the garbled problem is caused by the encoding ISO-8859-1 that it supports by default:
StringHttpMessageConverter
/** * Implementation of {@link HttpMessageConverter} that can read and write strings. * * <p>By default, this converter supports all media types ({@code }), * and writes with a {@code Content-Type} of {@code text/plain}. This can be overridden * by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 3.0 */public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> { public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; /** * A default constructor that uses {@code "ISO-8859-1"} as the default charset. * @see #StringHttpMessageConverter(Charset) */ public StringHttpMessageConverter() { this(DEFAULT_CHARSET); }}If garbled code occurs during use, we can set the encoding supported by HttpMessageConverter through methods, commonly used ones include UTF-8, GBK, etc.
4. Deserialization exception
This is another problem that is easy to encounter during the development process. Because there are so many open source frameworks and tools in Java and the version changes frequently, unexpected pitfalls often occur.
Take joda time as an example. joda time is a popular java time and date framework, but if your interface exposes the type of joda time, such as DateTime, then the interface caller (isomorphic and heterogeneous systems) may encounter serialization problems, and even throw the following exceptions directly during deserialization:
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.joda.time.Chronology]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.joda.time.Chronology` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contains additional type information
at [Source: (PushbackInputStream);
I encountered this in the previous factory, and later, for the convenience of calling, I changed back to the Date type that directly exposed Java.
Of course, there are more than this solution. You can use jackson to support the serialization and deserialization of custom classes. In systems with not very high accuracy requirements, implement simple DateTime custom serialization:
DateTimeSerializer
package com.power.demo.util;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import java.io.IOException;/** * By default, jackson will serialize joda time into a more complex form, which is not conducive to reading and has a larger object. * <p> * When serializing JodaTime, datetime can be serialized into a string, which is easier to read**/public class DateTimeSerializer extends JsonSerializer<DateTime> { private static DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); @Override public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeString(value.toString(dateFormatter)); }} And DateTime deserialization:
DatetimeDeserializer
package com.power.demo.util;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonNode;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import java.io.IOException;/** * JodaTime Deserialization converts strings into datetime **/public class DatetimeDeserializer extends JsonDeserializer<DateTime> { private static DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); @Override public DateTime deserialize(JsonParser jp, DeserializationContext context) throws IOException, JsonProcessingException { JsonNode node = jp.getCodec().readTree(jp); String s = node.asText(); DateTime parse = DateTime.parse(s, dateFormatter); return parse; }}Finally, you can summarize common calling problems in the RestTemplateConfig class, and you can refer to it as follows:
RestTemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module.SimpleModule;import com.google.common.collect.Lists;import com.power.demo.util.DateTimeSerializer;import com.power.demo.util.DatetimeDeserializer;import org.joda.time.DateTime;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.Bean;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;import java.lang.reflect.Field;import java.util.Arrays;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;@Componentpublic class RestTemplateConfig { // Be careful when starting that because we inject RestTemplate into the service, we need to instantiate an instance of this class when starting @Autowired private RestTemplateBuilder builder; @Autowired private ObjectMapper objectMapper; // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例@Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); //注册model,用于实现jackson joda time序列化和反序列化SimpleModule module = new SimpleModule(); module.addSerializer(DateTime.class, new DateTimeSerializer()); module.addDeserializer(DateTime.class, new DatetimeDeserializer()); objectMapper.registerModule(module); List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); //不加会出现异常//Could not extract response: no suitable HttpMessageConverter found for response type [class ] MediaType[] mediaTypes = new MediaType[]{ MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_STREAM_JSON, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_PDF, }; converter.setSupportedMediaTypes(Arrays.asList(mediaTypes)); try { //通过反射设置MessageConverters Field field = restTemplate.getClass().getDeclaredField("messageConverters"); field.setAccessible(true); List<HttpMessageConverter<?>> orgConverterList = (List<HttpMessageConverter<?>>) field.get(restTemplate); Optional<HttpMessageConverter<?>> opConverter = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName())) .findFirst(); if (opConverter.isPresent() == false) { return restTemplate; } messageConverters.add(converter);//添加MappingJackson2HttpMessageConverter //添加原有的剩余的HttpMessageConverter List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName()) == false) .collect(Collectors.toList()); messageConverters.addAll(leftConverters); System.out.println(String.format("【HttpMessageConverter】原有数量:%s,重新构造后数量:%s" , orgConverterList.size(), messageConverters.size())); } catch (Exception e) { e.printStackTrace(); } restTemplate.setMessageConverters(messageConverters); return restTemplate; }}目前良好地解决了RestTemplate常用调用问题,而且不需要你写RestTemplate帮助工具类了。
上面列举的这些常见问题,其实.NET下面也有,有兴趣大家可以搜索一下微软的HttpClient常见使用问题,用过的人都深有体会。更不用提RestSharp 这个开源类库,几年前用的过程中发现了非常多的Bug,到现在还有一个反序列化数组的问题困扰着我们,我只好自己造个简单轮子特殊处理,给我最深刻的经验就是,很多看上去简单的功能,真的碰到了依然会花掉不少的时间去排查和解决,甚至要翻看源码。所以,我们写代码要认识到,越是通用的工具,越需要考虑到特例,可能你需要花80%以上的精力去处理20%的特殊情况,这估计也是满足常见的二八定律吧。
refer to:
https://stackoverflow.com/questions/21854369/no-suitable-httpmessageconverter-found-for-response-type
https://stackoverflow.com/questions/40726145/rest-templatecould-not-extract-response-no-suitable-httpmessageconverter-found
https://stackoverflow.com/questions/10579122/resttemplate-no-suitable-httpmessageconverter
http://forum.spring.io/forum/spring-projects/android/126794-no-suitable-httpmessageconverter-found
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.