Spring的Template使用指南


  一般我們請求接口,都采用Apache Httpclient工具,這個工具穩定,既可以建立長連接,保持不錯的性能,而它唯一的不足就是使用起來麻煩多變,並且要很多層判斷處理,今天我要談的就是spring對httpClient的再封裝工具類,restTemplate,采用模板模式抽象出來的高效工具。有點類似於jdbcTemplate,今天我們就來一步步揭開它的使用方法。

一、restTemplate簡介

RestTemplate是Spring提供的用於訪問Rest服務的客戶端,RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。

1、restTemplate的類結構

可以看出它繼承自HttpAccessor這個統一的處理器,然后再繼承自InterceptingHttpAccessor,這個攔截轉換器,最終RestTemplate實現了封裝httpClient的模板工具類。

二、restTemplate的方法API

  Spring用於同步客戶端HTTP訪問的中心類。它簡化了與HTTP服務器的通信,並執行RESTful原則。它處理HTTP連接,使應用程序代碼提供URL,使用可能的模板變量,並提取結果。
  注意:默認情況下,RestTemplate依賴於標准的JDK來建立HTTP連接。你可以切換使用不同的HTTP庫,如Apache HttpComponents,Netty和OkHttp通過setRequestFactory屬性。 內部模板使用HttpMessageConverter實例將HTTP消息轉換為POJO和從POJO轉換。主要MIME類型的轉換器是默認注冊的,但您也可以注冊其他轉換器通過setMessageConverters。

  以下是http方法和restTempalte方法的比對映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用來做任何的請求,一般我們都是用它來封裝不同的請求方式。

1、使用GET

1.1 獲取JSON字符串

我們可以使用getForEntity()方法:

RestTemplate restTemplate = new RestTemplate();
String fooResourceUrl = "http://localhost:8080/spring-rest/foos";
ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl + "/1", String.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
  需要說明的是,通過 getForEntity()我們可以獲取到完整的 HTTP response,因此我們可以通過檢測狀態碼來判斷請求是否真正執行成功。我們也可以通過 getBody()方法獲取返回的具體內容,如:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
JsonNode name = root.path("name");
assertThat(name.asText(), notNullValue());

1.2 獲取POJO對象

我們也可以直接將請求直接映射為一個POJO對象,如:

public class User implements Serializable {
    private long id;
 
    private String name;
    // 這里省略了getters和setters
}

獲取User對象:

User user = restTemplate.getForObject(userResourceUrl + "/1", User.class);
assertThat(user.getName(), notNullValue());
assertThat(user.getId(), is(1L));

2. 獲取Headers

代碼如下:

HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);
assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));

3. 使用POST

  RestTemplate提供了三個API用來創建資源,它們分別是postForLocation()postForObject()postForEntity()postForLocation()返回新創建資源的URI,postForObject()則返回新創建的資源本身。

3.1 postForObject方法

ClientHttpRequestFactory requestFactory = getClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
 
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);
assertThat(foo, notNullValue());
assertThat(foo.getName(), is("bar"));

3.2 postForLocation方法

HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
URI location = restTemplate.postForLocation(fooResourceUrl, request);
assertThat(location, notNullValue());

3.3 exchange方法

RestTemplate還提供了一個更加通用的方法:exchange,下面我們來看看如何使用該方法完成一個POST請求:

RestTemplate restTemplate = new RestTemplate();
HttpEntity<User> request = new HttpEntity<>(new User("CD826"));
ResponseEntity<User> response = restTemplate.exchange(userResourceUrl, HttpMethod.POST, request, User.class);
  
assertThat(response.getStatusCode(), is(HttpStatus.CREATED));
  
User user = response.getBody();
  
assertThat(user, notNullValue());
assertThat(user.getName(), is("CD826"));  

4. 獲取允許執行操作列表

optionsForAllow方法可以讓我們獲取給定URI中允許執行的操作列表:

Set<HttpMethod> optionsForAllow = restTemplate.optionsForAllow(fooResourceUrl);
HttpMethod[] supportedMethods = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE};
assertTrue(optionsForAllow.containsAll(Arrays.asList(supportedMethods)));

5. 使用PUT

5.1 簡單的PUT

我們先看一個簡單的PUT示例,這里需要注意的時該請求並不會有任何返回:

User updatedInstance = new User("newName");
updatedInstance.setId(createResponse.getBody().getId());
String resourceUrl = userResourceUrl + '/' + createResponse.getBody().getId();
HttpEntity<User> requestUpdate = new HttpEntity<>(updatedInstance, headers);
template.exchange(resourceUrl, HttpMethod.PUT, requestUpdate, Void.class);

5.2 帶回調的PUT

我們先定義一個回調函數:

RequestCallback requestCallback(final User updatedInstance) {
    return clientHttpRequest -> {
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(clientHttpRequest.getBody(), updatedInstance);
        clientHttpRequest.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        clientHttpRequest.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic " + getBase64EncodedLogPass());
    };
}

然后,通過POST先創建需要更新的資源:

ResponseEntity<User> response = restTemplate.exchange(userResourceUrl, HttpMethod.POST, request, User.class);
assertThat(response.getStatusCode(), is(HttpStatus.CREATED));

最后,我們使用PUT進行更新:

User updatedInstance = new User("newName");
updatedInstance.setId(response.getBody().getId());
String resourceUrl = userResourceUrl + '/' + response.getBody().getId();
restTemplate.execute(resourceUrl, HttpMethod.PUT, 
                     requestCallback(updatedInstance), 
                     clientHttpResponse -> null);

6. 使用DELETE

示例如下:

String entityUrl = fooResourceUrl + "/" + existingResource.getId();
restTemplate.delete(entityUrl);  

三、restTemplate的配置方法 

  在springboot中的配置,springboot是一款簡化傳統xml配置式的開發方式,主要采用注解的方式來代替傳統繁瑣的xml配置,接下來我們就用springboot提供的注解來配置restTemplate:

@Configuration
public class RestTemplateConfig {

    private static final Logger logger= LoggerFactory.getLogger(RestTemplateConfig.class);

    @Bean
    public RestTemplate restTemplate() {
        // 添加內容轉換器,使用默認的內容轉換器
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
        // 設置編碼格式為UTF-8
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (item.getClass() == StringHttpMessageConverter.class) {
                converterTarget = item;
                break;
            }
        }
        if (converterTarget != null) {
            converterList.remove(converterTarget);
        }
        HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        converterList.add(1,converter);

        LOGGER.info("-----restTemplate-----初始化完成");
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {

        return new HttpComponentsClientHttpRequestFactory(httpClient());

    }

    @Bean
    public HttpClient httpClient() {
        // 長連接保持30秒
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
        //設置整個連接池最大連接數 根據自己的場景決定
        connectionManager.setMaxTotal(500);
        //同路由的並發數,路由是對maxTotal的細分
        connectionManager.setDefaultMaxPerRoute(500);

        //requestConfig
        RequestConfig requestConfig = RequestConfig.custom()
                //服務器返回數據(response)的時間,超過該時間拋出read timeout
                .setSocketTimeout(10000)
                //連接上服務器(握手成功)的時間,超出該時間拋出connect timeout
                .setConnectTimeout(5000)
                //從連接池中獲取連接的超時時間,超過該時間未拿到可用連接,會拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(500)
                .build();
        //headers
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .setDefaultHeaders(headers)
                // 保持長連接配置,需要在頭添加Keep-Alive
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                //重試次數,默認是3次,沒有開啟
                .setRetryHandler(new DefaultHttpRequestRetryHandler(2, true))
                .build();
    }
}

  首先解釋以下@configuration,它的主要作用就是在spring容器啟動的時候,初始化IOC,使用了這個注解,那么該類就會在spring啟動的時候,把@Bean注解標識的類進行依賴注入。@Bean理解的話,就好比在配置文件中配置<bean id="xxx">.接下來就是在restTemplate的構造方法中添加httpRequest的工廠,使用連接池來優化http通信,默認使用長連接時間為30秒,再設置路由讓http連接定向到指定的IP,然后設置並發數。再就是設置請求配置的超時時間,為了防止請求時間過長而引起資源的過渡浪費。如果在超過設置的timeout還沒有數據返回,就直接斷開連接。headers是添加默認的請求頭,這里設置了傳送的格式為json,語言為中-英等等屬性。HttpClientBuilder.create設置請求頭到HttpClient,然后在設置保持的時間,重試的次數,注入給httpClient進行封裝。

在bean中的HttpMessageConverter,就是http信息轉換器,它的主要作用就是轉換和解析返回來的json數據,restTemplate默認使用jackson來作為底層的解析工具,而其它的比如Gson,fastjson等等第三方開源庫放在headers這個list中,如果要使用,可以通過以下代碼進行改變:

   this.restTemplate.getMessageConverters().clear();

   final List<HttpMessageConverter<?>> myHttpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
         
    //自己實現的messgeConverter
    HttpMessageConverter<Object> messageConverter = new MyHttpMessageConverter<Object>();
        
    myHttpMessageConverter.add(messageConverter);
        
    this.restTemplate.setMessageConverters(myHttpMessageConverter);

四、總結

  本篇博客講述了RestTemplate的簡介,還有配置方法和使用示例,作為一款非常不錯的rest請求工具,屏蔽了復雜的HttpClient的實現細節,向外暴露出簡單、易於使用的接口,使得我們的開發工作越來越簡單、高效,更多的方法工具可以研究一下restTemplate的具體Api,打開源碼,一切都了如指掌。在平時的工作中,應該多發現這種工具類,從而來代替一些傳統的工具,對於提升工作效率有着突飛猛進的效果和不可言喻的方便。

擴展:

引自博客:https://www.cnblogs.com/xiaobug/p/11089018.html
springcloud之Feign原理簡述
啟動時,程序會進行包掃描,掃描所有包下所有@FeignClient注解的類,並將這些類注入到spring的IOC容器中。當定義的Feign中的接口被調用時,通過JDK的動態代理來生成RequestTemplate。
RequestTemplate中包含請求的所有信息,如請求參數,請求URL等。
RequestTemplate聲場Request,然后將Request交給client處理,這個client默認是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
最后client封裝成LoadBaLanceClient,結合ribbon負載均衡地發起調用。
 

 

參考文章:

https://www.cnblogs.com/wyq178/p/9058030.html

https://www.jianshu.com/p/462790156554


免責聲明!

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



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