RestTemplate用更優雅的方式發送HTTP請求

RestTemplate是Spring提供的用于訪問Rest服務的客戶端,RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
我之前的HTTP開發是用apache的HttpClient開發,代碼復雜,還得操心資源回收等。代碼很復雜,冗余代碼多,稍微截個圖,這是我封裝好的一個get請求工具:

public static String get(String url, String paramsStr) {
        //創建一個默認的HttpClients對象
        CloseableHttpClient createDefault = HttpClients.createDefault();
        //創建一個get請求并發送參數
        HttpGet httpGet = new HttpGet(url + paramsStr);
        //設置http頭部信息
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1;"
                + " Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0");
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("Accept-Encoding", "gzip, deflate");
        httpGet.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        //RequestConfig.Builder 配置器 ,我們可以通過custom獲取一個新的Builder對象
        RequestConfig config = RequestConfig.custom()
                //設置鏈接超時的時間1秒鐘
                .setConnectTimeout(90000)
                //設置讀取超時1秒鐘
                .setSocketTimeout(90000)
                //RequestConfig靜態方法  setProxy  設置代理
                .build();
        //設置頭部信息
        httpGet.setConfig(config);
        //實例話對象賦值
        CloseableHttpResponse execute = null;
        String jsonStr = "";
        try {
            //執行HttpClient
            execute = createDefault.execute(httpGet);
            //轉化json格式 并防止亂碼
            jsonStr = EntityUtils.toString(execute.getEntity(), "UTF-8");
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //關閉HttpClient
                createDefault.close();
                //關閉執行
                execute.close();
                //銷毀GET請求
                httpGet.abort();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return jsonStr;
    }

一、簡述RestTemplate

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能大幅簡化了提交表單數據的難度,并且附帶了自動轉換JSON數據的功能,但只有理解了HttpEntity的組成結構(header與body),且理解了與uriVariables之間的差異,才能真正掌握其用法。這一點在Post請求更加突出。
該類的入口主要是根據HTTP的六個方法制定:


restTemplate.png

二、RestTemplate解析

HttpMessageConverter

RestTemplate默認使用HttpMessageConverter實例將HTTP消息轉換成POJO或者從POJO轉換成HTTP消息。默認情況下會注冊主mime類型的轉換器,但也可以通過setMessageConverters注冊其他的轉換器。其實這點在使用的時候是察覺不到的,很多方法有一個responseType 參數,它讓你傳入一個響應體所映射成的對象,然后底層用HttpMessageConverter將其做映射

HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);

HttpMessageConverter.java源碼:

public interface HttpMessageConverter<T> {
    //指示此轉換器是否可以讀取給定的類。
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
 
    //指示此轉換器是否可以寫給定的類。
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
 
    //返回List<MediaType>
    List<MediaType> getSupportedMediaTypes();
 
    //讀取一個inputMessage
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;
 
    //往output message寫一個Object
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
 
}
創建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

  • 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();
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等信息

四、RestTemplate擴展

設置攔截器(ClientHttpRequestInterceptor)

有時候我們需要對請求做一些通用的攔截設置,這就可以使用攔截器進行處理。攔截器需要我們實現org.springframework.http.client.ClientHttpRequestInterceptor接口自己寫。

舉個簡單的例子,寫一個在header中根據請求內容和地址添加令牌的攔截器。

public class TokenInterceptor implements ClientHttpRequestInterceptor
{
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
    {
        //請求地址
        String checkTokenUrl = request.getURI().getPath();
        //token有效時間
        int ttTime = (int) (System.currentTimeMillis() / 1000 + 1800);
        //請求方法名 POST、GET等
        String methodName = request.getMethod().name();
        //請求內容
        String requestBody = new String(body);
        //生成令牌 此處調用一個自己寫的方法,有興趣的朋友可以自行google如何使用ak/sk生成token,此方法跟本教程無關,就不貼出來了
        String token = TokenHelper.generateToken(checkTokenUrl, ttTime, methodName, requestBody);
        //將令牌放入請求header中
        request.getHeaders().add("X-Auth-Token",token);

        return execution.execute(request, body);
    }
}

創建RestTemplate實例的時候可以這樣向其中添加攔截器

        RestTemplate restTemplate = new RestTemplate();
        //向restTemplate中添加自定義的攔截器
        restTemplate.getInterceptors().add(new TokenInterceptor());

五、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();
處理響應頭信息

使用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的實現類SimpleClientHttpRequestFactoryHttpComponentsClientHttpRequestFactory

SimpleClientHttpRequestFactory

如果什么都不設置,RestTemplate默認使用的是SimpleClientHttpRequestFactory,其內部使用的是jdk的java.net.HttpURLConnection創建底層連接,默認是沒有連接池的,connectTimeoutreadTimeout都是 -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項目不用指定 -->
</dependency>
設置超時時間

設置超時時間,可以直接使用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());
    }
}

使用fastjson做json轉換

springboot項目默認使用jackson做json轉換

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

  1. 添加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);
    }

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容