I believe RestTemplate is the most used http client in SpringBoot. Let's analyze its source code today.
1, Simplest usage
First, you need to inject the Bean instance of RestTemplate into the container
@Configuration public class RestTemplateConfig { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
Then use it directly
@RestController public class Controller { @Autowired RestTemplate restTemplate; @RequestMapping("/test") public String test() { String result = restTemplate.getForObject("https://www.baidu.com/", String.class); System.out.println(result); return result; } }
As you can see, RestTemplate simplifies the http request operation, and even only needs to pass in a url and response type to get the result.
2, RestTemplate class diagram structure
Class diagram structure is the most effective tool for us to intuitively understand a class. Many people may not know how to look at class diagram.
Just three simple steps
Then his class diagram structure is like this:
RestOperations is an interface, which means Rest operation. It abstracts operation methods with restful style. Our commonly used getForObject, postForEntity and exchange are the interface methods defined therein.
As shown in the figure:
HttpAccessor is an abstract class, which means Http accessor. It internally stores the requested factory class, that is, the clienthttprequefactory class. It uses createRequest to produce ClientHttpRequest. RestTemplate will finally construct the request into ClientHttpRequest, and ClientHttpRequest is responsible for interacting with the server. Remember this class first, which will be used later.
The createRequest method is as follows
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { //this.getRequestFactory() returns the default SimpleClientHttpRequestFactory ClientHttpRequest request = this.getRequestFactory().createRequest(url, method); this.initialize(request); if (this.logger.isDebugEnabled()) { this.logger.debug("HTTP " + method.name() + " " + url); } return request; }
InterceptingHttpAccessor is the implementation class of HttpAccessor. It mainly adds some logic about interceptors, which will not be repeated in this article.
3, Construction method of RestTemplate
Before looking at the source code, you can download the source code of the Spring framework. In view of the moving speed of github, you can download it from the code cloud https://gitee.com/mirrors/Spring-Framework For the version you are interested in, remember to install the Gradle environment first, Installation and configuration of Gradle environment Of course, idea also has its own Gradle.
The parameterless construction method is as follows:
public RestTemplate() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter(false)); if (!shouldIgnoreXml) { try { this.messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Error err) { // Ignore when no TransformerFactory implementation is available } } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { this.messageConverters.add(new AtomFeedHttpMessageConverter()); this.messageConverters.add(new RssChannelHttpMessageConverter()); } if (!shouldIgnoreXml) { if (jackson2XmlPresent) { this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); } else if (jaxb2Present) { this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } } if (jackson2Present) { this.messageConverters.add(new MappingJackson2HttpMessageConverter()); } else if (gsonPresent) { this.messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { this.messageConverters.add(new JsonbHttpMessageConverter()); } else if (kotlinSerializationJsonPresent) { this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter()); } if (jackson2SmilePresent) { this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter()); } if (jackson2CborPresent) { this.messageConverters.add(new MappingJackson2CborHttpMessageConverter()); } this.uriTemplateHandler = initUriTemplateHandler(); }
The construction method mainly does two things:
Many implementation classes of HttpMessageConverter have been added. Each implementation class of HttpMessageConverter mainly converts the text of request response into corresponding java objects. For more information about HttpMessageConverter, please refer to this article HttpMessageConverter
The uri template processor is initialized. This processor mainly splices the URIs. For example, in the get request, the param is spliced behind the url.
4, Core logic for executing requests
We take the initial code as an example and gradually enter the core logic.
restTemplate.getForObject("https://www.baidu.com/", String.class)
As you can see from the above, getForObject is a method in RestOperation, and the implementation of RestTemplate is:
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { //Get request callback RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); //Get http message transformation extractor HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); }
Enter the acceptHeaderRequestCallback(responseType)
public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) { return new AcceptHeaderRequestCallback(responseType); }
The AcceptHeaderRequestCallback instance is returned. The doWithRequest of this object will be used later and will be placed first.
HttpMessageConverterExtractor implements ResponseExtractor, which can convert the text data of http response into java object of response.
Then enter the execute method
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { //Splice uri URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); }
This is very simple. Go directly to doExecute
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 the ClientHttpRequest mentioned at the beginning of the article ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { //Execute request callback requestCallback.doWithRequest(request); } //Execute the request, and U obtains the response result response = request.execute(); //Processing response results handleResponse(url, method, response); //Use the response extractor to extract data and return predefined java objects, such as String in the example 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(); } } }
There are several methods involved:
createRequest(url, method)
The createRequest method in HttpAccessor is called here
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { //this.getRequestFactory() returns the default SimpleClientHttpRequestFactory ClientHttpRequest request = this.getRequestFactory().createRequest(url, method); this.initialize(request); if (this.logger.isDebugEnabled()) { this.logger.debug("HTTP " + method.name() + " " + url); } return request; }
The getRequestFactory method is overridden by InterceptingHttpAccessor (which exists in the class diagram structure)
public ClientHttpRequestFactory getRequestFactory() { 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(); } }
As you can see, we get interceptors first, but we don't set interceptors for this type. Therefore, the parent class, RequestFactory in HttpAccessor, is returned directly
public abstract class HttpAccessor { /** Logger available to subclasses. */ protected final Log logger = HttpLogging.forLogName(getClass()); private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); ..... }
The HttpAccessor uses the SimpleClientHttpRequestFactory class.
Then go to the createRequest method of SimpleClientHttpRequestFactory
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { //open a connection HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); //Preliminary preparation prepareConnection(connection, httpMethod.name()); //The default is true if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); } }
openConnection(uri.toURL(), this.proxy)
protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException { URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection()); if (!(urlConnection instanceof HttpURLConnection)) { throw new IllegalStateException( "HttpURLConnection required for [" + url + "] but got: " + urlConnection); } return (HttpURLConnection) urlConnection; }
At this point, you seem to understand something. The original RestTemplate encapsulates the native http request URLConnection.
prepareConnection(connection, httpMethod.name())
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { //Set connection timeout if (this.connectTimeout >= 0) { connection.setConnectTimeout(this.connectTimeout); } //Set read timeout if (this.readTimeout >= 0) { connection.setReadTimeout(this.readTimeout); } //Set request method, etc boolean mayWrite = ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)); connection.setDoInput(true); connection.setInstanceFollowRedirects("GET".equals(httpMethod)); connection.setDoOutput(mayWrite); connection.setRequestMethod(httpMethod); }
prepareConnection is relatively simple, and some simple settings are made. When we use URLConnection to make http requests, we must also set these settings. RestTemplate is just encapsulated
After the createRequest process is completed, a ClientHttpRequest object encapsulating URLConnection is returned
I copy the doExecute method again. On the one hand, I don't have to turn it up. On the other hand, I can deepen everyone's impression.
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 the ClientHttpRequest mentioned at the beginning of the article ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { //Execute request callback requestCallback.doWithRequest(request); } //Execute the request, and U obtains the response result response = request.execute(); //Processing response results handleResponse(url, method, response); //Use the response extractor to extract data and return predefined java objects, such as String in the example 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(); } } }
Next comes requestcallback doWithRequest (request), that is, the doWithRequest method that executes the request callback before executing the request
public void doWithRequest(ClientHttpRequest request) throws IOException { if (this.responseType != null) { List<MediaType> allSupportedMediaTypes = getMessageConverters().stream() .filter(converter -> canReadResponse(this.responseType, converter)) .flatMap((HttpMessageConverter<?> converter) -> getSupportedMediaTypes(this.responseType, converter)) .distinct() .sorted(MediaType.SPECIFICITY_COMPARATOR) .collect(Collectors.toList()); if (logger.isDebugEnabled()) { logger.debug("Accept=" + allSupportedMediaTypes); } request.getHeaders().setAccept(allSupportedMediaTypes); } }
The main logic of this code is to set the supported MediaType into the request header
Back to the main process, you need to really execute the call, that is, request execute(),
We learned earlier that this request is of type SimpleBufferingClientHttpRequest. Let's take a look at the class diagram structure of this class
The execute method is in the AbstractClientHttpRequest class
public final ClientHttpResponse execute() throws IOException { //Check whether ClientHttpRequest has been executed. If it has been executed, an error will be reported directly assertNotExecuted(); ClientHttpResponse result = executeInternal(this.headers); //After executing the request, set the flag bit to true this.executed = true; return result; }
Enter the executeInternal method, which is located in AbstractBufferingClientHttpRequest
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { byte[] bytes = this.bufferedOutput.toByteArray(); if (headers.getContentLength() < 0) { headers.setContentLength(bytes.length); } ClientHttpResponse result = executeInternal(headers, bytes); this.bufferedOutput = new ByteArrayOutputStream(0); return result; }
The core is the executeInternal(headers, bytes) method, which is located in SimpleBufferingClientHttpRequest
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { //Set the request header into the connection header addHeaders(this.connection, headers); // JDK <1.8 doesn't support getOutputStream with HTTP DELETE if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) { this.connection.setDoOutput(false); } if (this.connection.getDoOutput() && this.outputStreaming) { this.connection.setFixedLengthStreamingMode(bufferedOutput.length); } this.connection.connect(); if (this.connection.getDoOutput()) { //Copy the data from the cache into the output stream of the connection FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream()); } else { // Immediately trigger the request in a no-output scenario as well this.connection.getResponseCode(); } return new SimpleClientHttpResponse(this.connection); }
This method establishes a connection with the server using HttpURLConnection and returns a SimpleClientHttpResponse. Simply wrap this HttpURLConnection object.
request.execute() is finished, and then go to the next step of the main process
Paste it again
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 the ClientHttpRequest mentioned at the beginning of the article ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { //Execute request callback requestCallback.doWithRequest(request); } //Execute the request, and U obtains the response result response = request.execute(); //Processing response results handleResponse(url, method, response); //Use the response extractor to extract data and return predefined java objects, such as String in the example 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(); } } }
handleResponse(url, method, response)
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { //Get error handler ResponseErrorHandler errorHandler = getErrorHandler(); 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 } } //If an error occurs, for example, the status code is 500, enter the error handling process if (hasError) { errorHandler.handleError(url, method, response); } }
This step mainly checks whether the status code starts with 4 or 5. If so, follow the corresponding error handling process.
If there are no errors, go to the last step
responseExtractor. Extract data (response) to extract the data in the response
responseExtractor is of type httpmessageconvertereextractor
public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } MediaType contentType = getContentType(responseWrapper); try { //Traverse HttpMessageConverter for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 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 the messageConverter can read the contentType and convert it to the responseClass type if (messageConverter.canRead(this.responseClass, contentType)) { if (logger.isDebugEnabled()) { String className = this.responseClass.getName(); logger.debug("Reading to [" + className + "] as \"" + contentType + "\""); } //Convert the response data to the corresponding java type 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 UnknownContentTypeException(this.responseType, contentType, response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response)); }
extractData and core logic are to traverse all httpmessageconverters. If a messageConverter is found to be able to read the response data, it will return the converted data.
The last step of the main process is to close the input stream of the encapsulated HttpURLConnection.
Here, the source code analysis of RestTemplate is over.
To summarize RestTemplate in one sentence,
RestTemplate encapsulates the native HttpURLConnection and adopts the concept of Restful to call HTTP services more gracefully.