Several issues RestTemplate use in Spring Boot using RestTemplate consume REST services recorded a few questions

Spring Boot using RestTemplate consume REST services recorded a few questions

We can quickly develop a REST interface through Spring Boot, but also may need in the course of implementation of the interface, a complete business logic in the Spring Boot call external REST interface.

In Spring Boot, call REST Api common general there are two main ways, through the built-RestTemplate http or develop their own tools to achieve client service call.

RestTemplate basic function is very powerful, but some special scenes, we may still be more accustomed to using their own package of tools, such as uploading files to a distributed file system, processing with a certificate https requests, etc.

In this paper, RestTemplate for example, the use of several record RestTemplate call interface found during and solutions.

A, RestTemplate Profile

1. What is RestTemplate

Our own package HttpClient, usually some template code, such as establishing a connection, the request header and request body structure, then the response, parse the response information, and then close the connection.

Spring RestTemplate is again encapsulate the HttpClient, simplifies the process of initiating the HTTP request and response process, a higher level of abstraction, reduce consumer template code, so that less redundant code.

In fact, think about it a lot XXXTemplate class at Spring Boot, they also provide a variety of templates method, except higher level of abstraction, hiding it in more detail.

Incidentally, Spring Cloud has a declarative service call Feign, it is based Netflix Feign implementation, integration with Spring Cloud Ribbon Spring Cloud Hystrix, and to achieve a declarative way to define Web service client.

Essentially Feign is based RestTemplate again on its packaging, which it has to help us define and implement the definition of dependent services interface.

2, RestTemplate common method

Common REST service requests There are many ways, such as GET, POST, PUT, DELETE, HEAD, OPTIONS and so on. RestTemplate achieve the most common way to use the most is the Get and Post, calling the API can refer to the source code, here are a few method definition (GET, POST, DELETE):

Copy the code
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)
Copy the code

Note also that two more "flexible" approach to exchange and execute.

Different exposure RestTemplate exchange with the other interface:

(1) allows the caller to method (GET, POST, DELETE, etc.) specified in the HTTP request

(2) may be added in the request header and body information, the contents of the parameter 'HttpEntity <?> RequestEntity' described

(3) exchange support 'with a parameter of type' (i.e., generic class) as the return type, the characteristic by 'ParameterizedTypeReference <T> responseType' described.

RestTemplate all GET, POST method, etc., are ultimately call the execute method. Internal excute method of implementation is the URI String format turned into java.net.URI, after calling the doExecute methods to achieve doExecute method is as follows:

doExecute

doExecute method encapsulates the template method, such as creating a connection, processing requests and responses, and close the connection.

Most people see here, it is estimated will feel much better than it RestClient a package?

3, a simple call

POST to a call, for example:

Copy the code
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; /** * 商品REST接口客户端 (demo测试用) **/ @Component public class GoodsServiceClient { //服务消费者调用的接口URL 形如: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; } }
Copy the code

RestTemplate.postForObject demo in direct method calls to deserialize these entities transformed RestTemplate get inside the package.

Second, the problem summary

1、no suitable HttpMessageConverter found for request type异常

This problem is usually when incoming object in postForObject the call will appear.

Analysis RestTemplate source, the method doWithRequest HttpEntityRequestCallback class, if of MessageConverters (this field will continue to refer to later) during the list of fields in the loop processing logic does not satisfy the return jump (i.e. no matching HttpMessageConverter types), the abnormality is thrown :

Copy the code
        @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 instanceof 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 + "] using [" + messageConverter + "]"); } } ((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); } }
Copy the code

The simplest solution is to be requested by the first packaging http, and the request object into a string of serial transmission parameters, with reference to the following sample code:

Press Ctrl + C to copy the code
Press Ctrl + C to copy the code

If we want to return directly to the object, direct anti-serialized string to return:

Copy the code
    /*
     * Post请求调用
     * */
    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; }
Copy the code

Wherein the serialization and deserialization tools are more commonly used such fastjson, jackson and gson.

2、no suitable HttpMessageConverter found for response type异常

And a request to initiate an exception occurs, like when dealing with the response there will be a problem.

On StackOverflow was asked the same question, the root cause is the lack of HTTP message converter HttpMessageConverter MIME Type , that is to say when the HTTP output to the client, the client must start the appropriate application to handle the output document, which It can be accomplished by a variety of MIME (multi-purpose Internet Mail Extensions) Type.

For server-side response, many HttpMessageConverter default supported media types (MIMEType) are different. StringHttpMessageConverter supported by default is MediaType.TEXT_PLAIN, SourceHttpMessageConverter supported by default is MediaType.TEXT_XML, FormHttpMessageConverter supported by default is MediaType.APPLICATION_FORM_URLENCODED and MediaType.MULTIPART_FORM_DATA, in REST services, we use the most or MappingJackson2HttpMessageConverter, this is a More common converter (inherited from GenericHttpMessageConverter Interface), according to the analysis, which is supported by default MIMEType MediaType.APPLICATION_JSON:

MappingJackson2HttpMessageConverter

But some application interface is not the default response MIMEType application / json, such as we call an external interface to weather forecast, if RestTemplate default configuration, direct return a string answer is no problem:

String url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";
String result = restTemplate.getForObject(url, String.class);
ClientWeatherResultVO vo = SerializeUtil.DeSerialize(result, ClientWeatherResultVO.class);

However, if we want to return directly to an entity object:

String url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";

ClientWeatherResultVO weatherResultVO = restTemplate.getForObject(url, ClientWeatherResultVO.class);

则直接报异常:
Could not extract response: no suitable HttpMessageConverter found for response type [class ]
and content type [application/octet-stream]

A lot of people come across this problem, for the first time met most estimates are relatively ignorant of it, many interfaces are json or xml or plain text format to return, what is the application / octet-stream?

View RestTemplate source code, track all the way down will find extractData method HttpMessageConverterExtractor class has a resolution reply and de-serialization logic, if unsuccessful, throw the exception information and said the same:

Copy the code
    @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 instanceof 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 + "]"); }
Copy the code

Sample code to resolve on StackOverflow acceptable, but not accurate, common MIMEType should be added to the list, posted what I think the correct code:

Copy the code
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; @Component public 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()); //Start time to pay attention, because we inject in the service of RestTemplate, so start when the need to instantiate an instance of the class @Autowired Private RestTemplateBuilder Builder; @Autowired Private ObjectMapper ObjectMapper; // use RestTemplateBuilder RestTemplate to instantiate objects, spring examples have been implanted RestTemplateBuilder default @Bean public RestTemplate RestTemplate () = {RestTemplate RestTemplate builder.build (); List <HttpMessageConverter types <>> of MessageConverters =? Lists.newArrayList (); MappingJackson2HttpMessageConverter Converter = new new MappingJackson2HttpMessageConverter (); converter.setObjectMapper ( ObjectMapper); // without exception occurs //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 ByteArrayHttpMessageConverter()); 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; } }
Copy the code

See above code, then compare the internal RestTemplate achieve, I know I made reference to RestTemplate source, I got a little older people may say that this code is a little long-winded cook, above the pile static final variables and data messageConverters filling method, exposed the realization RestTemplate, if RestTemplate modified, you have to be changed here, very unfriendly and did not look OO.

After analysis, RestTemplateBuilder.build () RestTemplate constructed objects, as long as the internal MappingJackson2HttpMessageConverter MediaType can modify support, RestTemplate of messageConverters field, although private final, we can still modify it by reflex, code improvements are as follows:

Copy the code
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; @Component public class RestTemplateConfig { // 启动的时候要注意,由于我们在服务中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例  @Autowired private RestTemplateBuilder builder; @Autowired private ObjectMapper objectMapper; // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例  @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); 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; } }
Copy the code

除了一个messageConverters字段,看上去我们不再关心RestTemplate那些外部依赖包和内部构造过程,果然干净简洁好维护了很多。

3、乱码问题

这个也是一个非常经典的问题。解决方案非常简单,找到HttpMessageConverter,看看默认支持的Charset。AbstractJackson2HttpMessageConverter是很多HttpMessageConverter的基类,默认编码为UTF-8:

AbstractJackson2HttpMessageConverter

而StringHttpMessageConverter比较特殊,有人反馈过发生乱码问题由它默认支持的编码ISO-8859-1引起:

StringHttpMessageConverter

如果在使用过程中发生乱码,我们可以通过方法设置HttpMessageConverter支持的编码,常用的有UTF-8、GBK等。

4、反序列化异常

这是开发过程中容易碰到的又一个问题。因为Java的开源框架和工具类非常之多,而且版本更迭频繁,所以经常发生一些意想不到的坑。

以joda time为例,joda time是流行的java时间和日期框架,但是如果你的接口对外暴露joda time的类型,比如DateTime,那么接口调用方(同构和异构系统)可能会碰到序列化难题,反序列化时甚至直接抛出如下异常:

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 contain additional type information
 at [Source: (PushbackInputStream);

我在前厂就碰到过,可以参考这里,后来为了调用方便,改回直接暴露Java的Date类型。

当然解决的方案不止这一种,可以使用jackson支持自定义类的序列化和反序列化的方式。在精度要求不是很高的系统里,实现简单的DateTime自定义序列化:

DateTimeSerializer

And DateTime deserialization:

DatetimeDeserializer

Finally, it can be summarized in the treatment of common problems call RestTemplateConfig class, you can refer to the following:

Rest Template Config

Current good solution to the common problem of call RestTemplate, and does not require tools to help you write a RestTemplate.

These frequently asked questions listed above, the following is also .NET fact, we are interested can search using Microsoft's HttpClient common problem, people used all too well. Not to mention RestSharp this open-source library, a few years ago with the process found a lot of Bug, and now there is a deserialized array of problems plaguing us, I had to make myself a simple wheel special treatment, give me the most profound experience is that many seemingly simple function, actually met will still spend a lot of time to troubleshoot and resolve, or even to look at the source code. So, we write the code to realize that the more common tools, the need to take into account special cases, you may need to spend more than 80% of the energy to deal with 20% of the special circumstances, it is estimated that twenty-eight law to meet the common bar.

 

reference:

https://stackoverflow.com/questions/21854369/no-suitable-httpmessageconverter-found-for-response-type

Guess you like

Origin www.cnblogs.com/riverone/p/11976524.html