RestTemplate實踐

原文鏈接:https://www.jianshu.com/p/0fd5f3f64137

一、RestTemplate是什麼

環境約束:
spring-web-4.3.9.RELEASE

Spring文檔: https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/remoting.html#rest-client-access

The RestTemplate is the core class for client-side access to RESTful services. It is conceptually similar to other template classes in Spring, such as JdbcTemplate andJmsTemplate and other template classes found in other Spring portfolio projects.RestTemplate’s behavior is customized by providing callback methods and configuring the HttpMessageConverter used to marshal objects into the HTTP request body and to unmarshal any response back into an object. As it is common to use XML as a message format, Spring provides a MarshallingHttpMessageConverter that uses the Object-to-XML framework that is part of the org.springframework.oxm package. This gives you a wide range of choices of XML to Object mapping technologies to choose from.
This section describes how to use the RestTemplate and its associated HttpMessageConverters.

  • RestTemplate是Spring的通過客戶端訪問RESTful服務端的核心類,和JdbcTemplate、JmsTemplate概念相似,都是Spring提供的模板類
  • RestTemplate的行爲可以通過callback回調方法和配置HttpMessageConverter 來定製,用來把對象封裝到HTTP請求體,將響應信息放到一個對象中

Invoking RESTful services in Java is typically done using a helper class such as Apache HttpComponents HttpClient. For common REST operations this approach is too low level as shown below.
java中調用RESTful服務很典型的是使用HttpClient,對於常用的REST操作,這些方法屬於低等級的操作

String uri = "http://example.com/hotels/1/bookings";

PostMethod post = new PostMethod(uri);
String request = // create booking request content
post.setRequestEntity(new StringRequestEntity(request));

httpClient.executeMethod(post);

if (HttpStatus.SC_CREATED == post.getStatusCode()) {
    Header location = post.getRequestHeader("Location");
    if (location != null) {
        System.out.println("Created new booking at :" + location.getValue());
    }
}

使用HttpClient我們需要自己封裝Post請求,再根據響應的狀態碼判斷從響應中獲取header和body,有時候還需要自己做json轉換

RestTemplate provides higher level methods that correspond to each of the six main HTTP methods that make invoking many RESTful services a one-liner and enforce REST best practices.
RestTemplate提供更高等級的符合HTTP的6中主要方法,可以很簡單的調用RESTful服務

二、創建RestTemplate

創建RestTemplate很簡單,只需要new RestTemplate(),如果使用Spring架構,將創建的RestTemplate實例通過XML或註解的方式註冊到Spring容器中即可

舉例:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

RestTemplate構造方法

RestTemplate有3個構造方法,一個無參構造,兩個有參構造

RestTemplate無參構造

/**
 * Create a new instance of the {@link RestTemplate} using default settings.
 * Default {@link HttpMessageConverter}s are initialized.
 * 使用默認配置創建一個RestTemplate實例
 * 默認的HttpMessageConverter集合被初始化
 */
public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter());
    this.messageConverters.add(new SourceHttpMessageConverter<Source>());
    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());
    }

    /**
     * 如果類路徑下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
     * 使用jackson做http請求、響應的json轉換
     */
    if (jackson2Present) {
        this.messageConverters.add(new MappingJackson2HttpMessageConverter());
    }
    else if (gsonPresent) {
        this.messageConverters.add(new GsonHttpMessageConverter());
    }
}

參數爲ClientHttpRequestFactory的構造

/**
 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
 * @param requestFactory HTTP request factory to use
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory
 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
 * 使用指定的ClientHttpRequestFactory創建一個RestTemplate實例
 * requestFactory是用於創建HTTP請求的工廠,默認的實現有
 * SimpleClientHttpRequestFactory、HttpComponentsClientHttpRequestFactory
 * 如果沒有設置默認是SimpleClientHttpRequestFactory
 */
public RestTemplate(ClientHttpRequestFactory requestFactory) {
    this();  //也會調用無參構造初始化默認的messageConverters
    setRequestFactory(requestFactory);
}

參數爲messageConverters的構造

/**
 * Create a new instance of the {@link RestTemplate} using the given list of
 * {@link HttpMessageConverter} to use
 * @param messageConverters the list of {@link HttpMessageConverter} to use
 * @since 3.2.7
 * 傳入自定義的HttpMessageConverter集合,並賦值給messageConverters,之後使用自定義的HttpMessageConverter
 */
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
    Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
    this.messageConverters.addAll(messageConverters);
}

三、RestTemplate API使用

RestTemplate 4.3.9.RELEASE版本 API

HTTP Method RestTemplate Method
DELETE void delete(String url, Object… uriVariables)
void delete(String url, Map<String,?> uriVariables)
void delete(URI url)
GET T getForObject(String url, Class responseType, Object… uriVariables)
T getForObject(String url, Class responseType, Map<String,?> uriVariables)
T getForObject(URI url, Class responseType)

ResponseEntity getForEntity(String url, Class responseType, Object… uriVariables)
ResponseEntity getForEntity(String url, Class responseType, Map<String,?> uriVariables)
ResponseEntity getForEntity(URI url, Class responseType)
HEAD HttpHeaders headForHeaders(String url, Object… uriVariables)
HttpHeaders headForHeaders(String url, Map<String,?> uriVariables)
HttpHeaders headForHeaders(URI url)
OPTIONS Set optionsForAllow(String url, Object… uriVariables)
Set optionsForAllow(String url, Map<String,?> uriVariables)
Set optionsForAllow(URI url)
POST T postForObject(String url, Object request, Class responseType, Object… uriVariables)
T postForObject(String url, Object request, Class responseType, Map<String,?> uriVariables)
T postForObject(URI url, Object request, Class responseType)

ResponseEntity postForEntity(String url, Object request, Class responseType, Object… uriVariables)
ResponseEntity postForEntity(String url, Object request, Class responseType, Map<String,?> uriVariables)
ResponseEntity postForEntity(URI url, Object request, Class responseType)
PUT void put(String url, Object request, Object… uriVariables)
void put(String url, Object request, Map<String,?> uriVariables)
void put(URI url, Object request)
PATCH and others exchange()、execute()
  • RestTemplate的方法名遵循一定的命名規範,第一部分表示用哪種HTTP方法調用(get,post),第二部分表示返回類型

      -  getForObject() -- 發送GET請求,將HTTP response轉換成一個指定的object對象
      -  postForEntity() -- 發送POST請求,將給定的對象封裝到HTTP請求體,返回類型是一個HttpEntity對象
    
  • 每個HTTP方法對應的RestTemplate方法都有3種。其中2種的url參數爲字符串,URI參數變量分別是Object數組和Map,第3種使用URI類型作爲參數

    • 注意,使用字符串類型的url默認會對url進行轉義,如http://example.com/hotel list在執行時會轉義爲http://example.com/hotel%20list,這樣其實是沒有問題的,但如果字符串類型的url本身已經轉義過了,執行時就會再轉義一次,變成http://example.com/hotel%2520list。如果不需要這種隱式的轉義,可以使用java.net.URI參數的方法,這種方法不會在執行時存在隱式的url轉義,可以在創建URI對象時自行決定是否轉義,推薦使用UriComponentsBuilder創建URI
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}")
        .build() //build(true)就不會對url轉義,但如果包含http://example.com/hotel list這種需要轉義的url,會報錯
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();
  • exchange 和execute 方法比上面列出的其它方法(如getForObject、postForEntity等)使用範圍更廣,允許調用者指定HTTP請求的方法(GET、POST、PUT等),並且可以支持像HTTP PATCH(部分更新),但需要底層的HTTP庫支持,JDK自帶的HttpURLConnection不支持PATCH方法,Apache的HTTPClient 4.2及以後版本支持

GET方法

getForEntity()

發送GET請求,返回ResponseEntity

/**
 * 參數1: String類型 或 URI類型的請求地址
 * 參數2: 指定返回的實體類型,class對象
 * 參數3: uri參數,可以是變長數組或map
 * 返回值:ResponseEntity<T>是Spring對HTTP響應的封裝,包括了幾個重要的元素,如響應碼、contentType、contentLength、response header信息,response body信息等
 */
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}

舉例:

ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");

Book book = responseEntity.getBody();  //響應體轉換爲Book類型
int statusCodeValue = responseEntity.getStatusCodeValue();  //響應狀態碼
HttpHeaders headers = responseEntity.getHeaders();  //響應頭信息

getForObject()

發送GET請求,返回指定的Object類型

/**
 * 參數1: String類型 或 URI類型的請求地址
 * 參數2: 指定返回的實體類型,class對象
 * 參數3: uri參數,可以是變長數組或map
 * 返回值:responseType指定的Object類型
 */
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}

舉例:

Book book = restTemplate.getForObject("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");

POST方法

postForEntity()

發送POST請求,返回ResponseEntity

/**
 * 參數1: String類型 或 URI類型的請求地址
 * 參數2: 請求body,可以是HttpEntity類型(可設置request header),或其它Object類型
 * 參數3: 指定返回的實體類型,class對象
 * 參數4: uri參數,可以是變長數組或map
 * 返回值:ResponseEntity<T>是Spring對HTTP響應的封裝,包括了幾個重要的元素,如響應碼、contentType、contentLength、response header信息,response body信息等
 */
@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)  throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)  throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}

舉例:

//參數是Book類型,返回值是ResponseEntity<Book>類型
ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://127.0.0.1:8080/updateBook", book, Book.class);

Book book = responseEntity.getBody();  //響應體轉換爲Book類型
int statusCodeValue = responseEntity.getStatusCodeValue();  //響應狀態碼
HttpHeaders headers = responseEntity.getHeaders();  //響應頭信息

postForObject()

發送POST請求,返回指定的Object類型

/**
 * 參數1: String類型 或 URI類型的請求地址
 * 參數2: 請求body,可以是HttpEntity類型(可設置request header),或其它Object類型
 * 參數3: 指定返回的實體類型,class對象
 * 參數4: uri參數,可以是變長數組或map
 * 返回值:responseType指定的Object類型
 */
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}

舉例:

//參數是Book類型,返回值也是Book類型
Book book = restTemplate.postForObject("http://127.0.0.1:8080/updatebook", book, Book.class);

exchange方法

- 可以支持多種HTTP方法,在參數中指定
- 可以在請求中增加header和body信息,返回類型是ResponseEntity,可以從中獲取響應的狀態碼,header和body等信息
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET,     //GET請求
        requestEntity,      //requestEntity,可以設置請求header、body
        String.class, "42");

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");   //響應頭信息
String body = response.getBody();

四、RestTemplate擴展/配置

1、處理請求頭和響應頭

設置請求頭信息

(1)如果是發送post、put請求,要設置請求頭,可以在調用方法時的第二個參數傳入HttpEntity對象,HttpEntity可以用於設置請求頭信息,如

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

Book book = restTemplate.postForObject("http://127.0.0.1:8080/getbook", requestEntity, Book.class);

以postForObject()方法舉例,其第二個參數接收Object類型的數據,如傳入的是HttpEntity,則使用它作爲整個請求實體,如果傳入的是其它Object類型,則將Object參數作爲request body,新建一個HttpEntity作爲請求實體

private HttpEntityRequestCallback(Object requestBody, Type responseType) {
    super(responseType);
    //如果是HttpEntity類型的,直接作爲請求實體賦值給this.requestEntity
    if (requestBody instanceof HttpEntity) {
        this.requestEntity = (HttpEntity<?>) requestBody;
    }
    //如果requestBody不是HttpEntity類型,且不爲空,以Object參數作爲request body,並新建HttpEntity
    else if (requestBody != null) {
        this.requestEntity = new HttpEntity<Object>(requestBody);
    }
    else {
        this.requestEntity = HttpEntity.EMPTY;
    }
}

(2)如果是其它HTTP方法調用要設置請求頭,可以使用exchange()方法,可以參考 官方示例

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET, requestEntity, String.class, "42");

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

總之,設置request header信息,需要找到對應的restTemplate方法中可以使用HttpEntity作爲參數的,提前設置好請求頭信息
注意:HttpEntity有4個構造方法,無參構造,只設置請求body,只設置headers,既設置headers又設置body

/**
 * Create a new, empty {@code HttpEntity}.
 */
protected HttpEntity() {
  this(null, null);
}

/**
 * Create a new {@code HttpEntity} with the given body and no headers.
 * @param body the entity body
 */
public HttpEntity(T body) {
  this(body, null);
}

/**
 * Create a new {@code HttpEntity} with the given headers and no body.
 * @param headers the entity headers
 */
public HttpEntity(MultiValueMap<String, String> headers) {
  this(null, headers);
}

/**
 * Create a new {@code HttpEntity} with the given body and headers.
 * @param body the entity body
 * @param headers the entity headers
 */
public HttpEntity(T body, MultiValueMap<String, String> headers) {
  this.body = body;
  HttpHeaders tempHeaders = new HttpHeaders();
  if (headers != null) {
      tempHeaders.putAll(headers);
  }
  this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
}

處理響應頭信息

使用RestTemplate中xxxForEntity()的方法,會返回ResponseEntity,可以從中獲取到響應狀態碼,響應頭和body等信息

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET, requestEntity, String.class, "42");

//response相關信息
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
2、ClientHttpRequestFactory

ClientHttpRequestFactory是Spring定義的一個接口,其用於生產org.springframework.http.client.ClientHttpRequest對象,RestTemplate只是模板類,抽象了很多調用方法,而底層真正使用何種框架發送HTTP請求是通過ClientHttpRequestFactory指定的

接口定義

/**
 * Factory for {@link ClientHttpRequest} objects.
 * Requests are created by the {@link #createRequest(URI, HttpMethod)} method.
 * ClientHttpRequest對象的工廠
 *
 * @author Arjen Poutsma
 * @since 3.0
 */
public interface ClientHttpRequestFactory {

    /**
     * Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
     * <p>The returned request can be written to, and then executed by calling
     * {@link ClientHttpRequest#execute()}.
     * 使用指定的URI和HTTP方法新建一個ClientHttpRequest對象
     * 可以修改返回的request,並通過ClientHttpRequest的execute()方法執行調用
     * 即調用的邏輯也被Spring封裝到ClientHttpRequest中
     * 
     * @param uri the URI to create a request for
     * @param httpMethod the HTTP method to execute
     * @return the created request
     * @throws IOException in case of I/O errors
     */
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;

}

RestTemplate可以在構造時設置ClientHttpRequestFactory,也可以通過setRequestFactory()方法設置

構造方法設置:
/**
 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
 * @param requestFactory HTTP request factory to use
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory
 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
 */
public RestTemplate(ClientHttpRequestFactory requestFactory) {
    this();
    setRequestFactory(requestFactory);
}

可以看到上面註釋中已經給出了Spring的兩種ClientHttpRequestFactory的實現類SimpleClientHttpRequestFactory和HttpComponentsClientHttpRequestFactory

SimpleClientHttpRequestFactory

如果什麼都不設置,RestTemplate默認使用的是SimpleClientHttpRequestFactory,其內部使用的是jdk的java.net.HttpURLConnection創建底層連接,默認是沒有連接池的,connectTimeout和readTimeout都是 -1,即沒有超時時間

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
    。。。。。。
        
    private int connectTimeout = -1;
    private int readTimeout = -1;
    
    //創建Request
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
        prepareConnection(connection, httpMethod.name());

         //bufferRequestBody默認爲true
        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
        }
        else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }
    }
    
    
    /**
     * Opens and returns a connection to the given URL.
     * 打開並返回一個指定URL的連接
     * <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} -
     * if any - to open a connection.
     * @param url the URL to open a connection to
     * @param proxy the proxy to use, may be {@code null}
     * @return the opened connection  返回類型爲 java.net.HttpURLConnection
     * @throws IOException in case of I/O errors
     */
    protected HttpURLConnection openConnection(URL url, 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;
    }
    
    
    /**
     * Template method for preparing the given {@link HttpURLConnection}.
     * <p>The default implementation prepares the connection for input and output, and sets the HTTP method.
     * @param connection the connection to prepare
     * @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.)
     * @throws IOException in case of I/O errors
     */
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
         //如果connectTimeout大於等於0,設置連接超時時間
        if (this.connectTimeout >= 0) {
            connection.setConnectTimeout(this.connectTimeout);
        }
         //如果readTimeout大於等於0,設置讀超時時間
        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);
    }
    
    。。。。。。
}

HttpComponentsClientHttpRequestFactory

HttpComponentsClientHttpRequestFactory底層使用Apache HttpClient創建請求,訪問遠程的Http服務,可以使用一個已經配置好的HttpClient實例創建HttpComponentsClientHttpRequestFactory請求工廠,HttpClient實例中可以配置連接池和證書等信息

添加HttpClient依賴

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>x.x.x</version>   <!-- springboot項目不用指定 -->
</dependenc

設置超時時間

設置超時時間,可以直接使用Spring的底層基於HttpClient的HttpComponentsClientHttpRequestFactory,此處設置的是ClientHttpRequestFactory級別的全局超時時間

@Configuration  
public class RestTemplateConfig {  
  
    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate(clientHttpRequestFactory());  
    }  
  
    @Bean 
    private ClientHttpRequestFactory clientHttpRequestFactory() {  
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();  
        factory.setConnectTimeout(30 * 1000);  //連接超時時間
        factory.setReadTimeout(60 * 1000);  //讀超時時間
        return factory;  
    }  
} 

**注意:**如果通過一個HttpClient實例創建HttpComponentsClientHttpRequestFactory,並通過HttpClient指定了DefaultRequestConfig,設置了connectTimeout、readTimeout等,在實際執行請求創建request時會與HttpComponentsClientHttpRequestFactory的配置合併,connectTimeout、socketTimeout、connectionRequestTimeout 以HttpComponentsClientHttpRequestFactory的配置爲準

HttpComponentsClientHttpRequestFactory:
/**
 * Merge the given {@link HttpClient}-level {@link RequestConfig} with
 * the factory-level {@link RequestConfig}, if necessary.
 * @param clientConfig the config held by the current    httpClient級別的requestConfig配置
 * @return the merged request config
 * (may be {@code null} if the given client config is {@code null})
 * @since 4.2
 */
protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
    if (this.requestConfig == null) {  // nothing to merge
        return clientConfig;
    }

    RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
    int connectTimeout = this.requestConfig.getConnectTimeout();  //HttpComponentsClientHttpRequestFactory級別的配置
    if (connectTimeout >= 0) {
        builder.setConnectTimeout(connectTimeout);
    }
    int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
    if (connectionRequestTimeout >= 0) {
        builder.setConnectionRequestTimeout(connectionRequestTimeout);
    }
    int socketTimeout = this.requestConfig.getSocketTimeout();
    if (socketTimeout >= 0) {
        builder.setSocketTimeout(socketTimeout);
    }
    return builder.build();
}

上例中雖然沒有指定http連接池,但** HttpComponentsClientHttpRequestFactory無參構造會創建一個HttpClient,並默認使用了連接池配置,MaxTotal=10,DefaultMaxPerRoute=5 **,具體如下:

HttpComponentsClientHttpRequestFactory:
/**
 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
 * with a default {@link HttpClient}.
 */
public HttpComponentsClientHttpRequestFactory() {
    this(HttpClients.createSystem());
}


HttpClients:
/**
 * Creates {@link CloseableHttpClient} instance with default
 * configuration based on system properties.
 * 創建CloseableHttpClient實例使用基於system properties的默認配置
 */
public static CloseableHttpClient createSystem() {
    return HttpClientBuilder.create().useSystemProperties().build();
}


HttpClientBuilder:
/**
 * Use system properties when creating and configuring default
 * implementations.
 */
public final HttpClientBuilder useSystemProperties() {
    this.systemProperties = true;  //設置systemProperties爲true
    return this;
}

public CloseableHttpClient build() {
    HttpClientConnectionManager connManagerCopy = this.connManager; //沒有設置,爲null
    if (connManagerCopy == null) {
        。。。。。。
        //創建連接池管理器PoolingHttpClientConnectionManager
        @SuppressWarnings("resource")
        final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslSocketFactoryCopy)
                    .build(),
                null,
                null,
                dnsResolver,
                connTimeToLive,
                connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
        if (defaultSocketConfig != null) {
            poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
        }
        if (defaultConnectionConfig != null) {
            poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
        }
        //由於是HttpClientBuilder.create().useSystemProperties().build(),systemProperties爲true
        if (systemProperties) {
            String s = System.getProperty("http.keepAlive", "true");  //http.keepAlive默認值爲true
            if ("true".equalsIgnoreCase(s)) {
                s = System.getProperty("http.maxConnections", "5");  //默認值爲5
                final int max = Integer.parseInt(s);
                poolingmgr.setDefaultMaxPerRoute(max);  //DefaultMaxPerRoute=5
                poolingmgr.setMaxTotal(2 * max);  //MaxTotal=10
            }
        }
        if (maxConnTotal > 0) {
            poolingmgr.setMaxTotal(maxConnTotal);
        }
        if (maxConnPerRoute > 0) {
            poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
        }
        connManagerCopy = poolingmgr;
    }
}
配置連接池
@Configuration  
public class RestTemplateConfig {  
  
    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate(clientHttpRequestFactory());  
    }  
  
    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

            //開始設置連接池
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager 
                                                    = new PoolingHttpClientConnectionManager();
            poolingHttpClientConnectionManager.setMaxTotal(100);  //最大連接數
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);  //同路由併發數
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);

            HttpClient httpClient = httpClientBuilder.build();
            // httpClient連接配置
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory 
                                                    = new HttpComponentsClientHttpRequestFactory(httpClient);
            clientHttpRequestFactory.setConnectTimeout(30 * 1000);  //連接超時
            clientHttpRequestFactory.setReadTimeout(60 * 1000);     //數據讀取超時時間
            clientHttpRequestFactory.setConnectionRequestTimeout(30 * 1000);  //連接不夠用的等待時間
            return clientHttpRequestFactory;
        }
        catch (Exception e) {
            logger.error("初始化clientHttpRequestFactory出錯", e);
        }
        return null;
    } 
} 
3、自定義messageConverter

RestTemplate的無參構造中默認會初始化很多messageConverters,用於請求/響應中的消息轉換

/**
 * Create a new instance of the {@link RestTemplate} using default settings.
 * Default {@link HttpMessageConverter}s are initialized.
 * 使用默認配置創建一個RestTemplate實例
 * 默認的HttpMessageConverter集合被初始化
 */
public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter());
    this.messageConverters.add(new SourceHttpMessageConverter<Source>());
    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());
    }

    /**
     * 如果類路徑下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
     * 使用jackson做http請求、響應的json轉換
     */
    if (jackson2Present) {
        this.messageConverters.add(new MappingJackson2HttpMessageConverter());
    }
    else if (gsonPresent) {  //類路徑下包含 com.google.gson.Gson
        this.messageConverters.add(new GsonHttpMessageConverter());
    }
}
springboot項目默認使用jackson做json轉換

使用fastjson做json轉換

  1. 引入fastjson依賴
  2. 排除jackson的HttpMessageConverter轉換器
  3. 添加fastjson的轉換器

排除jackson的HttpMessageConverter轉換器有兩種方式:
(1)類路徑下去掉jackson的支持
從RestTemplate的無參構造可以看出,需要判斷類路徑下是否有jackson的相關類,有才會添加MappingJackson2HttpMessageConverter,故可以在pom.xml中排除jackson的支持,以springboot項目舉例

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>jackson-databind</artifactId> 
            <groupId>com.fasterxml.jackson.core</groupId>
        </exclusion>
    </exclusions>
</dependency>

(2)在初始化配置RestTemplate時,去掉其默認的MappingJackson2HttpMessageConverter

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(clientHttpRequestFactory());

    //restTemplate默認的HttpMessageConverter
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    List<HttpMessageConverter<?>> messageConvertersNew = new ArrayList<HttpMessageConverter<?>>();
    
    for(HttpMessageConverter httpMessageConverter : messageConverters){
        //跳過MappingJackson2HttpMessageConverter
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) continue;

        messageConvertersNew.add(httpMessageConverter);
    }

    //添加fastjson轉換器
    messageConvertersNew.add(fastJsonHttpMessageConverter());

    return restTemplate;
}

@Bean
public HttpMessageConverter fastJsonHttpMessageConverter() {
    //MediaType
    List<MediaType> mediaTypes = new ArrayList<>();
    mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

    //FastJsonConfig
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,
                                         SerializerFeature.QuoteFieldNames);

    //創建FastJsonHttpMessageConverter4    Spring 4.2後使用
    FastJsonHttpMessageConverter4 fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter4();
    fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
    fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

    return fastJsonHttpMessageConverter;
}

五、SpringBoot中使用RestTemplate

SpringBoot項目可以通過上面的方式,@Bean往Spring容器中註冊一個配置好的RestTemplate實例,也可以參考 SpringBoot官方 的方式自定義RestTemplate

由於RestTemplate實例在使用前經常需要自定義,SpringBoot沒有提供自動配置好的RestTemplate,但是自動配置好了可以用於創建RestTemplate的RestTemplateBuilder實例,可以按如下使用

    @Service
    public class MyBean {
    
        private final RestTemplate restTemplate;
    
        public MyBean(RestTemplateBuilder restTemplateBuilder) {
            this.restTemplate = restTemplateBuilder.build();
        }
    
        public Details someRestCall(String name) {
            return this.restTemplate.getForObject("/{name}/details", Details.class, name);
        }
    }

RestTemplate自定義

RestTemplate自定義主要有三種方法,具體取決於希望自定義應用的範圍

  1. 類範圍。爲了儘量縮小自定義的範圍,在類中注入自動配置的RestTemplateBuilder,然後根據需求調用它的配置方法,每次調用配置方法都會 new RestTemplateBuilder()並返回,所以對RestTemplateBuilder的配置只會影響由它創建的RestTemplate

  2. 應用範圍。可以使用RestTemplateCustomizer來自定義應用範圍的的RestTemplate,所有註冊到Spring容器的RestTemplateCustomizer都會自動生效。如下,通過RestTemplateCustomizer設置連接池

@Bean
    public RestTemplateCustomizer restTemplateCustomizer(){
        return new RestTemplateCustomizer(){
            @Override
            public void customize(RestTemplate restTemplate) {
                HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

                //創建連接管理器
                PoolingHttpClientConnectionManager poolingHttpClientConnectionManager 
                                         = new PoolingHttpClientConnectionManager();
                poolingHttpClientConnectionManager.setMaxTotal(100);
                poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);
                httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);

                //創建httpClient
                HttpClient httpClient = httpClientBuilder.build();

                //創建HttpComponentsClientHttpRequestFactory
                HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory 
                                             = new HttpComponentsClientHttpRequestFactory(httpClient);
                httpComponentsClientHttpRequestFactory.setConnectTimeout(10 * 1000);
                httpComponentsClientHttpRequestFactory.setReadTimeout(60 * 1000);
                httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(20 * 1000);

                restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory);
            }
        };
    }

最後,最極端的(也是很少使用的)選項是創建你自己的RestTemplateBuilder bean。這將關閉RestTemplateBuilder的自動配置,並阻止使用任何RestTemplateCustomizer bean

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章