Follow me to get the latest knowledge, classic interview questions and technical sharing
. Among them:
- RestTemplate uses HttpMessageConverter instance by default to convert HTTP message to POJO or from POJO to HTTP message. By default, converters of the primary mime type are registered, but custom converters can also be registered through setMessageConverters.
- RestTemplate uses the default DefaultResponseErrorHandler to capture error information such as 40X Bad Request or 50X internal exception error.
- RestTemplate can also use interceptor interceptor to track request links and unify head settings.
<br>
Among them, RestTemplate also defines many REST resource interaction methods, most of which correspond to HTTP methods, as follows:
Method | analysis |
---|---|
delete() | HTTP DELETE on a specific URL for a resource |
exchange() | Execute a specific HTTP method on the URL to return the ResponseEntity containing the object |
execute() | Execute a specific HTTP method on the URL to return an object mapped from the response body |
getForEntity() | Send an HTTP GET request, and the returned ResponseEntity contains the object mapped by the response body |
getForObject() | Send an HTTP GET request, and the returned request body will be mapped to an object |
postForEntity() | POST data to a URL, return the ResponseEntity containing an object |
postForObject() | POST data to a URL, return the object formed according to the response body matching |
headForHeaders() | Send HTTP HEAD request, return HTTP header containing specific resource URL |
optionsForAllow() | Send HTTP OPTIONS request, return Allow header information for specific URL |
postForLocation() | POST data to a URL, return the URL of the newly created resource |
put() | PUT resource to specific URL |
1. RestTemplate source code
1.1 default call link
When restTemplate makes API calls, the default call chain is:
###########1.Use createRequest Create request######## resttemplate->execute()->doExecute() HttpAccessor->createRequest() //Get Interceptor, intercepting clienthttprequestfactory, SimpleClientHttpRequestFactory InterceptingHttpAccessor->getRequestFactory() //Get the default SimpleBufferingClientHttpRequest SimpleClientHttpRequestFactory->createRequest() #######2.Get response response Processing########### AbstractClientHttpRequest->execute()->executeInternal() AbstractBufferingClientHttpRequest->executeInternal() ###########3.exception handling##################### resttemplate->handleResponse() ##########4.The response message body is encapsulated as java object####### HttpMessageConverterExtractor->extractData()
1.2 restTemplate->doExecute()
In the default call chain, when restTemplate makes API calls, it will call the doExecute method. The main steps of this method are as follows:
1) use createRequest to create a request and get a response
2) judge whether the response is abnormal and deal with it
3) encapsulate the response message body as a java object
[@Nullable](https://my.oschina.net/u/2896689) protected <T> T doExecute(URI url, [@Nullable](https://my.oschina.net/u/2896689) HttpMethod method, [@Nullable](https://my.oschina.net/u/2896689) RequestCallback requestCallback, [@Nullable](https://my.oschina.net/u/2896689) ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "URI is required"); Assert.notNull(method, "HttpMethod is required"); ClientHttpResponse response = null; try { //Create a request using createRequest ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } //Get response for processing response = request.execute(); //exception handling handleResponse(url, method, response); //The response message body is encapsulated as a java object 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(); } } }
1.3 InterceptingHttpAccessor->getRequestFactory()
In the default call chain, if the interceptor blocker is not set in the getRequestFactory() method of InterceptingHttpAccessor, the default SimpleClientHttpRequestFactory will be returned. Otherwise, the requestFactory of InterceptingClientHttpRequestFactory will be returned. The user-defined interceptor can be set through resttemplate.setInterceptors.
//Return the request factory that this accessor uses for obtaining client request handles. public ClientHttpRequestFactory getRequestFactory() { //Get 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(); } }
Then call createRequest of SimpleClientHttpRequestFactory to create the connection:
[@Override](https://my.oschina.net/u/1162528) 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); } }
1.4 resttemplate->handleResponse()
In the default call chain, the handleResponse of resttemplate, response handling, including exception handling, and exception handling can be realized by calling setErrorHandler method to set a user-defined ErrorHandler to distinguish and handle the request response exception. The custom ErrorHandler needs to implement the ResponseErrorHandler interface, and Spring boot also provides the default implementation of DefaultResponseErrorHandler, so you can also implement your own ErrorHandler by inheriting this class.
DefaultResponseErrorHandler captures error information such as 40X Bad Request or 50X internal exception error by default. If you want to catch the exception information thrown by the service itself, you need to implement the ErrorHandler of the RestTemplate by yourself.
ResponseErrorHandler errorHandler = getErrorHandler(); //Judge whether the response is abnormal boolean 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 } } //Exception handling in case of exception if (hasError) { errorHandler.handleError(url, method, response); } }
1.5 HttpMessageConverterExtractor->extractData()
In the default call chain, the response message body in the extractData of HttpMessageConverterExtractor is encapsulated as a java object. You need to use the message converter. You can add a custom messageConverter by appending: first get the existing messageConverter, and then add the custom messageConverter.
According to the source code of setMessageConverters of restTemplate, the original messageconverters can be prevented from being lost by adding. Source code:
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) { //test validateConverters(messageConverters); // Take getMessageConverters() List as-is when passed in here if (this.messageConverters != messageConverters) { //Clear the original messageConverter first this.messageConverters.clear(); //Load the redefined messageConverter after this.messageConverters.addAll(messageConverters); } }
The extractData source code of HttpMessageConverterExtractor:
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } //Get ContentType type of response MediaType contentType = getContentType(responseWrapper); try { //Loop the messageConverter in turn to determine whether the conversion conditions are met and convert the java object for (HttpMessageConverter<?> messageConverter : this.messageConverters) { //According to the set return type responseType and contentType parameters, the appropriate MessageConverter will be selected 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); } } } } ..... }
1.6 relationship between contenttype and messageConverter
From the extractData method of HttpMessageConverterExtractor, it can be seen that whether the messageConverter is readable or not and message conversion will be selected according to the contentType and responseClass. The relationship is as follows:
Class name | Supported javatypes | Supported mediatypes |
---|---|---|
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 integration RestTemplate
. This article uses the sample demo, which you can see later.
2.1. Import dependency: (RestTemplate is integrated in 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> <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>
2.2. RestTemplat configuration:
- Use the ClientHttpRequestFactory property to configure the RestTemplat parameter, such as ConnectTimeout and ReadTimeout;
- Add custom interceptor and exception handling;
- The message converter is added;
- Configure custom exception handling
<br>
@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 a custom interceptor List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>(); interceptors.add(new HeadClientHttpRequestInterceptor()); interceptors.add(new TrackLogClientHttpRequestInterceptor()); restTemplate.setInterceptors(interceptors); //Configure 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; } }
2.3. Components (custom exception handling, interceptor, message converter)
Custom interceptor to implement ClientHttpRequestInterceptor interface
- Customize TrackLogClientHttpRequestInterceptor to record the request and response information of resttemplate, which can be tracked and analyzed;
- Customize the HeadClientHttpRequestInterceptor, and set the parameters of the request Header. API sends various requests, many of which need to use similar or the same Http Header. If the Header is filled in HttpEntity/RequestEntity before each request, such code will appear very redundant and can be set uniformly in the interceptor.
<br>
TrackLogClientHttpRequestInterceptor:
/** * @Auther: ccww * @Date: 2019/10/25 22:48,Record resttemplate access information * @Description: Record 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 ========"); } }
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; } }
<br>
Custom exception handling can inherit DefaultResponseErrorHandler or implement the ResponseErrorHandler interface:
- The idea of implementing the user-defined ErrorHandler is to carry out the corresponding exception handling strategy according to the response message body. For other exceptions, the parent class DefaultResponseErrorHandler will handle them.
- Custom response error handler for 30x exception handling
<br>
CustomResponseErrorHandler:
/** * @Auther: Ccww * @Date: 2019/10/28 17:00 * @Description: 30X Exception handling for */ @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()){ log.info("########30X Error, redirection required!##########"); return; } super.handleError(response); } }
<br>
Custom message converter
/** * @Auther: Ccww * @Date: 2019/10/29 21:15 * @Description: Convert content type: "text / HTML" to 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); //Add text/html type support setSupportedMediaTypes(mediaTypes);// tag6 } }
Finally, I would like to ask you to give me a compliment, thank you for your support!!!