RestTemplate-postForObject詳解、調用Https接口、源碼解析,讀懂這一篇文章就夠了


restTemplate

1. 基本介紹

RestTemplate 是 Spring 提供的,用於訪問Rest服務的同步客戶端,提供了一些簡單的模板方法API;底層支持多種Http客戶端類庫,因為RestTemplate只是對其他的HTTP客戶端的封裝,其本身並沒有實現HTTP相關的基礎功能,底層實現可以按需配置;常用的有:

  • SimpleClientHttpRequestFactory,默認配置,對應的JDK自帶的HttpURLConnection,不支持Http協議的Patch方法,也無法訪問Https請求;
  • HttpComponentsClientHttpRequestFactory,對應的是Apache的HttpComponents(注:Apache的HttpClient是前身,后邊改名為Components);
  • OkHttp3ClientHttpRequestFactory,對應的是OkHttp。

如果向查看所有的http客戶端類庫,可以找下ClientHttpRequestFactory接口的實現類:

RestTemplate、Apache的HttpClient、OkHttp比較:

  • RestTemplate 提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率;
  • HttpClient代碼復雜,還得操心資源回收等。代碼很復雜,冗余代碼多,不建議直接使用;
  • okhttp是一個高效且開源的第三方HTTP客戶端類庫,常用於android中請求網絡,是安卓端最火熱的輕量級框架;允許所有同一個主機地址的請求共享同一個socket連接,連接池減少請求延時;透明的GZIP壓縮減少響應數據的大小,緩存響應內容,避免一些完全重復的請求。

2. 常用方法分析及舉例

這一塊主要講一些常用的方法及參數、對於一些重載的方法,其實原理都差不多。

2.1. get請求

除了getForEntity和getForObject外,使用exchange()也可以,前兩個是基於它實現的,此處不做介紹

參數包括請求url、響應類型的class、請求參數

  • url:String字符串或者URI對象;常用字符串
  • 響應對象的class實例
    • getForEntity() ==> 響應為ResponseEntity ,其中包括請求的響應碼和HttpHeaders
    • getForObject() ==> 響應為傳入的class對象,只包括響應內容
  • 請求參數:替換url中的占位符,可以使用可變長的Object,也可以使用Map;如果沒有,可以不填 其中object是按照占位符的順序匹配的,map是根據key匹配,如果匹配不上,就報錯
// 不帶參數的
String url = "localhost:8001/test/method";
Object object = restTemplate.getForObject(url, Object.class);

// 帶參數的,使用@PathVariable接收
String url = "localhost:8001/test/method/{param1}";
Object object = restTemplate.getForObject(url, Object.class, "param");
// 帶參數的,使用@Requestparam接收
String url = "localhost:8001/test/method?param={dd}";
ResponseEntity<Object> result = restTemplate.getForObject(url, Object.class, "param");

2.2. post請求

參數和get請求的相比,就多了第二個參數(Object request),如果使用最后一個參數傳參時,和get請求類似,request設置為null就可以,如果使用第二個參數傳參時,就需要考慮request的類型,request參數類型必須是實體對象、MultiValueMap、HttpEntity對象的的一種,其他不可以!!!

  • 實體對象傳參時,被請求的接口方法上必須使用@RequestBody,接收參數為實體或者Map;
  • HttpEntity傳參時,取決於HttpEntity對象的第一個參數,可以為任何類型,包括HashMap;
  • MultiValueMap傳參時,接收參數使用注解@RequestBody時,使用一個String類型、名稱隨意,使用@RequestParam時,使用對應個數的String類型字符串,名稱必須和map中的key相同;推薦使用@RequestParam
// 使用MultiValueMap傳參
String url = "localhost:8001/test/method";
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();		// 不能使用HashMap,源碼中會講!
map.add("param1", "string1");
map.add("param2", "string2");
ResponseEntity<String> response = restTemplate.postForEntity(url, map , String.class );
// 使用HttpEntity傳參
String url = "localhost:8001/test/method";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add(HttpHeaders.CONTENT_ENCODING, StandardCharsets.UTF_8.toString());
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
String string = "param";
HttpEntity<String> entity = new HttpEntity<String>(string,headers);
String result = restTemplate.postForObject(url, entity, String.class);

3. springboot中使用restTemplate步驟

  1. 導入jar包
<!-- springboot web依賴  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 編寫配置
    使用默認的SimpleClientHttpRequestFactory,也就是java JDK自帶的HttpURLConnection;
@Configuration
public class RestTemplateConfig {
        @Bean
        public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory){
            return new RestTemplate(factory);
        }

        @Bean
        public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setReadTimeout(5000);
            factory.setConnectTimeout(5000);
            return factory;
        }
}
  1. Service層調用
@Service
public class TestService implements ITestService {
    @Autowired
    RestTemplate restTemplate;

    @Override
    public ResultVo test() throws Exception {
        try {
            String url = "http://localhhost:9001/test/demo1";
            ResponseEntity<Object> objectResponseEntity = restTemplate.postForEntity(url, null, Object.class);
            if( 200 == objectResponseEntity.getStatusCodeValue()){
                return ResultVo.success();
            }
            return ResultVo.error();
        } catch (Exception e) {
            throw new Exception("調用接口失敗," + e.getMessage());
        }
    }
}

4. 源碼分析(postForEntity為例)

思考重點:
1. 第二個參數為什么不能直接使用HashMap,而只能使用MultiValueMap?
2. 接收參數時,怎么合理的使用@RequestBody和@RequestParam?
3. restTemplate底層默認使用的是SimpleClientHttpRequestFactory,為什么不支持調用Https接口?

  1. 依次進入方法:postForEntity() -> httpEntityCallback -> HttpEntityRequestCallback
  1. requestBody參數,會判斷類型是否是HttpEntity,如果不是,則創建一個HttpEntity類將 requestBody 參數傳入,然后查看HttpEntity構造器,具體做了什么?
  1. 可以看到,三個構造方法,上邊兩個調用的是最下邊一個;

    第一個傳入的是泛型,也就是傳入的Object對象

    第二個傳入的是MultiValueMap,這個值是存放Headers的

    所有只需要關注這個泛型,在哪塊使用的

  1. 回到postForEntity()方法中,找到調用請求的方法execute,點進去發現是調用方法doExecute(...);
  1. 在doExecute()中
    • 首先使用請求的url和method(post 或者get)構造出一個ClientHttpRequest
    • requestCallback.doWithRequest將之前的requestBody、requestHeader放入此ClientHttpRequest中;
    • 調用request的execute方法獲得response,調用handleResponse方法處理response中存在的error
    • 使用ResponseExtractor的extraData方法將返回的response轉換為某個特定的類型;
    • 最后關閉ClientHttpResponse資源,這樣就完成了發送請求並獲得對應類型的返回值的全部過程。
  1. 進入方法getRequestFactory() -> getRequestFactory()可以發現,通過this.requestFactory初始化了SimpleClientHttpRequestFactory();通過方法createRequest(url, method) -> openConnection()發現創建了HttpURLConnection連接,因此默認使用的restTemplate是無法訪問Https接口的
  1. 進入方法doWithRequest(request)可以發現,程序會執行第一個else中的邏輯,根據傳入的參數,判斷requestBodyClass、requestBodyType和MediaType;
    • 如果第二個參數為HashMap或者MultiValueMap時,MediaType為null;
    • 如果是HttpEntity時,requestBodyClass為對應參數的類型,MediaType為封裝HttpHeaders中的ContentType值

接下來會遍歷所有的HttpMessageConverter,這些對象在RestTemplate的構造函數中被初始化

  1. 在遍歷過程中判斷是否可以寫入,如果能寫入則執行寫入操作並返回;判斷MessageConvertor是否為GenericHttpMessageConverter的子類,是因為寫入的方式不同;在這些MessageConvertor中只有GsonHttpMessageConverter是GenericHttpMessageConverter的子類,且排在最后;因此,遍歷過程中會先判斷前六個convertor,能寫入則執行寫入,最后才是GsonHttpMessageConvertor。分析所有的HTTPMessageConvertor,可以發現

    • MultiValueMap子類的數據會被AllEncompassingFormHttpMessageConverter處理,將MediaType置為application/x-www-form-urlencoded、將request中的key value通過&=拼接並寫入到body中,接收時,可以為@RequestParam、也可以為@RequestBody Http協議中,如果不指定Content-Type,則默認傳遞的參數就是application/x-www-form-urlencoded類型

    • HashMap類型的數據會被GsonHTTPMessageConvertor處理,將MediaType置為application/json;charset=UTF-8、將request轉成json並寫入到body中,因此,第二個參數設置為HashMap時,無法設置ContentType值,所有第二個參數無法使用HashMap!但是可以使用HttpEntity對象,將HashMap存放在HttpEntity對象里邊,接收參數時,使用@RequestBody


5. restTemplate訪問Https接口

restTemplate底層默認使用的是SimpleClientHttpRequestFactory,是基於HttpURLConnection,是不支持調用Https接口的,可以修改為HttpComponentsClientHttpRequestFactory

public class RestTemplateConfig {
  @Bean
  public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                return true;
            }
        }).build();

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext,
                new String[]{"TLSv1"},
                null,
                NoopHostnameVerifier.INSTANCE);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(csf)
                .build();

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

        requestFactory.setHttpClient(httpClient);
        return new RestTemplate(requestFactory);
    }
}

以前工作中經常使用,但是時不時的就會出現問題,每次都是盲目的百度解決,趁着周末空余時間,詳細的學習一下下,如果有不對的地方或者疑問,請大家指正,一起討論。


免責聲明!

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



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