您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何使用RestTemplate”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
本文主要介紹 Spring Web 模塊中的 RestTemplate 組件的原理、優(yōu)缺點(diǎn)、以及如何擴(kuò)展以滿足各種需求。
在介紹 RestTemplate 之前,我們先來(lái)談?wù)?HTTP Client,談?wù)勥x擇一個(gè)優(yōu)秀的 HTTP Client 實(shí)現(xiàn)的的重要性,以及一個(gè)優(yōu)秀的 HTTP Client 應(yīng)該具備哪些特性。
在 Java 社區(qū)中,HTTP Client 主要有 JDK 的 HttpURLConnection、Apache Commons HttpClient(或被稱為 Apache HttpClient 3.x)、Apache HttpComponents Client(或被稱為 Apache HttpClient 4.x)、Square 公司開源的 OkHttp。
除了這幾個(gè)純粹的 HTTP Client 類庫(kù)以外,還有 Spring 的 RestTemplate、Square 公司的 Retrofit、Netflix 公司的 Feign,以及像 Apache CXF 中的 client 組件。這些框架和類庫(kù)主要是針對(duì) Web Service 場(chǎng)景,尤其是 RESTful Web Service。它們往往是基于前面提到的 HTTP Client 實(shí)現(xiàn),并在其基礎(chǔ)上提供了消息轉(zhuǎn)換、參數(shù)映射等對(duì)于 Web Service 來(lái)說(shuō)十分必要的功能。
(當(dāng)然,像 Netty、Mina 這樣的網(wǎng)絡(luò) IO 框架,實(shí)現(xiàn) HTTP 自然也不再話下,但這些框架通常過(guò)于底層,不會(huì)被直接使用)
雖然現(xiàn)在服務(wù)間的調(diào)用越來(lái)越多地使用了 RPC 和消息隊(duì)列,但是 HTTP 依然有適合它的場(chǎng)景。
RPC 的優(yōu)勢(shì)在于高效的網(wǎng)絡(luò)傳輸模型(常使用 NIO 來(lái)實(shí)現(xiàn)),以及針對(duì)服務(wù)調(diào)用場(chǎng)景專門設(shè)計(jì)協(xié)議和高效的序列化技術(shù)。而 HTTP 的優(yōu)勢(shì)在于它的成熟穩(wěn)定、使用實(shí)現(xiàn)簡(jiǎn)單、被廣泛支持、兼容性良好、防火墻友好、消息的可讀性高。所以在開放 API、跨平臺(tái)的服務(wù)間調(diào)用、對(duì)性能要求不苛刻的場(chǎng)景中有著廣泛的使用。
正式因?yàn)?HTTP 存在著很廣泛的應(yīng)用場(chǎng)景,所以選擇一個(gè)優(yōu)秀的 HTTP Client 便是十分重要的。
連接池
超時(shí)時(shí)間設(shè)置(連接超時(shí)、讀取超時(shí)等)
是否支持異步
請(qǐng)求和響應(yīng)的編解碼
可擴(kuò)展性
因?yàn)槟壳?HTTP 1.1 不支持多路復(fù)用,只有 HTTP Pipeline 這用半復(fù)用的模型支持。所以,在需要頻繁發(fā)送消息的場(chǎng)景中,連接池使必須支持的,以減少頻繁建立連接所帶來(lái)的不必要的性能損耗。
當(dāng)對(duì)端出現(xiàn)問(wèn)題的時(shí)候,長(zhǎng)時(shí)間的,甚至是無(wú)限的超時(shí)等待是絕對(duì)不能接受的。所以必須必須能夠設(shè)置超時(shí)時(shí)間。
HTTP 相關(guān)技術(shù)(服務(wù)器端和客戶端)通常被人認(rèn)為是性能低下的一個(gè)重要原因在于,在很長(zhǎng)一段時(shí)間里,HTTP 的相關(guān)實(shí)現(xiàn)缺乏對(duì)異步的支持。這不僅指非阻塞 IO,也包括異步的編程模型。缺乏異步編程模型的后果就是,即便 HTTP 協(xié)議棧是基于非阻塞 IO 實(shí)現(xiàn)的,調(diào)用客戶端的或者在服務(wù)端處理消息的線程有大量時(shí)間被浪費(fèi)在了等待 IO 上面。所以,異步是非常重要的特性。
通常,開發(fā)人員希望面向?qū)ο笫褂酶鞣N服務(wù)(這里面自然也包括基于 HTTP 協(xié)議的服務(wù)),而不是直接面對(duì)原始的消息和響應(yīng)開發(fā)。所以,透明地將 HTTP 請(qǐng)求和響應(yīng)進(jìn)行編解碼是十分有必要,因?yàn)檫@可以很大程度地降低開發(fā)人員的工作量。
不論一個(gè)框架設(shè)計(jì)的多好,總有一些特殊場(chǎng)景是它們無(wú)法原生支持的。這時(shí)可擴(kuò)展性的好壞便體現(xiàn)出來(lái)了。
基于上述幾點(diǎn)的考慮,RestTemplate 是相對(duì)好的選擇。原因在于 RestTemplate 本身基于成熟的 HTTP Client 實(shí)現(xiàn)(Apache HttpClient、OkHttp 等),并可以靈活地在這些實(shí)現(xiàn)中切換,而且具有良好的擴(kuò)展性。最重要的是提供了前面幾個(gè) HTTP Client 不具備的消息編解碼能力。
這里要提一句為什么沒有自己封裝 HTTP Client 的原因。這個(gè)原因在于想要基于一種 HTTP Client 去提供消息編解碼能力和一定的擴(kuò)展能力并不難,但是如果要設(shè)計(jì)出一個(gè)通用的,對(duì)底層實(shí)現(xiàn)透明的,具有優(yōu)秀如 Spring 的擴(kuò)展性設(shè)計(jì)的框架并不是一件容易事。這里的不易并不在于技術(shù)有多高深,而是在于優(yōu)秀的擴(kuò)展性設(shè)計(jì)往往源自從眾多優(yōu)秀程序員、社區(qū)和軟件公司得到的豐富的一線實(shí)踐經(jīng)驗(yàn),再由像 Spring 轉(zhuǎn)換為最終設(shè)計(jì)。這樣的產(chǎn)品不是一朝一夕就能得到的。在我們覺得自己打造自己的工具之前,我們可以先深入了解現(xiàn)有的優(yōu)秀功能都能做到什么。
欲揚(yáng)先抑,我們先來(lái)看加入使用 RestTemplate,可能會(huì)遇到哪些“坑”。
雖然 spring-web 模塊對(duì)其它 Spring 模塊并沒有顯式的依賴(Maven dependency 的 scope 為 compile),但是對(duì)于一些功能,比如異步版本的 RestTemplate,要求必須有 4.1 以上版本的 spring-core 模塊。
所以,要想 RestTemplate 完全發(fā)揮其功能,最好能有相近版本的其它的 Spring 模塊相配合(spring-core、spring-context、spring-beans、spring-aop)
Spring Web 模塊中的 RestTemplate 是一個(gè)很不錯(cuò)的面向 RESTful Web 服務(wù)的客戶端。它提供了很多簡(jiǎn)化對(duì) RESTful Web 服務(wù)調(diào)用的功能,例如 Path Parameter 的格式化功能(/hotels/{hotel_id}/books/{book_id},這里的 hotel_id 和 book_id 就是 Path Paramter)、JSON 或 XML 等格式的數(shù)據(jù)與實(shí)體類之間的透明轉(zhuǎn)換等。
所謂默認(rèn)情況指的是不去擴(kuò)展 RestTemplate 所提供的類或接口,而是完全依賴其本身提供的代碼。在這種情況下,RestTemplate 還是有一些不便的地方。例如,它的 Path Parameter 格式化功能,對(duì)于普通 HTTP 服務(wù)的調(diào)用來(lái)說(shuō),反而成為了一個(gè)缺點(diǎn),因?yàn)槠胀ǖ?HTTP 服務(wù)的 GET 方法常使用 Query Parameter,而不是 Path Parameter。Query Paramter 的形式是 an_http_url?name1=value1&name2=value2。例如 getOrder.action?order_code=xxx。如果使用 RestTemplate,作為參數(shù)傳遞給 RestTemplate 的 URL 就必須是 getOrder.action?order_code={order_code}。如果是固定的參數(shù)還好,如果一個(gè) HTTP 服務(wù)的 Query Parameter 是可變的,那就很不方便了。
注意,下面涉及到的代碼都是基于 spring-web 4.2.6.RELEASE 版本
上面提到,RestTemplate 的 getForEntity、getForObject、postForEntity 等方法中的 Map 參數(shù)是 uriVariables,即我們常說(shuō)的 Path Param,而非 Query Param(這兩個(gè)參數(shù)的定義可以參照 JAX-RS 中 @PathParam 和 @QueryParam 的定義)。
Path Param 是 URL 的一部分,RESTful 的 Web Service 會(huì)按照其定義的 URL Template 從 URL 中解析出其對(duì)應(yīng)的值
RestTemplate 的這種機(jī)制面對(duì) RESTful 的 Web Service 無(wú)疑是方便的,但很多情況下我們還是希望 RestTemplate 能夠在開發(fā)人員不用編寫額外代碼的情況下將 Map 類型的參數(shù)當(dāng)做 Query Param 發(fā)送給對(duì)端的服務(wù)。
幸好來(lái)自 Spring 大家庭的 RestTemplate 也具有良好的可擴(kuò)展性,其具有一個(gè)名為 UriTemplateHandler 擴(kuò)展點(diǎn)。因?yàn)椴徽撌?Path Param 還是 Query Param,它們都是 URI 的一部分,所以只需實(shí)現(xiàn)自定義的 URI 生成機(jī)制即可解決這個(gè)問(wèn)題。
通過(guò)擴(kuò)展 DefaultUriTemplateHandler,我們可以將 Map<String, ?> uriVariables 也作為 Query Param。具體實(shí)現(xiàn)如下:
public class QueryParamsUrlTemplateHandler extends DefaultUriTemplateHandler { @Override public URI expand(String uriTemplate, Map<String, ?> uriVariables) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(uriTemplate); for (Map.Entry<String, ?> varEntry : uriVariables.entrySet()) { uriComponentsBuilder.queryParam(varEntry.getKey(), varEntry.getValue()); } uriTemplate = uriComponentsBuilder.build().toUriString(); return super.expand(uriTemplate, uriVariables); } }
上面的實(shí)現(xiàn)基于 DefaultUriTemplateHandler,所以保有了原來(lái)設(shè)置 Path Param 的功能。
實(shí)現(xiàn)這個(gè)需求有多種方法,比如通過(guò)攔截器。這里使用另一個(gè)方法,通過(guò)一個(gè)自定義的 ClientHttpRequestFactory
public class CustomHeadersClientHttpRequestFactoryWrapper extends AbstractClientHttpRequestFactoryWrapper { private HttpHeaders customHeaders = new HttpHeaders(); /** * Create a {@code AbstractClientHttpRequestFactoryWrapper} wrapping the given request factory. * * @param requestFactory the request factory to be wrapped */ protected CustomHeadersClientHttpRequestFactoryWrapper(ClientHttpRequestFactory requestFactory) { super(requestFactory); } @Override protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) throws IOException { ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); for (Map.Entry<String, List<String>> headerEntry : customHeaders.entrySet()) { request.getHeaders().put(headerEntry.getKey(), headerEntry.getValue()); } return request; } public void addHeader(String header, String... values) { customHeaders.put(header, Arrays.asList(values)); } }
RestTemplate 提供了良好的擴(kuò)展性,但是有些設(shè)置是使用 ``
RestTemplate 本身并沒有做 HTTP 底層的實(shí)現(xiàn),而是利用了現(xiàn)有的技術(shù),如 JDK 或 Apache HttpClient 等。
RestTemplate 需要使用一個(gè)實(shí)現(xiàn)了 ClientHttpRequestFactory 接口的類為其提供 ClientHttpRequest 實(shí)現(xiàn)(另外還有 AsyncClientHttpRequestFactory 對(duì)應(yīng)于異步 HTTP 實(shí)現(xiàn),這里暫且不表)。而 ClientHttpRequest 則實(shí)現(xiàn)封裝了組裝、發(fā)送 HTTP 消息,以及解析響應(yīng)的的底層細(xì)節(jié)。
目前(4.2.6.RELEASE)的 RestTemplate 主要有四種 ClientHttpRequestFactory 的實(shí)現(xiàn),它們分別是:
基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory
基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory
基于 OkHttp 2(OkHttp 最新版本為 3,有較大改動(dòng),包名有變動(dòng),不和老版本兼容)的 OkHttpClientHttpRequestFactory
基于 Netty4 的 Netty4ClientHttpRequestFactory
另外,還有用于提供攔截器功能的 InterceptingClientHttpRequestFactory。
寫消息指的是 requestBody 轉(zhuǎn)換為某一種格式,如 JSON、XML 的數(shù)據(jù)的過(guò)程。
spring-web 模塊提供了一個(gè) HttpMessageConverter 接口,用來(lái)讀寫 HTTP 消息。這個(gè)接口不僅被 RestTemplate 使用,也被 Spring MVC 所使用。
spring-web 模塊提供了基于 Jackson、GSON 等類庫(kù)的 HttpMessageConverter,用于進(jìn)行 JSON 或 XML 格式數(shù)據(jù)的轉(zhuǎn)換。
RestTemplate 在發(fā)送消息時(shí),會(huì)根據(jù)消息的 ContentType 或者 RequestBody 對(duì)象本身的一些屬性判斷究竟是使用哪個(gè) HttpMessageConverter 寫消息。
具體來(lái)說(shuō),如果 RequestBody 是一個(gè) HttpEntity 的話,會(huì)從中讀取 ContentType 屬性。同時(shí),RequestBody 對(duì)象本身也會(huì)覺得一個(gè) HttpMessageConverter 是否會(huì)處理這個(gè)對(duì)象。例如,ProtobufHttpMessageConverter 會(huì)要求 RequestBody 對(duì)象必須實(shí)現(xiàn) com.google.protobuf.Message 接口。
讀消息指的是讀取 HTTP Response 中的數(shù)據(jù),轉(zhuǎn)換為用戶指定的格式(通過(guò) Class<T> responseType 參數(shù)指定)。類似于寫消息的處理,讀消息的處理也是通過(guò) ContentType 和 responseType 來(lái)選擇的相應(yīng) HttpMessageConverter 來(lái)進(jìn)行的。
RestTemplate 提供了一個(gè) ResponseErrorHandler 的接口,用來(lái)處理錯(cuò)誤的 Response??梢酝ㄟ^(guò)設(shè)置自定義的 ResponseErrorHandler 來(lái)實(shí)現(xiàn)擴(kuò)展。
根據(jù)我上面表達(dá)的思想,一個(gè)統(tǒng)一、規(guī)范和簡(jiǎn)化 RestTemplate 使用的工具已經(jīng)產(chǎn)生,不過(guò)暫時(shí)由于其代碼是公司項(xiàng)目的一部分,所以暫時(shí)不便公開。而且我希望是在這個(gè)工具經(jīng)過(guò)了更多的實(shí)踐考驗(yàn)之后再貢獻(xiàn)出來(lái)會(huì)更好。
目前的一個(gè)完整使用案例如下:
@Configuration public class SpringConfigurationDemo { @Bean public RestTemplate myRestTemplate() { return RestTemplateBuilder.create() .withClientKey("myRestTemplate") .implementation(HttpClientImplementation.OK_HTTP) .clearMessageConverters() .setMessageConverter(new MappingJackson2HttpMessageConverter(), MediaType.TEXT_PLAIN) .enableAutoQueryParams() .connectTimeout(100) .readTimeout(200) .header(HttpHeaders.USER_AGENT, "MyAgent") .build(); } }
雖然 RestTemplate 是一個(gè)很不錯(cuò)的 HTTP Client,但 Netflix 已經(jīng)開源了一個(gè)更好地 HTTP Client 工具 - Feign。它是一個(gè)聲明式的 HTTP Client,在易用性、可讀性等方面大幅領(lǐng)先于現(xiàn)有的工具。我打算稍后寫一篇文章分析 Feign 的思想、原理和優(yōu)點(diǎn)(原理其實(shí)不復(fù)雜,但是能想到這么做的卻沒幾個(gè),原創(chuàng)的創(chuàng)新思想永遠(yuǎn)是最可貴的)
“如何使用RestTemplate”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。