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:
RestTemplate
Use the defaultHttpMessageConverter
Instance willHTTP
Message conversion toPOJO
Or fromPOJO
Converted toHTTP
The message. The master is registered by defaultmime
Type of converter, but can also passsetMessageConverters
Register custom converters.RestTemplate
The default is usedDefaultResponseErrorHandler
For 40 xBad Request
Or 50 xinternal
abnormalerror
Such as error message capture.RestTemplate
You 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
restTemplate
When 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:
- use
ClientHttpRequestFactory
Property configures the RestTemplat parameters, for exampleConnectTimeout
.ReadTimeout
; - Add custom
interceptor
Interceptors and exception handling; - additional
message
Converter; - 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 custominterceptor
Interceptor, implementationClientHttpRequestInterceptor
interface
- The custom
TrackLogClientHttpRequestInterceptor
To recordresttemplate
therequest
andresponse
Information that can be tracked and analyzed; - The custom
HeadClientHttpRequestInterceptor
To 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 aHeader
Fill in theHttpEntity/RequestEntity
Such 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, inheritableDefaultResponseErrorHandler
Or implementationResponseErrorHandler
Interface:
- Implement customization
ErrorHandler
The idea is to carry out the corresponding exception handling strategy according to the response message body, for other exceptions by the parent classDefaultResponseErrorHandler
To process it. - The custom
CustomResponseErrorHandler
Perform 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