Spring - RestTemplate執行原理分析


e8fd3caf5a180e7f9110639848ed548d.jpg

什么是RestTemplate

Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, and others.

這是RestTemplate源碼里對其自身的解釋,從類名來看把類想設計成一個標准的模板, 簡單來說就是簡化Http的請求以及響應的封裝,並且執行了Restful原則。如果沒有RestTemplate,我們可能使用更多的還是Apache HttpClient工具。 另外從這個定義里我看到一個很重要的類HttpUrlConnection,這是RestTemplate與HTTP服務器通信的核心類。

RestTempate類結構

截屏2020-08-1420.46.03.png
從類庫中可以看出,這個類是一個很Spring的設計,繼承抽象類InterceptingHttpAccessor,實現接口RestOperations。

RestTemplate執行流程圖

開始分析之前,我們以RestTemplate#getForObject()為例,先瀏覽一下整個方法調用過程的執行流程圖,幾個關鍵步驟,看到其中的幾個關鍵類,我會在后面詳細說明這幾個關鍵類。
SpringCloud RestTemplate 攔截器.jpg

RestTemplate構造函數

在看具體的執行http請求方法前,我們先看一下構造函數都做了那些工具。

public RestTemplate() {
    #設置各種messageConvert, 比如我們最常見的StringHttpMessageConverter。
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter(false));
    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 (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());
    }

    if (jackson2SmilePresent) {
        this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
    }
    if (jackson2CborPresent) {
        this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
    }
    #設置UriTemplateHandler
    this.uriTemplateHandler = initUriTemplateHandler();
}

構造方法總結來說只做了兩件事,添加HttpMessageConvert實現類,手動配置SpringMVC的時代,想必大家都知道HttpMessageConverter吧,顧名思義就是轉換HTTP請求響應過程中的消息數據。第二就是初始化UriTemplateHandler,在initUriTemplateHanlder()方法中可以看到實際實例化的是DefaultUriBuilderFactory類並返回。


RestTemplat#getForObject()

我們現在開始沿着getForObject入口來分析一下執行一個HTTP Rest請求的流程到底是怎樣的哈。

getForObject

#RestTemplate.getForObject
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
   RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
   HttpMessageConverterExtractor<T> responseExtractor =
		new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
   #這里傳入requestCallback和responseExtractor,調用execute()
   return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

execute()

#RestTemplate.execute
@Override
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {

  URI expanded = getUriTemplateHandler().expand(url, uriVariables);
  #調用doExecute()
  return doExecute(expanded, method, requestCallback, responseExtractor);
}

doExecute()

@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 {
        #1.生成請求
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
            #2.設置header
            requestCallback.doWithRequest(request);
        }
        #3.執行請求
        response = request.execute();
        #4.處理響應
        handleResponse(url, method, response);
        #5.返回執行結果
        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();
        }
    }
}

HttpAccessor

doExecute()方法的第一個步驟就是創建ClientHttpRequest, 這是一個接口,那么這個方法執行完會創建那個實現類呢? createRequest() 是父類HttpAccessor的方法,我們要先簡單分析下HttpAcessor。

package org.springframework.http.client.support;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpLogging;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInitializer;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.Assert;

public abstract class HttpAccessor {

    protected final Log logger = HttpLogging.forLogName(getClass());

	private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

	private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList<>();

	public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
		Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
		this.requestFactory = requestFactory;
	}

	public ClientHttpRequestFactory getRequestFactory() {
		return this.requestFactory;
	}

	public void setClientHttpRequestInitializers(
			List<ClientHttpRequestInitializer> clientHttpRequestInitializers) {

		if (this.clientHttpRequestInitializers != clientHttpRequestInitializers) {
			this.clientHttpRequestInitializers.clear();
			this.clientHttpRequestInitializers.addAll(clientHttpRequestInitializers);
			AnnotationAwareOrderComparator.sort(this.clientHttpRequestInitializers);
		}
	}
    
	public List<ClientHttpRequestInitializer> getClientHttpRequestInitializers() {
		return this.clientHttpRequestInitializers;
	}

    #在這個方法里會創建ClientHttpRequest實例並且返回。
	protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
		ClientHttpRequest request = getRequestFactory().createRequest(url, method);
		initialize(request);
		if (logger.isDebugEnabled()) {
			logger.debug("HTTP " + method.name() + " " + url);
		}
		return request;
	}

	private void initialize(ClientHttpRequest request) {
		this.clientHttpRequestInitializers.forEach(initializer -> initializer.initialize(request));
	}
}

從HttpAccessor#createRequest()分析來看,會先調用getRequestFactory() 返回_SimpleClientHttpRequestFactory,然后調用SimpleClientHttpRequestFactory.createRequest()。但是這里我們忽略了一個環節,那就是**InterceptingHttpAccessor, **_這里我們需要再深吸一口氣,再看看InterceptingHttpAccessor類。

SimpleClientHttpRequestFactory類結構圖

截屏2020-08-1421.53.41.png

InterceptingHttpAccessor

public abstract class InterceptingHttpAccessor extends HttpAccessor {
    
    #攔截器屬性,可以自定義攔截器實現業務邏輯,當然玩過SpringCloud Ribbon調用,對這個屬性再熟悉不過了。
	private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();

    #這個屬性要和HttpAccessor.requestFactory呼應一下。
	@Nullable
	private volatile ClientHttpRequestFactory interceptingRequestFactory;

	public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
		if (this.interceptors != interceptors) {
			this.interceptors.clear();
			this.interceptors.addAll(interceptors);
			AnnotationAwareOrderComparator.sort(this.interceptors);
		}
	}

	public List<ClientHttpRequestInterceptor> getInterceptors() {
		return this.interceptors;
	}

	@Override
	public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
		super.setRequestFactory(requestFactory);
		this.interceptingRequestFactory = null;
	}


	@Override
	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();
		}
	}
}

這個類的關鍵在於重寫了父類的getRequestFacotry(), 當前做過SpringCloud Ribbon遠程調用,會對這個方法格外熟悉,Spring會判斷攔截器屬性_interceptors是否有值,如果有值則會繼續判斷,然后返回InterceptionClientHttpRequestFactory, 如果攔截器屬性沒有值,則調用父類HttpAccessor#getRequestFactory().



RestTemplate#doExecute()

通過分析抽象父類InterceptingHttpAccessor和HttpAccessor, 我們得出結果,如果沒有攔截器,只是普通的RESTFUL調用,那么最終是調用SimpleClientHttpRequestFactory#createRequest().

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    #再回頭看看Spring對RestTemplate定義里提到的,RestTemplate的核心就是使用
    #HttpURLConnection和HTTP服務器進行通信。
    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);
    }
}

protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException {
	URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
    if (!HttpURLConnection.class.isInstance(urlConnection)) {
        throw new IllegalStateException("HttpURLConnection required for [" + url + "] but got: " + urlConnection);
    }
    return (HttpURLConnection) urlConnection;
}

protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
    if (this.connectTimeout >= 0) {
        connection.setConnectTimeout(this.connectTimeout);
    }
    if (this.readTimeout >= 0) {
        connection.setReadTimeout(this.readTimeout);
    }

    connection.setDoInput(true);

    if ("GET".equals(httpMethod)) {
        connection.setInstanceFollowRedirects(true);
    }
    else {
        connection.setInstanceFollowRedirects(false);
    }

    if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
        "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
        connection.setDoOutput(true);
    }
    else {
        connection.setDoOutput(false);
    }

	connection.setRequestMethod(httpMethod);
}

從這里代碼片段分析,我們可以回到上面提的問題, 在doExecute里createRequest()最終創建的實現類是SimpleBufferingClientHttpRequest。

SimpleBufferingClientHttpRequest類結構圖

截屏2020-08-1421.55.50.png

SimpleStreamingClientHttpRequest類結構圖

截屏2020-08-1421.56.54.png

requestCallback.doWithRequest()

根據自己需要實現接口RequestCallback#doWithRequest()。

request.execute()

這里也就是調用SimplezBufferingClientHttpRequest#execute()。通過分析SimplezBufferingClientHttpRequest代碼,我們知道execute()定義在抽象父類AbstractClientHttpRequest里

#AbstractClientHttpRequest#execute()
@Override
public final ClientHttpResponse execute() throws IOException {
    assertNotExecuted();
    ClientHttpResponse result = executeInternal(this.headers);
    this.executed = true;
    return result;
}

分析這個類可以看出,很經典的一個設計,父類定義行為,具體實現交給子類,這里可以看出execute里繼續調用executeInternal,這是一個抽象方法,交給子類實現。

AbstractBufferingClientHttpRequest#executeInternal

@Override
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;
}

SimpleBufferingClientHttpRequest#executeInternal

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    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()) {
        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);
}

至此,我們就拿到請求的返回結果Response了,封裝SimpleClientHttpResponse並返回。似乎這幾個類分析下來覺得他的調用流程並沒有想象的那么復雜,整個設計還是很規范。

handleResponse

RestTemplate#handleResponse

protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
    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 (hasError) {
        errorHandler.handleError(url, method, response);
    }
}

getErrorHandler獲取了一個錯誤處理器,如果Response的狀態碼是錯誤的,那么就調用handleError處理錯誤並拋出異常。

responseExtractor.extractData

ResponseExtractor是一個函數式接口,主要實現類有三個。

  • HeadersExtractor - RestTemplat的內部類。
  • ResponseEntityResponseExtractor - RestTemplate內部類。
  • HttpMessageConverterExtractor。

前兩個都是內部類,我們主要看一下HttpMessageConverterExtractor。

@Override
@SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {
    MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
    if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
        return null;
    }
    MediaType contentType = getContentType(responseWrapper);

    try {
        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 (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);
                }
            }
        }
    }
    catch (IOException | HttpMessageNotReadableException ex) {
        throw new RestClientException("Error while extracting response for type [" +
                                      this.responseType + "] and content type [" + contentType + "]", ex);
    }

    throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
                                  "for response type [" + this.responseType + "] and content type [" + contentType + "]");
}

可以看到,extractData先將response交給responseWrapper,如果responseWrapper有消息體且非空,則進行返回消息的讀取操作。
消息的讀取需要借助HttpMessageConverter接口,HttpMessageConverter具有多種實現類,以完成不同格式消息的讀取,相當於消息解碼器或轉換頭。

總結

RestTemplate提供了多種便捷訪問HTTP服務的方法,提高了客戶端的編碼效率。底層通過使用java.net包創建HTTP請求。主要使用ClientHttpRequestFactory指定不同的HTTP請求方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM