1. 程式人生 > >restTemplate原始碼詳解深入剖析底層實現思路

restTemplate原始碼詳解深入剖析底層實現思路

一 準備工作

1 啟動一個專案,釋出一個restful的get請求,埠設定為8090。

@RestController
@RequestMapping("/youku1327")
public class ProviderController {


    @GetMapping("user")
    public String getUser(){
        return "youku1327";
    }
    @GetMapping("user/{name}")
    public String getUserName(@PathVariable String name){
        return name;
    }

2 新建一個專案配置restTemplate

/**
 * @Author lsc
 * @Description <p> </p>
 * @Date 2019/10/14 11:40
 * @Version 1.0
 */
@Configuration
public class RestTemplateConfig {

    // 配置 RestTemplate
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        // 建立一個 httpCilent 簡單工廠
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        // 設定連線超時
        factory.setConnectTimeout(15000);
        // 設定讀取超時
        factory.setReadTimeout(5000);
        return factory;
    }
}

3 在新專案下寫個測試類使用restTemplate呼叫介面

    @Autowired
    RestTemplate restTemplate;

    /* 
     * @Author lsc 
     * @Description  <p> URL帶參 </p>
     * @Date 2019/10/18 13:49 
     * @Param []
     * @return void
     **/
    @Test
    public void testGETParams(){
        // http://localhost:8090/youku1327/user/{1}
        String result = restTemplate.getForObject("http://localhost:8090/youku1327/user/{name}", String.class,"lsc");
        System.out.println(result);
    }

二 實現架構圖

三 原始碼分析

(1) 點選原始碼進入getForObject方法。

引數列表:

  • url :地址路徑
  • responseType:返回值型別
  • uriVariables: uri變數
@Nullable
    public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
        // request回撥函式
        RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
        // HttpMessageConverterExtractor是對訊息轉換器裡面的資訊進行提取
        HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor(responseType,        this.getMessageConverters(), this.logger);
        // 執行restful過程呼叫
        return this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, (Object[])uriVariables);
    }

(2) 對 RequestCallback 進行分析

2.1 acceptHeaderRequestCallback

點選進入acceptHeaderRequestCallback方法,在次期間,其建立了一個RestTemplate物件,並且呼叫了
AcceptHeaderRequestCallback類裡面AcceptHeaderRequestCallback方法。

 public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
        return new RestTemplate.AcceptHeaderRequestCallback(responseType);
    }

2.2建立RestTemplate物件發生的事情

在建立RestTemplate過程中主要是封裝了7種messageConverters ,errorHandler ,logger,uriTemplateHandler,headersExtractor,requestFactory,Interceptors.最重要的還是messageConverters ,本次示例採用的String型別的返回值,那麼對應的就是StringHttpMessageConverter.

 public RestTemplate() {
        this.messageConverters = new ArrayList();
        this.errorHandler = new DefaultResponseErrorHandler();
        this.headersExtractor = new RestTemplate.HeadersExtractor();
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        this.messageConverters.add(new ResourceHttpMessageConverter(false));

        try {
            this.messageConverters.add(new SourceHttpMessageConverter());
        } catch (Error var2) {
            ;
        }

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

        this.uriTemplateHandler = initUriTemplateHandler();
    }

2.3RestTemplate內部類AcceptHeaderRequestCallback

AcceptHeaderRequestCallback實現了RequestCallback,其中AcceptHeaderRequestCallback構造方法裡面主要是對返回值型別(這裡是java.lang.String型別)賦值,裡面還有一個方法(實現函式式介面void doWithRequest(ClientHttpRequest var1) throws IOException;)主要是是做一些request相關的事情,比如封裝responseType和messageConverters ,判定response是否可讀,支援的媒體型別等。

       public AcceptHeaderRequestCallback(@Nullable Type responseType) {
            this.responseType = responseType;
        }

(3)HttpMessageConverterExtractor

HttpMessageConverterExtractor是一個HttpMessageConverter的資訊提取器,裡面對於返回值型別,訊息轉換器進行斷言非空判定,封裝日誌資訊等。

構造器:

HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, Log logger) {
        Assert.notNull(responseType, "'responseType' must not be null");
        Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
        this.responseType = responseType;
        this.responseClass = responseType instanceof Class ? (Class)responseType : null;
        this.messageConverters = messageConverters;
        this.logger = logger;
    }

(4)execute

excute方法主要做了url的規則判定和解析重組,request,response封裝,請求資源資訊,輸入流轉為我們指定返回值型別的資料。

   @Nullable
    public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
       // 會對uri進行解析重組 http://localhost:8090/youku1327/user/lsc
        URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
       // 
        return this.doExecute(expanded, method, requestCallback, responseExtractor);
    }

4.1進入getUriTemplateHandler方法

// 獲得uriTemplateHandler
public UriTemplateHandler getUriTemplateHandler() {
        return this.uriTemplateHandler;
    }
// UriTemplateHandler介面
public interface UriTemplateHandler {
    URI expand(String var1, Map<String, ?> var2);

    URI expand(String var1, Object... var2);
}
// uri擴充套件資訊
public URI expand(String uriTemplate, Object... uriVars) {
        // uriTemplate = http://localhost:8090/youku1327/user/{name}
        return this.uriString(uriTemplate).build(uriVars);
    }

4.1.1uriString

到了expand方法要分兩步看,第一個是預設的uri構造器(DefaultUriBuilderFactory),這個過程會對進行解析,把scheme(http),host(localhost),port(8090)等資訊從uil中解析出來,把路徑的資訊封裝進pathBuilder(這邊是path存放的是/youku1327/user/{name}).

// DefaultUriBuilderFactory
    public UriBuilder uriString(String uriTemplate) {
        return new DefaultUriBuilderFactory.DefaultUriBuilder(uriTemplate);
    }
// 這裡走的是預設的uri構建器(DefaultUriBuilder) 
   public DefaultUriBuilder(String uriTemplate) {
            this.uriComponentsBuilder = this.initUriComponentsBuilder(uriTemplate);
        }
// initUriComponentsBuilder 初始化 UriComponentsBuilder 解析uri
private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
            UriComponentsBuilder result;
            if (StringUtils.isEmpty(uriTemplate)) {
                result = DefaultUriBuilderFactory.this.baseUri != null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder() : UriComponentsBuilder.newInstance();
            } else if (DefaultUriBuilderFactory.this.baseUri != null) {
                UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
                UriComponents uri = builder.build();
                result = uri.getHost() == null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder().uriComponents(uri) : builder;
            } else {
            // 
                result = UriComponentsBuilder.fromUriString(uriTemplate);
            }

            if (DefaultUriBuilderFactory.this.encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES)) {
                result.encode();
            }

            this.parsePathIfNecessary(result);
            return result;
        }

// UriComponentsBuilder構造器 裡面封裝了uri解析的資訊
 protected UriComponentsBuilder(UriComponentsBuilder other) {
        this.charset = StandardCharsets.UTF_8;
        this.scheme = other.scheme;
        this.ssp = other.ssp;
        this.userInfo = other.userInfo;
        this.host = other.host;
        this.port = other.port;
        this.pathBuilder = other.pathBuilder.cloneBuilder();
        this.queryParams.putAll(other.queryParams);
        this.fragment = other.fragment;
        this.encodeTemplate = other.encodeTemplate;
        this.charset = other.charset;
    }
    

4.1.2 build

uriString初始化完畢,第二步回到之前的expand方法裡面執行build方法,這邊會初始化uriComponentsBuilder

public URI build(Object... uriVars) {
            if (ObjectUtils.isEmpty(uriVars) && !DefaultUriBuilderFactory.this.defaultUriVariables.isEmpty()) {
                return this.build(Collections.emptyMap());
            } else {
                if (DefaultUriBuilderFactory.this.encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY)) {
                    // uriVars
                    uriVars = UriUtils.encodeUriVariables(uriVars);
                }
                // http://localhost:8090/youku1327/user/lsc
                UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars);
                // 返回URI物件 http://localhost:8090/youku1327/user/lsc
                return this.createUri(uric);
            }
        }

public UriComponents build() {
        return this.build(false);
    }

    public UriComponents build(boolean encoded) {
        Object result;
        if (this.ssp != null) {
            result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
        } else {
             // 具有層次結構的uri也就是解析後的uri
            HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
            // http://localhost:8090/youku1327/user/{name}
            result = this.encodeTemplate ? uric.encodeTemplate(this.charset) : uric;
        }

        if (!this.uriVariables.isEmpty()) {
            result = ((UriComponents)result).expand((name) -> {
                return this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE);
            });
        }

        return (UriComponents)result;
    }

uriComponentsBuilder初始化完畢會執行expand

public final UriComponents expand(Object... uriVariableValues) {
        Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
        return this.expandInternal(new UriComponents.VarArgsTemplateVariables(uriVariableValues));
    }

4.2 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;

        Object var14;
        try {
            // 建立request 
            ClientHttpRequest request = this.createRequest(url, method);
            if (requestCallback != null) {
                // request資訊封裝
                requestCallback.doWithRequest(request);
            }
            // 執行execute獲得response
            response = request.execute();
            // response資訊校驗
            this.handleResponse(url, method, response);
            // response資訊提取
            var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
        } catch (IOException var12) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
            throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
        } finally {
            if (response != null) {
                response.close();
            }

        }
        // 返回值 
        return var14;
    }

4.2.1 createRequest

 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        // 建立 HttpURLConnection sun.net.www.protocol.http.HttpURLConnection:http://localhost:8090/youku1327/user/lsc
        HttpURLConnection connection = this.openConnection(uri.toURL(), this.proxy);
        // 準備連線操作
        this.prepareConnection(connection, httpMethod.name());
        // 建立 ClientHttpRequest 
        return (ClientHttpRequest)(this.bufferRequestBody ? new SimpleBufferingClientHttpRequest(connection, this.outputStreaming) : new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming));
    }
//  HttpAccessor 中 createRequest
 protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = this.getRequestFactory().createRequest(url, method);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("HTTP " + method.name() + " " + url);
        }

        return request;
    }

prepareConnection準備連線操作,設定連線的超時,讀取超時資訊,設定是否是輸入,設定請求方式(示例是GET)。

 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(false);
        } else {
            connection.setDoOutput(true);
        }

        connection.setRequestMethod(httpMethod);
    }
 

SimpleBufferingClientHttpRequest繼承了AbstractClientHttpRequest,在此期間會跳至LinkedMultiValueMap,HttpHeaders。

   // 
 SimpleBufferingClientHttpRequest(HttpURLConnection connection, boolean outputStreaming) {
        // 連線資訊
        this.connection = connection;
        // 判定是請求輸出 outputStreaming=true
        this.outputStreaming = outputStreaming;
    }

4.2.2doWithRequest

這期間是封裝request的body,head,connection,如果你使用的是postForEntity測試會更加明顯

 public void doWithRequest(ClientHttpRequest request) throws IOException {
            if (this.responseType != null) {
                List<MediaType> allSupportedMediaTypes = (List)RestTemplate.this.getMessageConverters().stream().filter((converter) -> {
                    return this.canReadResponse(this.responseType, converter);
                }).flatMap(this::getSupportedMediaTypes).distinct().sorted(MediaType.SPECIFICITY_COMPARATOR).collect(Collectors.toList());
                if (RestTemplate.this.logger.isDebugEnabled()) {
                    RestTemplate.this.logger.debug("Accept=" + allSupportedMediaTypes);
                }

                request.getHeaders().setAccept(allSupportedMediaTypes);
            }

        }

4.2.3 request.execute()

就是執行request的executeInternal方法,獲得response

    public final ClientHttpResponse execute() throws IOException {
        this.assertNotExecuted();
        ClientHttpResponse result = this.executeInternal(this.headers);
        this.executed = true;
        // 返回 ClientHttpResponse
        return result;
    }
    // AbstractBufferingClientHttpRequest類 拿到 ClientHttpResponse
    protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
        byte[] bytes = this.bufferedOutput.toByteArray();
        if (headers.getContentLength() < 0L) {
            headers.setContentLength((long)bytes.length);
        }

        ClientHttpResponse result = this.executeInternal(headers, bytes);
        this.bufferedOutput = new ByteArrayOutputStream(0);
        return result;
    }

4.2.4 responseExtractor.extractData(response)

提取response資訊

public T extractData(ClientHttpResponse response) throws IOException {
        MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
       // 這裡很重要就是從這裡獲得輸入流
        if (responseWrapper.hasMessageBody() && !responseWrapper.hasEmptyMessageBody()) {
            MediaType contentType = this.getContentType(responseWrapper);

            try {
                Iterator var4 = this.messageConverters.iterator();

                while(var4.hasNext()) {
                    HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var4.next();
                    if (messageConverter instanceof GenericHttpMessageConverter) {
                        GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter)messageConverter;
                        if (genericMessageConverter.canRead(this.responseType, (Class)null, contentType)) {
                            if (this.logger.isDebugEnabled()) {
                                ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                                this.logger.debug("Reading to [" + resolvableType + "]");
                            }

                            return genericMessageConverter.read(this.responseType, (Class)null, responseWrapper);
                        }
                    }

                    if (this.responseClass != null && messageConverter.canRead(this.responseClass, contentType)) {
                        if (this.logger.isDebugEnabled()) {
                            String className = this.responseClass.getName();
                            this.logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
                        }
                        // 將responseWrapper裡面的資訊讀取
                        return messageConverter.read(this.responseClass, responseWrapper);
                    }
                }
            } catch (HttpMessageNotReadableException | IOException var8) {
                throw new RestClientException("Error while extracting response for type [" + this.responseType + "] and content type [" + contentType + "]", var8);
            }

            throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found for response type [" + this.responseType + "] and content type [" + contentType + "]");
        } else {
            return null;
        }
    }
// 1 判定中會呼叫getBody方法
public boolean hasEmptyMessageBody() throws IOException {
        // 呼叫
        InputStream body = this.response.getBody();
        if (body.markSupported()) {
            body.mark(1);
            if (body.read() == -1) {
                return true;
            } else {
                body.reset();
                return false;
            }
        } else {
            this.pushbackInputStream = new PushbackInputStream(body);
            int b = this.pushbackInputStream.read();
            if (b == -1) {
                return true;
            } else {
                this.pushbackInputStream.unread(b);
                return false;
            }
        }
    }
// 2 獲得輸入流
  public InputStream getBody() throws IOException {
        return (InputStream)(this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
    }    
    // 3 SimpleClientHttpResponse 類  getBody 方法
    public InputStream getBody() throws IOException {
        InputStream errorStream = this.connection.getErrorStream();
        // 呼叫了connection的getInputStream方法
        this.responseStream = errorStream != null ? errorStream : this.connection.getInputStream();
        return this.responseStream;
    }

4.2.5 read

後面就比較簡單將獲得輸入流轉為字串

    // 1 read方法
 public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return this.readInternal(clazz, inputMessage);
    }
    // 2 呼叫readInternal方法
      protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
       // 設定字符集
       Charset charset = this.getContentTypeCharset(inputMessage.getHeaders().getContentType());
       // 將輸入流轉為字串
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }
    
    //  3 將輸入流轉為字串
     public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException {
        if (in == null) {
            return "";
        } else {
            StringBuilder out = new StringBuilder();
            InputStreamReader reader = new InputStreamReader(in, charset);
            char[] buffer = new char[4096];
            boolean var5 = true;

            int bytesRead;
            while((bytesRead = reader.read(buffer)) != -1) {
                out.append(buffer, 0, bytesRead);
            }
            // lsc
            return out.toString();
        }
    }

四 總結

restTemplate的getForObject方法的本質其實就是HttpURLConnection進行資源的呼叫,在此期間它會幫我們進行uri的校驗,引數封裝,頭資訊放置,其次會建立request,response,然後封裝請求頭,響應頭資訊,最終將獲得的輸入流通過工具類轉換為我們引數指定的返回型別的值。

五 原理驗證和restTemplate實現思路

既然restTemplate底層是HttpURLConnection實現我們試驗一下這種方式呼叫,發現確實呼叫成功,程式碼清單如下,如果自己要手動寫一個restTemplate,那麼你只要寫好request,response用於封裝請求和響應資訊,,uri解析重組合法驗證,然後HttpURLConnection獲取流,經過工具類解析流就成功了。

@Test
    public void testURLConnection() throws IOException {

        getMethod("http://localhost:8090/youku1327/user",null);
    }

    public void getMethod(String url, String query) throws IOException {
        URL restURL = new URL(url);
        // 獲得一個URLConnection
        HttpURLConnection conn = (HttpURLConnection) restURL.openConnection();
        // 設定為 GET請求
        conn.setRequestMethod("GET");
        // 設定請求屬性
        conn.setRequestProperty("Content-Type", "text/plain");
        // 表示輸出
        conn.setDoOutput(true);
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        br.close();
    }