RestTemplate source code analysis

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.

Keywords: Java source code analysis

Added by poleposters on Wed, 29 Dec 2021 14:11:42 +0200