Spring RestTemplate終極使用歸納總結

前言

第一次看到這個RestTemplate的時候,還是遙遠的2017年,記得那年去廣西亞信學習,然後從一個項目中看到了RestTemplate,那個時候也就知道怎麼用,也就有那麼個大概的印象。後來從廣西回來後,這幾年基本上也就沒有過多的寫Java代碼了,基本就是與C++和Lua爲伴;而這些年基本也就不寫代碼了,更多的是看別人寫代碼,前段時間評審團隊的代碼時,發現在調用第三方提供的服務時,都是直接使用的HttpClient調用的,你要是使用HttpClient的話,也是沒有任何問題的,關鍵是你自己封裝的工具類還漏洞百出。後來經過評審,由於我們的整個項目都是基於Spring框架的,最後還是決定直接使用RestTemplate+HttpClient的方式調用第三方的服務。

說說RestTemplate

既然要使用RestTemplate,然後自己又好幾年沒有寫Java了,然後又手癢,就寫篇文章總結下這個RestTemplate,以及如何使用RestTemplate

RestTemplate是Spring框架提供的一個在應用中調用其他Restful服務的一個工具,它簡化了與Http服務的通信方式,統一了Restful的標準,對使用方來說,提供了非常友好的上層API接口。相較於上面說的HttpClient,RestTemplate是一種更優雅的調用Restful服務的方式。所以說,好用纔會用。

RestTemplate中包含以下幾個部分:

  • HttpMessageConverter:對象轉換器;將請求對象轉換爲具體的數據格式輸出,例如:Jaxb2RootElementHttpMessageConverterket提供對xml格式的輸入輸出支持;可以參見這篇文章,通過實戰搞懂HttpMessageConverter

  • ClientHttpRequestFactory:客戶端請求工廠接口,可以通過使用ClientHttpRequestFactory指定不同的HTTP請求方式;實現了該接口就都可以與RestTemplate進行集成,後面會細說與不同的框架進行集成;默認是JDK的HttpURLConnection

  • ResponseErrorHandler:異常錯誤處理;可以參考這篇文章,看下如何自定義異常處理;

  • ClientHttpRequestInterceptor:請求攔截器

以後有機會,我們會再在源碼的層面上來對上面的幾個部分進行分析。下面來看下RestTemplate類提供的一些主要API:

方法名

描述

getForObject

通過GET請求獲得響應結果

getForEntity

通過GET請求獲取ResponseEntity對象,包容有狀態碼,響應頭和響應數據

headForHeaders

以HEAD請求資源返回所有響應頭信息

postForObject

通過POST請求創建資源,獲得響應結果

exchange

更通用版本的請求處理方法,接受一個RequestEntity對象,可以設置路徑,請求頭,請求信息等,最後返回一個ResponseEntity實體

execute

最通用的執行HTTP請求的方法,上面所有方法都是基於execute的封裝,全面控制請求信息,並通過回調接口獲得響應數據

下面就開始通過實戰的代碼來熟悉一下RestTemplate

RestTemplate實戰

說了這麼多,最重要的還是需要落實在實戰上面。下面通過一個基於Spring Boot的測試工程來演示如何使用RestTemplate

getForObject使用示例:

// 不帶請求參數
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
    BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
            ".0.0.1:8080/test2", BookConfigBean.class);
    log.info(bookConfigBean.toString());
    return objectMapper.writeValueAsString(bookConfigBean);
}


// 攜帶請求參數
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果凍想公衆號");
    uriVars.put("author", "Jelly");
    BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
            ".0.0.1:8080/test2?name={name}&author={author}",
            BookConfigBean.class, uriVars);
    log.info(bookConfigBean.toString());
    return objectMapper.writeValueAsString(bookConfigBean);
}

getForEntity使用示例:

@GetMapping("/getForEntity")
public String getForEntity() throws JsonProcessingException {
    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果凍想公衆號");
    uriVars.put("author", "Jelly");
    ResponseEntity<BookConfigBean> responseEntity = restTemplate.getForEntity("http://127" +
                    ".0.0.1:8080/test2?name={name}&author={author}",
            BookConfigBean.class, uriVars);


    HttpStatus statusCode = responseEntity.getStatusCode();
    log.info("statusCode:" + statusCode);


    int httpCode = responseEntity.getStatusCodeValue();
    log.info("statusCodeValue:" + httpCode);


    HttpHeaders headers = responseEntity.getHeaders();
    log.info("headers:" + headers);


    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

postForObject使用示例:

@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果凍想公衆號");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    BookConfigBean bookConfigBean = restTemplate.postForObject("http://127" +
            ".0.0.1:8080/test3", request, BookConfigBean.class);
    return objectMapper.writeValueAsString(bookConfigBean);
}

postForEntity使用示例:

@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果凍想公衆號");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    ResponseEntity<BookConfigBean> responseEntity =
            this.restTemplate.postForEntity("http://127" +
            ".0.0.1:8080/test3", request, BookConfigBean.class);


    HttpStatus statusCode = responseEntity.getStatusCode();
    log.info("statusCode:" + statusCode);


    int httpCode = responseEntity.getStatusCodeValue();
    log.info("statusCodeValue:" + httpCode);


    headers = responseEntity.getHeaders();
    log.info("headers:" + headers);


    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

exchange使用示例:

// 第一種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    HttpEntity<String> request =
            new HttpEntity<String>(null, headers);


    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果凍想公衆號");
    uriVars.put("author", "Jelly");


    ResponseEntity<BookConfigBean> responseEntity =
            restTemplate.exchange("http://127.0.0.1:8080/test2?name={name" +
                    "}&author={author}", HttpMethod.GET, request,
                    BookConfigBean.class, uriVars);
    return objectMapper.writeValueAsString(responseEntity.getBody());
}


// 第二種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果凍想公衆號");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    ResponseEntity<BookConfigBean> responseEntity =
            restTemplate.exchange("http://127.0.0.1:8080/test3",
                    HttpMethod.POST, request,
                    BookConfigBean.class);
    return objectMapper.writeValueAsString(responseEntity.getBody());
}


// 第三種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    RequestEntity requestEntity = RequestEntity.get(new URI("http://127.0" +
            ".0.1:8080/test2")).headers(headers).build();
    ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}


// 第四種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果凍想公衆號");
    map.add("author", "Jelly");


    RequestEntity requestEntity = RequestEntity.post(new URI("http://127" +
            ".0.0.1:8080/test3")).headers(headers).body(map);
    ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

異常處理

前面也簡單說了一下異常處理,在Spring中對於RestTemplate提供了一個默認的異常處理,我們在自定義異常處理時建議繼承該默認繼承類,例如這樣:

public class CustomErrorHandler extends DefaultResponseErrorHandler {
    private final Logger log = LoggerFactory.getLogger(CustomErrorHandler.class);


    public void handleError(ClientHttpResponse response) throws IOException {
        // todo
        log.error("自定義異常處理");
    }
}

在構建RestTemplate實例的時候,需要設置異常處理:

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setErrorHandler(new CustomErrorHandler());
    return restTemplate;
}

RestTemplate與OkHttp集成

上面也說到RestTemplate默認是與JDK的HttpURLConnection進行的集成,但是在實際生產項目中,HttpURLConnection能力不夠強大,無法滿足我們的要求。而RestTemplate本身又具備與實現了ClientHttpRequestFactory接口的Http Client類庫進行集成,在實際工作中,最多的就是RestTemplate與OkHttp集成和RestTemplate與HttpClient集成,這裏就重點把這兩種集成方式進行總結。

至於OkHttp的牛叉之處,我這裏就不再敘述了,因爲好用,我這裏才進行總結。

要使用OkHttp,首先要添加以下Maven依賴:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.0</version>
</dependency>

接下來,我們對OkHttp3進行配置:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }


    @Bean
    public ClientHttpRequestFactory okHttp3clientHttpRequestFactory() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                //.sslSocketFactory(sslSocketFactory(), x509TrustManager())
                .retryOnConnectionFailure(false)
                .connectionPool(pool())
                .connectTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .build();
        return new OkHttp3ClientHttpRequestFactory(httpClient);
    }


    @Bean
    public X509TrustManager x509TrustManager() {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
    }


    @Bean
    public SSLSocketFactory sslSocketFactory() {
        try {
            //信任任何鏈接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return null;
    }


    @Bean
    public ConnectionPool pool() {
        return new ConnectionPool(200, 5, TimeUnit.MINUTES);
    }
}

RestTemplate與HttpClient集成

對於HttpClient,首先要添加以下Maven依賴:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

接下來,我們對HttpClient進行配置:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }


    @Bean
    public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){
        //Httpclient連接池,長連接保持30秒
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);


        //設置總連接數
        connectionManager.setMaxTotal(1000);


        //設置同路由的併發數
        connectionManager.setDefaultMaxPerRoute(1000);


        //設置header
        List<Header> headers = new ArrayList<Header>();
        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"));
        headers.add(new BasicHeader("Connection", "keep-alive"));


        //創建HttpClient
        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .setDefaultHeaders(headers)
                .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //設置重試次數
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //設置保持長連接
                .build();


        //創建HttpComponentsClientHttpRequestFactory實例
        HttpComponentsClientHttpRequestFactory requestFactory =
                new HttpComponentsClientHttpRequestFactory(httpClient);


        //設置客戶端和服務端建立連接的超時時間
        requestFactory.setConnectTimeout(5000);


        //設置客戶端從服務端讀取數據的超時時間
        requestFactory.setReadTimeout(5000);


        //設置從連接池獲取連接的超時時間,不宜過長
        requestFactory.setConnectionRequestTimeout(200);


        //緩衝請求數據,默認爲true。通過POST或者PUT大量發送數據時,建議將此更改爲false,以免耗盡內存
        requestFactory.setBufferRequestBody(false);


        return requestFactory;
    }
}

總結

各種找資料,編碼驗證,用了好久,終於搞定這篇文章,這篇文章裏面最大的就是用代碼說話,文中的所有代碼都經過測試,可以直接複製使用。總結了這篇文章,以後在使用RestTemplate的時候會更加從容和得心應手。

2020年11月7日 於呼和浩特。


人生是個圓,有的人走了一輩子也沒有走出命運畫出的圓圈,其實,圓上的每一個點都有一條騰飛的切線。

df6262c6568a80e341ea97df5b353ce2.jpeg

玩代碼、玩技術

長按識別二維碼,關注“果凍想”

如果覺得還不錯,可以點個“在看”哦~

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