最近使用restTemplate實現了一個服務對另一個服務中的Rest接口的調用,這里總結下其用法,並分析下源碼,話不多說這就開始。
一、RestTemplate簡單使用
1》首先是RestTemplate的配置文件:
package cn.com.ctsi.busiobj.config; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @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);//ms factory.setConnectTimeout(15000);//ms return factory; } }
1.使用@Configuration表名這是一個配置文件
2.使用RestTemplate的構造方法:
public RestTemplate(ClientHttpRequestFactory requestFactory) { this(); setRequestFactory(requestFactory); }
ClientHttpRequestFactory對超時時間等屬性進行設置。
3.ClientHttpRequestFactory接口有4個實現類,分別是:
2》RestTemplate的調用
Service層調用方式如下:
@Service public class CdmApiUtil { @Autowired RestTemplate restTemplate; public CdmRes sendCdmReq(CdmReq cctReq)throws Exception{ try{ CdmRes res =restTemplate.postForObject(cdmBaseUrl+tableCreateUrl,cctReq,CdmRes.class); Assert.notNull(cdmCreateTemplateTableRes,"調用接口,返回為null"); int code= res.getStatus().getCode(); Assert.isTrue((code==Status.CODE_SUCCESS),"sendCreateCdmTableReq 創建業務數據表失敗,"+ res.getStatus().getDesc());return res; }catch (Exception e){ logger.error("sendCdmReq,"+e.getMessage()); throw new Exception("調用cdm接口失敗,"+e.getMessage()); } } }
這里我們使用了RestTemplate的postForObject方法發送請求,此方法中的參數:
1)調用rest接口的url路徑,
2)拼接的請求實體對象,
3)返回的響應的class實體對象
至此就完成了對遠程rest接口的簡單調用。
二、RestTemplate使用詳解
轉自:https://blog.csdn.net/u012702547/article/details/77917939
GET請求
在RestTemplate中,發送一個GET請求,我們可以通過如下兩種方式:
第一種:getForEntity
@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); }
getForEntity方法的返回值是一個ResponseEntity<T>
,ResponseEntity<T>
是Spring對HTTP請求響應的封裝,包括了幾個重要的元素,如響應碼、contentType、contentLength、響應消息體等。比如下面一個例子:
@RequestMapping("/gethello") public String getHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); }
關於這段代碼:
- getForEntity的第一個參數為我要調用的服務的地址,這里我調用了服務提供者提供的/hello接口,注意這里是通過服務名調用而不是服務地址,如果寫成服務地址就沒法實現客戶端負載均衡了。
- getForEntity第二個參數String.class表示我希望返回的body類型是String
- 拿到返回結果之后,將返回結果遍歷打印出來
最終顯示結果如下:
有時候我在調用服務提供者提供的接口時,可能需要傳遞參數,有兩種不同的方式,如下:
@RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "張三"); return responseEntity.getBody(); } @RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); }
- 可以用一個數字做占位符,最后是一個可變長度的參數,來一一替換前面的占位符
- 也可以前面使用name={name}這種形式,最后一個參數是一個map,map的key即為前邊占位符的名字,map的value為參數值
第一個調用地址也可以是一個URI而不是字符串,這個時候我們構建一個URI即可,參數神馬的都包含在URI中了,如下:
@RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class); return responseEntity.getBody(); }
通過Spring中提供的UriComponents來構建Uri即可。
當然,服務提供者不僅可以返回String,也可以返回一個自定義類型的對象,比如我的服務提供者中有如下方法:
@RequestMapping(value = "/getbook1", method = RequestMethod.GET) public Book book1() { return new Book("三國演義", 90, "羅貫中", "花城出版社"); }
對於該方法我可以在服務消費者中通過如下方式來調用:
@RequestMapping("/book1") public Book book1() { ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/getbook1", Book.class); return responseEntity.getBody(); }
運行結果如下:
第二種:getForObject
getForObject函數實際上是對getForEntity函數的進一步封裝,如果你只關注返回的消息體的內容,對其他信息都不關注,此時可以使用getForObject,舉一個簡單的例子,如下:
@RequestMapping("/book2") public Book book2() { Book book = restTemplate.getForObject("http://HELLO-SERVICE/getbook1", Book.class); return book; }
getForObject也有幾個重載方法,如下:
@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); }
這幾個重載方法參數的含義和getForEntity一致,我就不再贅述了。
在RestTemplate中,POST請求可以通過如下三個方法來發起:
第一種:postForEntity
該方法和get請求中的getForEntity方法類似,如下例子:
@RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("紅樓夢"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); }
- 方法的第一參數表示要調用的服務的地址
- 方法的第二個參數表示上傳的參數
- 方法的第三個參數表示返回的消息體的數據類型
我這里創建了一個Book對象,這個Book對象只有name屬性有值,將之傳遞到服務提供者那里去,服務提供者代碼如下:
@RequestMapping(value = "/getbook2", method = RequestMethod.POST) public Book book2(@RequestBody Book book) { System.out.println(book.getName()); book.setPrice(33); book.setAuthor("曹雪芹"); book.setPublisher("人民文學出版社"); return book; }
服務提供者接收到服務消費者傳來的參數book,給其他屬性設置上值再返回,調用結果如下:
postForEntity的其他重載方法如下:
@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); }
這些方法的參數含義和getForEntity參數的含義一致,不再贅述。
第二種:postForObject
如果你只關注,返回的消息體,可以直接使用postForObject。用法和getForObject一致。
@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); }
第三種:postForLocation
postForLocation也是提交新資源,提交成功之后,返回新資源的URI,postForLocation的參數和前面兩種的參數基本一致,只不過該方法的返回值為Uri,這個只需要服務提供者返回一個Uri即可,該Uri表示新資源的位置。
PUT請求
在RestTemplate中,PUT請求可以通過put方法調用,put方法的參數和前面介紹的postForEntity方法的參數基本一致,只是put方法沒有返回值而已。舉一個簡單的例子,如下:
@RequestMapping("/put") public void put() { Book book = new Book(); book.setName("紅樓夢"); restTemplate.put("http://HELLO-SERVICE/getbook3/{1}", book, 99); }
book對象是我要提交的參數,最后的99用來替換前面的占位符{1}
DELETE請求
delete請求我們可以通過delete方法調用來實現,如下例子:
@RequestMapping("/delete") public void delete() { restTemplate.delete("http://HELLO-SERVICE/getbook4/{1}", 100); }
delete方法也有幾個重載的方法,不過重載的參數和前面基本一致,不贅述。
三、源碼分析
以postForObject為例進行源碼分析:
@Override @Nullable public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType);//1. HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters());//2. return execute(url, HttpMethod.POST, requestCallback, responseExtractor);//3. }
httpEntityCallback(@Nullable Object requestBody, Type responseType)方法:
protected <T> RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) { return new HttpEntityRequestCallback(requestBody, responseType); }
private HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) { super(responseType); if (requestBody instanceof HttpEntity) { this.requestEntity = (HttpEntity<?>) requestBody; } else if (requestBody != null) { this.requestEntity = new HttpEntity<>(requestBody); } else { this.requestEntity = HttpEntity.EMPTY; } }
1)如果傳遞進來的requestBody是HttpEntity類型則將其強制轉型為HttpEntity<?>類型;
2)否則如果requestBody不為null,則將其轉換成HttpEntity格式
3)否則如果requestBody為null,則創建一個沒有請求頭和請求體的空HttpEntity。
HttpEntity實體類如下:
public class HttpEntity<T> { /** * The empty {@code HttpEntity}, with no body or headers. */ public static final HttpEntity<?> EMPTY = new HttpEntity<>(); private final HttpHeaders headers; @Nullable private final T body; /** * Create a new, empty {@code HttpEntity}. */ protected HttpEntity() { this(null, null); } /** * Create a new {@code HttpEntity} with the given body and no headers. * @param body the entity body */ public HttpEntity(T body) { this(body, null); } /** * Create a new {@code HttpEntity} with the given headers and no body. * @param headers the entity headers */ public HttpEntity(MultiValueMap<String, String> headers) { this(null, headers); } /** * Create a new {@code HttpEntity} with the given body and headers. * @param body the entity body * @param headers the entity headers */ public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) { this.body = body; HttpHeaders tempHeaders = new HttpHeaders(); if (headers != null) { tempHeaders.putAll(headers); } this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders); } /** * Returns the headers of this entity. */ public HttpHeaders getHeaders() { return this.headers; } /** * Returns the body of this entity. */ @Nullable public T getBody() { return this.body; } /** * Indicates whether this entity has a body. */ public boolean hasBody() { return (this.body != null); }
....... }
即為將requestBody寫成HttpEntity的請求頭、請求體的形式。
下面我們來看第二步:
官方對HttpMessageConverterExtractor的解釋是:
Response extractor that uses the given {@linkplain HttpMessageConverter entity converters}
* to convert the response into a type {@code T}
即能夠將返回的相應轉換為某個特定的類型responseType。
第三步:
首先將String類型的URI轉換為URI類型,然后execute方法底層調用doExecute方法。
@Override public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException { URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); }
/** * Execute the given method on the provided URI. * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }
doExecute方法:
1.首先使用請求的url和method(post 或者get)構造出一個ClientHttpRequest。
2.requestCallback.doWithRequest將之前的requestBody requestHeader放入此ClientHttpRequest中;
3.調用request的execute方法獲得response,調用handleResponse方法處理response中存在的error;
4.使用ResponseExtractor的extraData方法將返回的response轉換為某個特定的類型;
5.最后記得關閉ClientHttpResponse資源,這樣就完成了發送請求並獲得對應類型的返回值的全部過程。
四、另外一種方式:使用RestTemplateUtil使用restTemplate
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package ctsi.mts.common.mvc.util; import com.fasterxml.jackson.databind.DeserializationFeature; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestTemplate; public class RestTemplateUtil { private static Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class); public RestTemplateUtil() { } private static RestTemplate getRestTemplate() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.getObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); List<HttpMessageConverter<?>> lstConverters = new ArrayList(); lstConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8"))); lstConverters.add(converter); RestTemplate rt = new RestTemplate(); rt.setMessageConverters(lstConverters); return rt; } public static String getForStringResult(String url) { return (String)get(url, String.class, (Map)null); } public static String getForStringResult(String url, Map<String, ?> urlVariables) { return (String)get(url, String.class, urlVariables); } public static <T> T get(String url, Class<T> responseType) { return get(url, responseType, (Map)null); } public static <T> T get(String url, Class<T> responseType, Map<String, ?> urlVariables) { logger.info("[rest get] {}", url); logger.info("[responseType] {}", responseType); Object result; if (urlVariables == null) { result = getRestTemplate().getForObject(url, responseType, new Object[0]); } else { Iterator var4 = urlVariables.entrySet().iterator(); while(var4.hasNext()) { Entry entry = (Entry)var4.next(); logger.info("[params] {} = {}", entry.getKey(), entry.getValue()); } result = getRestTemplate().getForObject(url, responseType, urlVariables); } logger.info("[result] {}", result); return result; } public static String postForStringResult(String url, Object request) { return (String)post(url, request, String.class, (Map)null); } public static String postForStringResult(String url, Object request, Map<String, ?> urlVariables) { return (String)post(url, request, String.class, urlVariables); } public static <T> T post(String url, Object request, Class<T> responseType) { return post(url, request, responseType, (Map)null); } public static <T> T post(String url, Object request, Class<T> responseType, Map<String, ?> urlVariables) { logger.info("[rest post] {}", url); logger.info("[request] {}", request); logger.info("[responseType] {}", responseType); Object result; if (urlVariables == null) { result = getRestTemplate().postForObject(url, request, responseType, new Object[0]); } else { Iterator var5 = urlVariables.entrySet().iterator(); while(var5.hasNext()) { Entry entry = (Entry)var5.next(); logger.info("[params] {} = {}", entry.getKey(), entry.getValue()); } result = getRestTemplate().postForObject(url, request, responseType, urlVariables); } logger.info("[result] {}", result); return result; } }
看了下,大致只有getForString getForObject postForString postForObject沒有forEntity類型,對restTemplate的readTimeOut proxy等屬性沒有要求時可通過RestTemplateUtil來獲取restTemplate對象。