javaweb--Rest訪問(RestTemplate)

Rest訪問(RestTemplate)

在實際的項目中,往往需要發送一個Get/Post請求到其他的系統(Rest API),比如向人員管理部門請求,然後解析返回信息獲取該用戶的基本信息等。JDK傳統的HttpURLConnection、Apache HttpClient、Netty 4和OkHttp等可以實現訪問請求。不過spring的RestTemplate封裝了這些操作庫,使之更容易使用。

一. Rest的具體使用

1.1 get方式

RestTemplate get方式中有兩個方法: getForObject和getForEntity
getForObject可以指定返回類型,getForEntity同一返回ResponseEntity

1.1.1 getForObject

有三種重載方式
(1) T getForObject(URI url, Class responseType)
(2) T getForObject(String url, Class responseType, MapString< String, ?> urlVariables)
(3) T getForObject(String url, Class responseType, Object… urlVariables)
其中url爲請求url,可用通配符表示請求參數,responseType爲請求返回的對象類,自動封裝成對象該對象形式(如String.class 或User.class), Map< String, ?> urlVariables表示請求參數,與通配符對應即可, Object… urlVariables爲請求的參數數組形式,按順序一一匹配url中內.

下面舉一個例子:
定義一個返回類型:User

public class User
{ 
    private String name; 
    private Integer age;
}

省略getset方法

發送方

private RestTemplate restTemplate = new RestTemplate();
private String name = "xiaoming"; 
private Integer age = 18;

#重載方式3
String url = "http://localhost:8080/getUser?name={name}&age={age}";
Object[] arr = new Object[]{name, age};
User u = restTemplate.getForObject(url, User.class, arr);

#重載方式2
String url = "http://localhost:8080/getUser?name={name}&age={age}"; 
Map<String, Object> map = new HashMap<>(); 
map.put("name", name); 
map.put("age", age);
User u = restTemplate.getForObject(url, User.class, map);

#重載方式1
#沒有參數的get方式直接調用重載方式1。如
String url = "http://localhost:8080/findAllUser";
User u = restTemplate.getForObject(url, User.class);

#實際生產中常用的是
String url = "http://localhost:8080/getUser?name=${name}&age=${age}"; 
url.replace("${name}", name)
   .replace("${age}", age);
User u = restTemplate.getForObject(url, User.class);

接受方controller

public  User getUser(@RequestParam String name, @RequestParam Integer age)

1.1.2 getForEntity

getForEntity與getForObject請求參數基本一樣,只是返回內容不一樣 .getForEntity返回ResponseEntity,裏面包含返回消息內容和http headers,http 狀態碼。
在實際的生產環境中get請求幾乎全部getForEntity,因爲需要判斷狀態碼判斷接口是否調用成功(status == 10000)。返回類型往往是Json格式的字符串,然後通過轉換爲Json獲取對應的信息。

發送方

String url = "http://localhost:8080/getUser?name={name}&age={age}"; 
Map<String, Object> map = new HashMap<>(); 
map.put("name", name);
map.put("age", age);
ResponseEntity<User> res = restTemplate.getForEntity(url, User.class, map);
User u = res.getBody();
HttpHeaders headers = res.getHeaders();
HttpStatus status = res.getStatusCode();

1.2 Post方式

post方法主要有3種方法:postForObject , postForEntity和postForLocation

1.2.1 postForObject

(1) T postForObject(URI url, Object request, Class responseType )
(2) T postForObject(String url, Object request, Class responseType, MapString< String, ?> urlVariables)
(3) T postForObject(String url, Object request, Class responseType, Object… urlVariables)
形式和getForObject類似。

發送方

String url = "http://localhost:8080/getUser";
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); 
map.add("name", name);
map.add("age", age);
User u = restTemplate.postForObject(url, map, User.class);

更常見的爲:

String url = "http://localhost:8080/getUser";
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); 
map.add("name", name); 
map.add("age", age);
HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map);

##還可以設置http頭
HttpHeaders headers = new HttpHeaders();
headers.add("msg", "head msg test");
HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, headers);

User u = restTemplate.postForObject(url, httpEntity, User.class);

接受方

@RequestMapping(value = "/getUser", method = RequestMethod.POST)
    public User get1(@RequestParam String name, @RequestParam Integer age, @RequestHeader(required = false) String msg)

1.2.2 postForEntity

postForEntity返回ResponseEntity,裏面包含返回消息內容和http headers,http 狀態碼。在實際中,也是往往使用PostForEntity.

1.2.3 postForLocation

postForLocation返回URI,返回的是response header中的location信息,一般用於資源定位。

1.3 其他方式

http請求共有8種方式,Post和Get是最常見的兩類方式。

方法 描述
GET 請求指定的頁面信息,並返回實體主體。
HEAD 類似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
POST 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的修改。
PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
DELETE 請求服務器刪除指定的頁面。
CONNECT HTTP/1.1協議中預留給能夠將連接改爲管道方式的代理服務器。
OPTIONS 允許客戶端查看服務器的性能。
TRACE 回顯服務器收到的請求,主要用於測試或診斷。

RestTemplate支持以下6種主要的方式。
這裏寫圖片描述
另外,exchange和execute方法提供了以上方法的通用版本,用來支持額外的、不常用的組合(如:HTTP PATCH,帶有消息體的HTTP PUT,等等)。注意,無論怎樣使用底層HTTP庫,都必須支持必要的組合。

1.4 配置

1.4.1 設置超時時間

spring boot 中

@Configuration
public class AppConfig
{
    @Bean
    @ConfigurationProperties(prefix = "custom.rest.connection")
    public HttpComponentsClientHttpRequestFactory customHttpRequestFactory() 
    {
        return new HttpComponentsClientHttpRequestFactory();
    }

    @Bean
    public RestTemplate customRestTemplate()
    {
        return new RestTemplate(customHttpRequestFactory());
    }
}

然後在配置文件指定
custom.rest.connection.connection-request-timeout=3000
custom.rest.connection.connect-timeout=3000
custom.rest.connection.read-timeout=3000

1.4.2 上傳文件

public void testUpload() throws Exception {  
    String url = "http://127.0.0.1:8080/test/upload.do";  
    String filePath = "C:\\Users\\MikanMu\\Desktop\\test.txt";  

    RestTemplate rest = new RestTemplate();  
    FileSystemResource resource = new FileSystemResource(new File(filePath));  
    MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();  
    param.add("jarFile", resource);  
    param.add("fileName", "test.txt");  
    //設置頭爲上傳
    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.MULTIPART_FORM_DATA);
    HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(param, headers);

    String string = rest.postForObject(url, httpEntity, String.class);  
}  

1.4.3 下載文件

// 小文件  
RequestEntity requestEntity = RequestEntity.get(uri).build();  
ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);  
byte[] downloadContent = responseEntity.getBody();  

// 大文件  
ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {  
    @Override  
    public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {  
        File rcvFile = File.createTempFile("rcvFile", "zip");  
        FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));  
        return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);  
    }  
};  
File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor); 

二. RestTemplate的原理(轉)

RestTemplate包含以下幾個部分:

  • HttpMessageConverter 對象轉換器
  • ClientHttpRequestFactory 默認是JDK的HttpURLConnection
  • ResponseErrorHandler 異常處理
  • ClientHttpRequestInterceptor 請求攔截器
    這裏寫圖片描述

2.1 RestTemplate類圖

這裏寫圖片描述

2.2 postForEntity 處理過程

以postForEntity方法作爲切入點,來梳理一下請求是如何執行的,以下概要流程圖。(灰色方框內爲doExcute方法的內部處理。)
postForEntity方法中,創建了兩個內部類對象requestCallback和responseExtractor並傳遞給execute方法,分別用於請求和響應的關鍵處理。
總結了一下,不管是請求還是響應,這裏的關鍵處理就是明確資源的媒體類型(也就是要明確請求端和響應端交換的信息的格式),
根據媒體類型選擇適合的解析器,將消息寫入輸出流或者從輸入流讀入。
這裏寫圖片描述

2.3 requestCallback.doWithRequest 處理過程

——內部類AcceptHeaderRequestCallback.doWithRequest的處理。
發送請求時,Http頭部需要設置Accept字段,該字段表明了發送請求的這方接受的媒體類型(消息格式),也是響應端要返回的信息的媒體類型(消息格式)。
根據postForEntity方法的第三個參數responseType,程序將選擇適合的解析器XXXConverter,並依據該解析器找出所有支持的媒體類型。

——內部類HttpEntityRequestCallback.doWithRequest的處理。
如果是POST請求並且消息體存在時,除了設置Accept字段,還可能需要設置Content-Type字段,該字段表明了所發送請求的媒體類型(消息格式),也是響應端接受的媒體類型(消息格式)。
根據postForEntity方法的第二個參數request,程序將選擇適合的解析器XXXConverter,將請求消息寫入輸出流。
這裏寫圖片描述
這裏寫圖片描述

2.4 responseExtractor.extractData 處理過程

與請求消息體的處理過程相似。
雖然,postForEntity方法中responseExtractor對象的類型爲ResponseEntityResponseExtractor,但是實際執行處理過程是HttpMessageConverterExtractor的對象實例。
在postForObject方法中,則是直接使用了HttpMessageConverterExtractor創建對象。 下圖畫出的也是HttpMessageConverterExtractor類中的extractData方法的處理過程。

這裏寫圖片描述

2.5 關於GenericHttpMessageConverter

在以上幾個方法的梳理過程中,我注意到每次消息解析轉換都要作GenericHttpMessageConverter分支判斷,爲什麼呢?

GenericHttpMessageConverter接口繼承自HttpMessageConverter接口,二者都是在org.springframework.http.converter路徑下。
此包中還有其他幾種Converter實現類,看名字就可以猜到主要功能。唯獨GenericHttpMessageConverter沒猜出來。
於是,我在eclipse中使用Ctrl+Shift+G快捷鍵搜索了一下它的實現類AbstractGenericHttpMessageConverter。
看到AbstractJackson2HttpMessageConverter類的時候,我好像明白了。
GenericHttpMessageConverter是其他轉換器派生類的接口,用於解析特殊格式的資源,比如json,xml等。
這裏寫圖片描述

2.6 關於RestTemplate 中的轉換器列表

轉換器列表messageConverters是final類型的,由RestTemplate的構造函數賦值。一旦創建了RestTemplate對象,該對象也就同時擁有了一個當前系統支持的轉換器列表。

那麼,對於需要引用jar包的轉換器,RestTemplate是怎麼添加轉換器實例的呢?
在聲明messageConverters列表之前,定義了幾個布爾型靜態常量,該常量是對某一個特殊類是否可以被加載的判斷結果。
在RestTemplate的構造函數中,根據該常量值來判斷是否將某個轉換器的實例加入到列表中。

private static boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", RestTemplate.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
                    ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());

    private static final boolean jackson2XmlPresent =
            ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", RestTemplate.class.getClassLoader());

    private static final boolean gsonPresent =
            ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader());


    private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

由此可知,RestTemplate的初始化順序:
創建(new)一個RestTemplate實例時,首先裝載RestTemplate類,然後按照出現的順序轉載靜態變量或代碼。
裝載完成之後,進行實例化。首先實例化成員變量,然後執行構造函數。

那麼,外部引用的類是否可以被加載具體是怎麼判斷的?
通過ClassUtils.isPresent(String className, ClassLoader classLoader)方法。——感覺ClassUtils可以單獨寫一篇orz
ClassUtils 類在 spring-core 工程的 org.springframework.util 路徑下。
簡單來說,isPresent實際上是返回了ClassUtils.forName方法的處理結果,當forName方法正常執行,則鑑定的類被加載,返回true;若拋出異常(注意,此處異常是Throwable)則返回false。
forName方法的處理是:
首先,根據類名的長度(<=8)來確定是否是原始類型,若是原始類型則返回類對象Class

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