Follow me for the latest knowledge, classic interview questions and micro service technology sharing

In micro services, REST services call each other is very common, we how to elegantly call, in fact, in the Spring framework using RestTemplate class can elegantly call rest services, it simplifies the way of communication with HTTP services, unified RESTful standards, encapsulating HTTP links, It is easy to use, and you can customize the patterns required by the RestTemplate. Among them:

  • RestTemplateUse the defaultHttpMessageConverterInstance willHTTPMessage conversion toPOJOOr fromPOJOConverted toHTTPThe message. The master is registered by defaultmimeType of converter, but can also passsetMessageConvertersRegister custom converters.
  • RestTemplateThe default is usedDefaultResponseErrorHandlerFor 40 xBad RequestOr 50 xinternalabnormalerrorSuch as error message capture.
  • RestTemplateYou can also use interceptorsinterceptor, for request link tracking, and unified head Settings.

RestTemplate also defines a number of REST resource interaction methods, most of which correspond to HTTP methods as follows:

methods parsing
delete() Perform an HTTP DELETE operation on a resource at a specific URL
exchange() Performs a specific HTTP method on the URL to return the ResponseEntity containing the object
execute() Executes a specific HTTP method on the URL that returns an object mapped from the response body
getForEntity() Send an HTTP GET request that returns ResponseEntity containing the object mapped to the response body
getForObject() Send an HTTP GET request, and the returned request body is mapped to an object
postForEntity() POST data to a URL and return ResponseEntity containing an object
postForObject() POST data to a URL that returns an object based on the response body match
headForHeaders() Send an HTTP HEAD request that returns an HTTP header containing the URL of a particular resource
optionsForAllow() Send an HTTP OPTIONS request that returns the Allow header for a specific URL
postForLocation() POST data to a URL that returns the URL of the newly created resource
put() PUT a resource to a specific URL

1. The RestTemplate source code

1.1 Default Link Invocation

restTemplateWhen making API calls, the default call chain is:

###########1. CreateRequest ######## resttemplate->execute()->doExecute() HttpAccessor->createRequest() / / get Interceptor Interceptor, InterceptingClientHttpRequestFactory, SimpleClientHttpRequestFactory InterceptingHttpAccessor - > getRequestFactory () / / get the default SimpleBufferingClientHttpRequest SimpleClientHttpRequestFactory->createRequest() #######2. Get a response the response for processing # # # # # # # # # # # AbstractClientHttpRequest - > the execute () - > executeInternal () AbstractBufferingClientHttpRequest->executeInternal() ###########3. Exception handling # # # # # # # # # # # # # # # # # # # # # resttemplate - > handleResponse () # # # # # # # # # # 4. Response message body to encapsulate into Java objects # # # # # # # HttpMessageConverterExtractor - > extractData ()Copy the code

1.2 restTemplate – > the doExecute ()

In the default call chain, the restTemplate API calls the doExecute method, which performs the following steps:

1) Create the request using createRequest, get the response 2) determine if the response is abnormal, handle the exception 3) encapsulate the response message body as a Java object

@Nullable protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "URI is required"); Assert.notNull(method, "HttpMethod is required"); ClientHttpResponse response = null; Try {// create a request with createRequest ClientHttpRequest request = createRequest(URL, method); if (requestCallback ! = null) { requestCallback.doWithRequest(request); } response = request.execute(); // Handle handleResponse(url, method, response); Return (responseExtractor! = null ? responseExtractor.extractData(response) : 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(); }}}Copy the code

1.3 InterceptingHttpAccessor – > getRequestFactory ()

In the default invocation chain, InterceptingHttpAccessor getRequestFactory () method, if there is no set interceptor interceptor, it returns the default SimpleClientHttpRequestFactory, on the other hand, Returns the InterceptingClientHttpRequestFactory requestFactory, can pass resttemplate. SetInterceptors set custom interceptors interceptor.

//Return the request factory that this accessor uses for obtaining client request handles. public ClientHttpRequestFactory getRequestFactory () {/ / for the interceptor interceptor (custom) List < ClientHttpRequestInterceptor > interceptors = getInterceptors(); if (! CollectionUtils.isEmpty(interceptors)) { ClientHttpRequestFactory factory = this.interceptingRequestFactory; if (factory == null) { factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors); this.interceptingRequestFactory = factory; } return factory; } else { return super.getRequestFactory(); }}Copy the code

Then call the createRequest SimpleClientHttpRequestFactory create connections:

@Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); }}Copy the code

1.4 resttemplate – > handleResponse ()

In the default call chain, The RestTemplate handleResponse has response handling, including exception handling, and exception handling can be done by calling setErrorHandler methods to set custom ErrorHandler to identify and handle request response exceptions. Custom ErrorHandler to realize ResponseErrorHandler interface, at the same time, the Spring boot DefaultResponseErrorHandler also provides a default implementation, Therefore, you can also implement your own ErrorHandler by inheriting this class.

DefaultResponseErrorHandler default of 40 x Bad Request or 50 x abnormal internal error error information capture, etc. If you want to catch exception information thrown by the service itself, you implement the ErrorHandler of the RestTemplate itself.

ResponseErrorHandler errorHandler = getErrorHandler(); HasError = errorHandler. HasError (response); if (logger.isDebugEnabled()) { try { int code = response.getRawStatusCode(); HttpStatus status = HttpStatus.resolve(code); logger.debug("Response " + (status ! = null ? status : code)); }catch (IOException ex) {// ignore}} if (hasError) {errorHandler.handleError(url, method, response); }}Copy the code

1.5 HttpMessageConverterExtractor – > extractData ()

In the default invocation chain, HttpMessageConverterExtractor extractData in response message body encapsulation for Java object, you need to use the message converter, can be increased by means of additional custom messageConverter: Get the existing messageConverter and add the custom messageConverter.

RestTemplate setMessageConverters (); restTemplate setMessageConverters ();

public void setMessageConverters(List<HttpMessageConverter<? >> messageConverters) {// Validate validateConverters(messageConverters); // Take getMessageConverters() List as-is when passed in here if (this.messageConverters ! = messageConverters) {/ / to clear the original messageConverter enclosing messageConverters. The clear (); / / after loading to redefine messageConverter enclosing messageConverters. AddAll (messageConverters); }}Copy the code

HttpMessageConverterExtractor extractData source code:

MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (! responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } MediaType ContentType = getContentType(responseWrapper); For (HttpMessageConverter<? > messageConverter : This.messageconverters) {// Matches the responseType and contentType parameters set to responseType. Choose the appropriate MessageConverter if (MessageConverter instanceof GenericHttpMessageConverter) {GenericHttpMessageConverter <? > genericMessageConverter = (GenericHttpMessageConverter<? >) messageConverter; if (genericMessageConverter.canRead(this.responseType, null, contentType)) { if (logger.isDebugEnabled()) { ResolvableType resolvableType = ResolvableType.forType(this.responseType); logger.debug("Reading to [" + resolvableType + "]"); } return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); } } if (this.responseClass ! = null) { if (messageConverter.canRead(this.responseClass, contentType)) { if (logger.isDebugEnabled()) { String className = this.responseClass.getName(); logger.debug("Reading to [" + className + "] as \"" + contentType + "\""); } return (T) messageConverter.read((Class) this.responseClass, responseWrapper); } } } } ..... }Copy the code

1.6 Relationship between contentType and messageConverter

In HttpMessageConverterExtractor extractData method, can choose according to contentType and responseClass messageConverter is readable, message transformation. The relationship is as follows:

The name of the class Support the JavaType Support the MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
StringHttpMessageConverter String text/plain, */*
ResourceHttpMessageConverter Resource * / *
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<? >> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization; charset=UTF-8
FastJsonHttpMessageConverter Object * / *

2. Springboot integrates with the RestTemplate

You can easily and elegantly use the RestTemplate in your project by adding custom exception handling, MessageConverter, and interceptor interceptors. This article uses a sample demo, see the next section for details.

2.1. Import dependencies :(RestTemplate is integrated with Web Start)

<! -- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> The < version > 2.2.0. RELEASE < / version > < / dependency > <! -- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> < artifactId > lombok < / artifactId > < version > 1.18.10 < / version > < scope > provided < / scope > < / dependency >Copy the code

2.2. RestTemplat Configuration:

  • useClientHttpRequestFactoryProperty configures the RestTemplat parameters, for exampleConnectTimeout.ReadTimeout;
  • Add custominterceptorInterceptors and exception handling;
  • additionalmessageConverter;
  • Configure custom exception handling.


@Configuration public class RestTemplateConfig { @Value("${resttemplate.connection.timeout}") private int restTemplateConnectionTimeout; @Value("${resttemplate.read.timeout}") private int restTemplateReadTimeout; @Bean //@LoadBalanced public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) { RestTemplate restTemplate = new RestTemplate(); // Configure a custom message converter List<HttpMessageConverter<? >> messageConverters = restTemplate.getMessageConverters(); messageConverters.add(new CustomMappingJackson2HttpMessageConverter()); restTemplate.setMessageConverters(messageConverters); / / configure custom interceptor interceptor List < ClientHttpRequestInterceptor > interceptors = new ArrayList < ClientHttpRequestInterceptor > (); interceptors.add(new HeadClientHttpRequestInterceptor()); interceptors.add(new TrackLogClientHttpRequestInterceptor()); restTemplate.setInterceptors(interceptors); / / configure a custom exception handling restTemplate setErrorHandler (new CustomResponseErrorHandler ()); restTemplate.setRequestFactory(simleClientHttpRequestFactory); return restTemplate; } @Bean public ClientHttpRequestFactory simleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory(); reqFactory.setConnectTimeout(restTemplateConnectionTimeout); reqFactory.setReadTimeout(restTemplateReadTimeout); return reqFactory; }}Copy the code

2.3. Components (custom exception handling, Interceptor interceptor, Message converter)

The custominterceptorInterceptor, implementationClientHttpRequestInterceptorinterface

  • The customTrackLogClientHttpRequestInterceptorTo recordresttemplatetherequestandresponseInformation that can be tracked and analyzed;
  • The customHeadClientHttpRequestInterceptorTo set the parameters of the request header. The API sends various requests, many of which require similar or identical Http headers. If you start every request with aHeaderFill in theHttpEntity/RequestEntitySuch code is redundant and can be set up uniformly in interceptors.

TrackLogClientHttpRequestInterceptor:

/** * @auther: CCWW * @date: 2019/10/25 22:48, record restTemplate access information * @description: Record the resttemplate access information * / @ Slf4j public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { trackRequest(request,body); ClientHttpResponse httpResponse = execution.execute(request, body); trackResponse(httpResponse); return httpResponse; } private void trackResponse(ClientHttpResponse httpResponse)throws IOException { log.info("============================response begin=========================================="); log.info("Status code : {}", httpResponse.getStatusCode()); log.info("Status text : {}", httpResponse.getStatusText()); log.info("Headers : {}", httpResponse.getHeaders()); log.info("=======================response end================================================="); } private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException { log.info("======= request begin ========"); log.info("uri : {}", request.getURI()); log.info("method : {}", request.getMethod()); log.info("headers : {}", request.getHeaders()); log.info("request body : {}", new String(body, "UTF-8")); log.info("======= request end ========"); }}Copy the code

HeadClientHttpRequestInterceptor:

@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
       log.info("#####head handle########");
        HttpHeaders headers = httpRequest.getHeaders();
        headers.add("Accept", "application/json");
        headers.add("Accept-Encoding", "gzip");
        headers.add("Content-Encoding", "UTF-8");
        headers.add("Content-Type", "application/json; charset=UTF-8");
        ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
        HttpHeaders headersResponse = response.getHeaders();
        headersResponse.add("Accept", "application/json");
        return  response;
    }
}
Copy the code


Custom exception handling, inheritableDefaultResponseErrorHandlerOr implementationResponseErrorHandlerInterface:

  • Implement customizationErrorHandlerThe idea is to carry out the corresponding exception handling strategy according to the response message body, for other exceptions by the parent classDefaultResponseErrorHandlerTo process it.
  • The customCustomResponseErrorHandlerPerform 30X exception handling

CustomResponseErrorHandler:

/** * @Auther: Ccww * @Date: 2019/10/28 17:00 * @Description: 30 x exception handling * / @ Slf4j public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {@ Override public boolean hasError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = response.getStatusCode(); if(statusCode.is3xxRedirection()){ return true; } return super.hasError(response); } @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = response.getStatusCode(); If (statusCode is3xxRedirection ()) {the info (" # # # # # # # # 30 x error, need to redirect! # # # # # # # # # # "); return; } super.handleError(response); }}Copy the code


Custom Message converter

/** * @Auther: Ccww * @Date: 2019/10/29 21:15 * @Description: Will the content-type: text/HTML "" into the Map Type format * / public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { public CustomMappingJackson2HttpMessageConverter() { List<MediaType> mediaTypes = new ArrayList<MediaType>(); mediaTypes.add(MediaType.TEXT_PLAIN); mediaTypes.add(MediaType.TEXT_HTML); // Added support for text/ HTML types setSupportedMediaTypes(mediaTypes); // tag6 } }Copy the code

Is everyone still ok? If you like, move your hands to show 💗, point a concern!! Thanks for your support!

Welcome to pay attention to the public number [Ccww technology blog], original technical articles launched at the first time