詳解RestTemplate?用法
一、簡(jiǎn)言
RestTemplate 是從 Spring3.0 開(kāi)始支持的一個(gè) HTTP 請(qǐng)求工具,也有的稱之為網(wǎng)絡(luò)框架,說(shuō)白了就是Java版本的一個(gè)postman。專門用于在Java當(dāng)中服務(wù)與服務(wù)之間遠(yuǎn)程調(diào)用接口的時(shí)候用。
舉例A服務(wù),需要從B服務(wù)獲取到數(shù)據(jù),假如不經(jīng)過(guò)前端頁(yè)面只是后端A服務(wù)獲取B服務(wù)的數(shù)據(jù)的話,這時(shí)候就需要通過(guò)Java的HTTP請(qǐng)求工具來(lái)遠(yuǎn)程調(diào)用。
類似的還有Apache 的 HttpClient 以及OKHttp3,都是Java當(dāng)中常用的HTTP 請(qǐng)求工具。
關(guān)于HttpClient用法:http://chabaoo.cn/article/256131.htm
RestTemplate優(yōu)點(diǎn):
- RestTemplate屬于spring的,假如springboot項(xiàng)目的話,完全不用引入任何其他依賴,直接可以用。
- RestTemplate可以和cloud當(dāng)中的ribbon進(jìn)行配合使用,只需要一個(gè)注解就可以完成負(fù)載均衡調(diào)用。
官網(wǎng)api:https://docs.spring.io/spring-framework/docs/5.0.9.RELEASE/javadoc-api/
二、注入容器
既然是遠(yuǎn)程調(diào)用,那么我們系統(tǒng)肯定不會(huì)只調(diào)用一次,通常情況我們會(huì)將RestTemplate注入到容器當(dāng)中,讓他保持單例,當(dāng)我們哪個(gè)類要使用的時(shí)候直接從容器里面獲取即可。這樣可以避免每調(diào)用一次創(chuàng)建一個(gè)RestTemplate對(duì)象。
2.1、普通配置
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextBean { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
使用的時(shí)候直接通過(guò)容器注入即可。
@Autowired private RestTemplate restTemplate;
2.2、詳細(xì)配置
對(duì)于一個(gè)請(qǐng)求來(lái)說(shuō),連接超時(shí)時(shí)間,請(qǐng)求超時(shí)時(shí)間等都是可以設(shè)置的參數(shù),為了更好的適應(yīng)業(yè)務(wù)需求,所以可以自己修改restTemplate的配置。如下利用@Bean注解的參數(shù)特性,形成了一個(gè)實(shí)例化鏈條。(我實(shí)際開(kāi)發(fā)當(dāng)中一般沒(méi)有詳細(xì)設(shè)置過(guò),包括我涉及到的項(xiàng)目也是,一般都是直接像上面的簡(jiǎn)單配置一下就開(kāi)始使用了)
@Bean注解修飾的方法,假如帶有參數(shù),其實(shí)就是代表@Bean所標(biāo)注的對(duì)象的實(shí)例化依賴于參數(shù)中的類,需要先將參數(shù)中的類實(shí)例化,才能實(shí)例化@Bean所標(biāo)注的對(duì)象。
import org.apache.http.Header; import org.apache.http.client.HttpClient; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.List; @Configuration public class RestTemplateConfig { /** * http連接管理器 * * @return */ @Bean public HttpClientConnectionManager poolingHttpClientConnectionManager() { /* 注冊(cè)http和https請(qǐng)求 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/ PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(); // 最大連接數(shù) poolingHttpClientConnectionManager.setMaxTotal(500); // 同路由并發(fā)數(shù)(每個(gè)主機(jī)的并發(fā)) poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); return poolingHttpClientConnectionManager; } /** * HttpClient * * @param poolingHttpClientConnectionManager * @return */ @Bean public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); // 設(shè)置http連接管理器 httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); // 設(shè)置重試次數(shù) httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 設(shè)置默認(rèn)請(qǐng)求頭(具體可根據(jù)自己業(yè)務(wù)場(chǎng)景進(jìn)行設(shè)置) List<Header> headers = new ArrayList<>(); headers.add(new BasicHeader("atoken", "WAKJDWAJLDKWAKDLKWALKDALKW")); httpClientBuilder.setDefaultHeaders(headers); return httpClientBuilder.build(); } /** * 請(qǐng)求連接池配置 * * @param httpClient * @return */ @Bean public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) { HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(); // httpClient創(chuàng)建器 clientHttpRequestFactory.setHttpClient(httpClient); // 連接超時(shí)時(shí)間/毫秒(連接上服務(wù)器(握手成功)的時(shí)間,超出拋出connect timeout) clientHttpRequestFactory.setConnectTimeout(5 * 1000); // 數(shù)據(jù)讀取超時(shí)時(shí)間(socketTimeout)/毫秒(務(wù)器返回?cái)?shù)據(jù)(response)的時(shí)間,超過(guò)拋出read timeout) clientHttpRequestFactory.setReadTimeout(10 * 1000); // 連接池獲取請(qǐng)求連接的超時(shí)時(shí)間,不宜過(guò)長(zhǎng),必須設(shè)置/毫秒(超時(shí)間未拿到可用連接,會(huì)拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool) clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000); return clientHttpRequestFactory; } /** * rest模板 * * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { // boot中可使用RestTemplateBuilder.build創(chuàng)建 RestTemplate restTemplate = new RestTemplate(); // 配置請(qǐng)求工廠 restTemplate.setRequestFactory(clientHttpRequestFactory); // 添加攔截器(這塊可以通過(guò)攔截器來(lái)實(shí)現(xiàn)日志打印,既然是攔截器,那肯定會(huì)丟失一定的性能,所以可根據(jù)情況使用,如果計(jì)劃在攔截器當(dāng)中打印request當(dāng)中的日志,一定要注意request是流,不能讀兩次,這塊要做的話,需要想辦法將流取出然后再替換一個(gè)新的流) // List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(); // interceptors.add(restTemplateLog); // restTemplate.setInterceptors(interceptors); return restTemplate; } }
三、GET請(qǐng)求
這里的方法一共有兩類,getForEntity
和 getForObject
,每一類有三個(gè)重載方法,下面我們分別予以介紹,首先我們先了解一下傳參:
- String url:請(qǐng)求接口地址
- URI url:使用 Uri 對(duì)象時(shí),參數(shù)可以直接拼接在地址中
- Class<T> responseType:返回的response當(dāng)中body的對(duì)象類型,相當(dāng)于只要這塊寫好了,我們就不用將返回值 再進(jìn)行序列化成對(duì)象了。
- Map<String,?> uriVariables:map類型的key value參數(shù),這個(gè)key需要和地址當(dāng)中的key相對(duì)應(yīng)的
- Object... uriVariables:這是一個(gè)可變長(zhǎng)參數(shù),地址欄當(dāng)中可以用1,2數(shù)字占位符當(dāng)中參數(shù),傳參的時(shí)候,只要這塊的可變長(zhǎng)參數(shù)能按順序和占位符 一 一對(duì)上即可。
3.1、getForEntity
把這個(gè)接口當(dāng)做要遠(yuǎn)程調(diào)用的接口:
@GetMapping("/getTest2") public String getTest2(String name) { return "hello " + name + " !"; }
這種寫法實(shí)際是就相當(dāng)于是這樣的:http://localhost:8006/getTest2?name=222
那么遠(yuǎn)程調(diào)用怎么調(diào)用呢?下面一共提供了三種寫法!
@GetMapping("/test") public void test() throws UnsupportedEncodingException { // 第一種方案(這種方案有點(diǎn)類似hibernate占位符取值,getForEntity第三個(gè)參數(shù)就是一個(gè)可變長(zhǎng)參數(shù),當(dāng)get請(qǐng)求有多個(gè)參數(shù)的時(shí)候也是可以用這種方式的) String url1 = "http://127.0.0.1:8006/getTest2?name={1}"; // 這塊的String.class就是ResponseEntity當(dāng)中的body要序列化成的java對(duì)象類型 ResponseEntity<String> responseEntity1 = restTemplate.getForEntity(url1, String.class, "aaa"); System.out.println(responseEntity1.getStatusCode()); System.out.println(responseEntity1.getBody()); System.out.println(responseEntity1.getHeaders()); // 第二種方案,將參數(shù)放入到一個(gè) map 中。map 中的 key 和占位符的 key 相對(duì)應(yīng),map 中的 value 就是參數(shù)的具體值 Map<String, Object> map = new HashMap<>(); String url2 = "http://127.0.0.1:8006/getTest2?name={name}"; map.put("name", "bbb"); ResponseEntity<String> responseEntity2 = restTemplate.getForEntity(url2, String.class, map); System.out.println(responseEntity2.getStatusCode()); System.out.println(responseEntity2.getBody()); System.out.println(responseEntity2.getHeaders()); // 第三種方案,使用 Uri 對(duì)象時(shí),參數(shù)可以直接拼接在地址中 String url = "http://127.0.0.1:8006/getTest2?name=" + URLEncoder.encode("ccc", "UTF-8"); URI uri = URI.create(url); ResponseEntity<String> responseEntity3 = restTemplate.getForEntity(uri, String.class); System.out.println(responseEntity3.getStatusCode()); System.out.println(responseEntity3.getBody()); System.out.println(responseEntity3.getHeaders()); }
RestTemplate 發(fā)送的是 HTTP 請(qǐng)求,那么在響應(yīng)的數(shù)據(jù)中必然也有響應(yīng)頭,如果開(kāi)發(fā)者需要獲取響應(yīng)頭的話,那么就需要使用 getForEntity
來(lái)發(fā)送 HTTP 請(qǐng)求,此時(shí)返回的對(duì)象是一個(gè) ResponseEntity
的實(shí)例。通過(guò)ResponseEntity
我們可以獲取請(qǐng)求的響應(yīng)詳細(xì)信息,例如:body請(qǐng)求體,header請(qǐng)求頭,status狀態(tài)碼
ResponseEntity對(duì)象,他實(shí)際上就是包含了例如網(wǎng)頁(yè)訪問(wèn)時(shí)候的響應(yīng)信息。
3.2、getForObject
getForObject
方法和 getForEntity
方法類似,getForObject
方法也有三個(gè)重載的方法,參數(shù)和 getForEntity
一樣,因此這里我就不重復(fù)介紹參數(shù)了,這里主要說(shuō)下 getForObject
和 getForEntity
的差異,這兩個(gè)的差異主要體現(xiàn)在返回值的差異上, getForObject
的返回值就是服務(wù)提供者返回的數(shù)據(jù),使用 getForObject
無(wú)法獲取到響應(yīng)頭。
把下面接口當(dāng)做要遠(yuǎn)程調(diào)用的接口:
// 沒(méi)有參數(shù)的時(shí)候調(diào)用 @GetMapping("/getTest1") public String getTest1() { return "getTest1"; } // ?拼接的參數(shù) @GetMapping("/getTest2") public String getTest2(String name) { return "hello " + name + " !"; } // 斜杠地址欄/ 拼接的參數(shù) @GetMapping("/getTest3/{name}") public String getTest3(@PathVariable(value = "name") String name) { return "hello " + name + " !"; }
那么遠(yuǎn)程調(diào)用怎么調(diào)用呢?下面提供多個(gè)示例供參考!
@GetMapping("/test1") public void test1() throws UnsupportedEncodingException { // 無(wú)參調(diào)用 String result = restTemplate.getForObject("http://127.0.0.1:8006/getTest1", String.class); System.out.println(result); String url2 = "http://127.0.0.1:8006/getTest2?name={1}"; String result1 = restTemplate.getForObject(url2, String.class, "aaa"); System.out.println(result1); // 第一種方案 調(diào)用地址欄/拼接 String url3 = "http://127.0.0.1:8006/getTest3/{1}"; String result2 = restTemplate.getForObject(url3, String.class, "bbb"); System.out.println(result2); // 第二種方案 調(diào)用地址欄/拼接 String url4 = "http://127.0.0.1:8006/getTest3/" + URLEncoder.encode("ccc", "UTF-8"); URI uri = URI.create(url4); String result3 = restTemplate.getForObject(uri, String.class); System.out.println(result3); // 第三種方案 調(diào)用地址欄/拼接 Map<String, Object> map = new HashMap<>(); String url5 = "http://127.0.0.1:8006/getTest3/{name}"; map.put("name", "ddd"); String result4 = restTemplate.getForObject(url5, String.class, map); System.out.println(result4); }
通過(guò)以上練習(xí)后會(huì)發(fā)現(xiàn)一個(gè)致命問(wèn)題,提供的get請(qǐng)求的API當(dāng)中并沒(méi)有header傳參這一項(xiàng),假如請(qǐng)求的別的服務(wù)接口有認(rèn)證,那我們?cè)撛趺崔k呢?
我們可以使用RestTemplate 當(dāng)中的 exchange自定義請(qǐng)求。
四、POST 請(qǐng)求
可以看到,post 請(qǐng)求的方法類型除了 postForEntity
和 postForObject
之外,還有一個(gè) postForLocation
。這里的方法類型雖然有三種,但是這三種方法重載的參數(shù)基本是一樣的。其余參數(shù)都和get一樣,就多了一個(gè)request參數(shù),如下:
Object request
: 該參數(shù)可以是一個(gè)普通對(duì)象, 也可以是一個(gè)HttpEntity對(duì)象。
- 如果是一個(gè)普通對(duì)象, 而非HttpEntity對(duì)象的時(shí)候, RestTemplate會(huì)將請(qǐng)求對(duì)象轉(zhuǎn)換為一個(gè)HttpEntity對(duì)象來(lái)處理, 其中Object就是 request請(qǐng)求體(body)的類型, request內(nèi)容會(huì)被視作完整的body來(lái)處理;
- 如果 request 是一個(gè)HttpEntity對(duì)象, 那么就會(huì)被當(dāng)作一個(gè)完成的HTTP請(qǐng)求對(duì)象來(lái)處理, 這個(gè) request 中不僅包含了body的內(nèi)容, 也包含了header的內(nèi)容。
如下源碼是對(duì)request參數(shù)進(jìn)行轉(zhuǎn)換調(diào)用的:
什么情況下要使用HttpEntity?
假如我們遠(yuǎn)程調(diào)用的接口是有token認(rèn)證的,我們請(qǐng)求的時(shí)候就需要在header請(qǐng)求頭當(dāng)中添加token,這時(shí)候我們就需要通過(guò)HttpEntity對(duì)象來(lái)攜帶請(qǐng)求頭。
4.1、postForEntity
示例一:下面接口是JSON傳參,然后還獲取token了,把它當(dāng)做要遠(yuǎn)程調(diào)用的接口:
@PostMapping("/postTest2") public User test2(HttpServletRequest request, @RequestBody User user1) { System.out.println("接受的參數(shù):" + user1); // 正常情況我們是需要通過(guò)攔截器來(lái)攔截request獲取請(qǐng)求頭當(dāng)中的token來(lái)進(jìn)行認(rèn)證,這塊只是演示 System.out.println("接受的請(qǐng)求頭:" + request.getHeader("token")); User user = new User(); user.setId(1); user.setName("張三"); user.setSex("男性"); return user; }
那么遠(yuǎn)程調(diào)用怎么調(diào)用呢?下面示例供參考!
示例使用的是如下圖API,request 可以是一個(gè)普通對(duì)象,也可以是HttpEntity。
@GetMapping("/hello2") public User hello5() { String url = "http://127.0.0.1:8006/postTest2"; // 請(qǐng)求示例一:攜帶token請(qǐng)求頭 // 請(qǐng)求體 User user = new User(); user.setId(1); user.setName("李四"); user.setSex("女性"); // 請(qǐng)求頭(這里有一點(diǎn)需要注意,HttpHeaders和HttpEntity都是引入的spring的包) HttpHeaders headers = new HttpHeaders(); headers.add("token", "WDWADAWDWADAWSAXZCXZCXZEDF"); // 請(qǐng)求 HttpEntity<User> requst = new HttpEntity<>(user, headers); ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, requst, User.class); System.out.println("響應(yīng)的結(jié)果:" + responseEntity.getBody()); // 請(qǐng)求示例二:不攜帶請(qǐng)求頭,直接傳普通對(duì)象,這里直接傳的user,map也是可以的,他都可以轉(zhuǎn)換成json,切記不能使用LinkedMultiValueMap,使用他不會(huì)轉(zhuǎn)換成json ResponseEntity<User> responseEntity1 = restTemplate.postForEntity(url, user, User.class); System.out.println("響應(yīng)的結(jié)果:" + responseEntity1.getBody()); return responseEntity1.getBody(); }
調(diào)用結(jié)果:
示例二:下面接口是key/value傳參,然后還獲取token了,把它當(dāng)做要遠(yuǎn)程調(diào)用的接口:
@PostMapping("/postTest1") public String test1(HttpServletRequest request, String name) { System.out.println("接受的參數(shù):" + name); System.out.println("接受的請(qǐng)求頭:" + request.getHeader("token")); return "Hello " + name + " !"; }
那么遠(yuǎn)程調(diào)用怎么調(diào)用呢?下面示例供參考!
下面每個(gè)示例都使用了request參數(shù),正常情況下,假如第三方接口沒(méi)有token認(rèn)證,我們是不需要使用request參數(shù)的,直接傳null即可。
@GetMapping("/hello1") public void hello1() throws UnsupportedEncodingException { // 請(qǐng)求頭(這里有一點(diǎn)需要注意,HttpHeaders和HttpEntity都是引入的spring的包) HttpHeaders headers = new HttpHeaders(); headers.add("token", "WDWADAWDWADAWSAXZCXZCXZEDF"); HttpEntity<Map> requst = new HttpEntity<>(null, headers); // 調(diào)用方式一,通過(guò)map傳參 String url1 = "http://127.0.0.1:8006/postTest1?name={name}"; Map<String, String> param = new HashMap<>(16); param.put("name", "李四"); ResponseEntity<String> responseEntity1 = restTemplate.postForEntity(url1, requst, String.class, param); System.out.println(responseEntity1.getBody()); // 調(diào)用方式二,通過(guò)占位符和可變長(zhǎng)參數(shù)傳參 String url2 = "http://127.0.0.1:8006/postTest1?name={1}"; ResponseEntity<String> responseEntity2 = restTemplate.postForEntity(url2, requst, String.class, "牛牛"); System.out.println(responseEntity2.getBody()); // 調(diào)用方式三,正常這塊其實(shí)都不需要URI對(duì)象了,根據(jù)地址直接訪問(wèn)就可以。 String url3 = "http://127.0.0.1:8006/postTest1?name=" + URLEncoder.encode("ccc", "UTF-8"); URI uri = URI.create(url3); ResponseEntity<String> responseEntity3 = restTemplate.postForEntity(uri, requst, String.class); System.out.println(responseEntity3.getBody()); // 調(diào)用方式四:這種方式是沒(méi)有使用請(qǐng)求頭的,使用LinkedMultiValueMap,切記不能使用hashmap和java實(shí)體類,不然就轉(zhuǎn)換成json了,將LinkedMultiValueMap放到HttpEntity的body當(dāng)中其實(shí)也是可以的,然后就可以攜帶token了。 String url4 = "http://127.0.0.1:8006/postTest1"; MultiValueMap map = new LinkedMultiValueMap(); map.add("name", "薩瓦迪卡"); ResponseEntity<String> responseEntity4 = restTemplate.postForEntity(url4, map, String.class); System.out.println(responseEntity4.getBody()); }
調(diào)用示例:
前三種方式都是攜帶了header,最后一種沒(méi)有帶。
4.2、postForObject
postForObject 和 postForEntity 基本一致,就是返回類型不同而已,這里不再贅述。
4.3、postForLocation
postForLocation 方法的返回值是一個(gè) Uri 對(duì)象,因?yàn)?POST 請(qǐng)求一般用來(lái)添加數(shù)據(jù),有的時(shí)候需要將剛剛添加成功的數(shù)據(jù)的 URL 返回來(lái),此時(shí)就可以使用這個(gè)方法,一個(gè)常見(jiàn)的使用場(chǎng)景如用戶注冊(cè)功能,用戶注冊(cè)成功之后,可能就自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面了,此時(shí)就可以使用該方法。例如在 provider 中提供一個(gè)用戶注冊(cè)接口,再提供一個(gè)用戶登錄接口,如下:
@RequestMapping("/register") public String register(User user) throws UnsupportedEncodingException { return "redirect:/loginPage?username=" + URLEncoder.encode(user.getUsername(),"UTF-8") + "&address=" + URLEncoder.encode(user.getAddress(),"UTF-8"); } @GetMapping("/loginPage") @ResponseBody public String loginPage(User user) { return "loginPage:" + user.getUsername() + ":" + user.getAddress(); }
這里一個(gè)注冊(cè)接口,一個(gè)是登錄頁(yè)面,不過(guò)這里的登錄頁(yè)面我就簡(jiǎn)單用一個(gè)字符串代替了。然后在 consumer 中來(lái)調(diào)用注冊(cè)接口,如下:
@GetMapping("/hello8") public String hello8() { List<ServiceInstance> list = discoveryClient.getInstances("provider"); ServiceInstance instance = list.get(0); String host = instance.getHost(); int port = instance.getPort(); String url = "http://" + host + ":" + port + "/register"; MultiValueMap map = new LinkedMultiValueMap(); map.add("username", "牧碼小子"); map.add("address", "深圳"); URI uri = restTemplate.postForLocation(url, map); String s = restTemplate.getForObject(uri, String.class); return s; }
注意: postForLocation 方法返回的 Uri 實(shí)際上是指響應(yīng)頭的 Location 字段,所以,provider 中 register 接口的響應(yīng)頭必須要有 Location 字段(即請(qǐng)求的接口實(shí)際上是一個(gè)重定向的接口),否則 postForLocation 方法的返回值為null,初學(xué)者很容易犯這個(gè)錯(cuò)誤。
五、PUT請(qǐng)求
PUT 請(qǐng)求和POST請(qǐng)求傳參基本上一模一樣,他也有request參數(shù),唯一區(qū)別就是PUT請(qǐng)求是沒(méi)有返回值的,PUT 請(qǐng)求本身方法也比較少,只有三個(gè),如下:
六、DELETE請(qǐng)求
DELETE 請(qǐng)求和GET請(qǐng)求傳參基本上一模一樣,同樣都沒(méi)有request參數(shù),唯一區(qū)別就是DELETE 請(qǐng)求是沒(méi)有返回值的,DELETE 請(qǐng)求本身方法也比較少,只有三個(gè),如下:
七、LinkedMultiValueMap源碼分析
1、項(xiàng)目中在使用RestTemplate進(jìn)行http請(qǐng)求時(shí),會(huì)用到LinkedMultiValueMap,現(xiàn)在簡(jiǎn)單分析一下LinkedMultiValueMap的數(shù)據(jù)結(jié)構(gòu)。
2、打開(kāi)LinkedMultiValueMap的源碼,可以看到里面封裝的是一個(gè)Map,再看構(gòu)造方法,最終創(chuàng)建的是一個(gè)LinkedHashMap。這個(gè)Map的value有點(diǎn)特別,他不能放任何值,必須是一個(gè)List。
其實(shí)LinkedMultiValueMap可以看作是一個(gè)表單,在表單中,一個(gè)name可以對(duì)應(yīng)很多值,比如根據(jù)id批量刪除,一個(gè)key需要對(duì)應(yīng)很多需要?jiǎng)h除的id
3、add、put、set
public static void main(String[] args) { MultiValueMap params = new LinkedMultiValueMap(); params.add("username", "張三"); params.add("username", "李四"); System.out.println(params); params.put("password", Arrays.asList("1234")); System.out.println(params); params.set("username", "王五"); System.out.println(params); }
運(yùn)行結(jié)果:
4、通過(guò)源碼分析
add是把值放到list中,若map中沒(méi)有key對(duì)應(yīng)的list,先new一個(gè)LinkedList。
put直接接收l(shuí)ist
set不管原來(lái)怎么樣,都會(huì)重新new一個(gè)LinkedList,再把值放進(jìn)去
5、什么時(shí)候使用LinkedMultiValueMap,答案就是模擬普通的表單提交時(shí)。
HttpEntity的body里面使用LinkedMultiValueMap,那么就是key/value傳參,如果是其他的bean類型,就會(huì)格式化成json。
八、通用方法 exchange
為什么說(shuō)它通用呢?因?yàn)檫@個(gè)方法需要你在調(diào)用的時(shí)候去指定請(qǐng)求類型,即它既能做 GET 請(qǐng)求,也能做 POST 請(qǐng)求,也能做其它各種類型的請(qǐng)求。如果開(kāi)發(fā)者需要對(duì)請(qǐng)求進(jìn)行封裝,使用它再合適不過(guò)了。這個(gè)方法重載的方法非常多,其中參數(shù)也多了很多個(gè),如下:
HttpMethod method
:請(qǐng)求的方法(GET、POST、PUT等)HttpEntity<?> requestEntity
:HttpEntity對(duì)象,封裝了請(qǐng)求頭和請(qǐng)求體,在上面post的時(shí)候應(yīng)該掌握的差不多了。
8.1、用法示例
使用如下API演示兩個(gè)示例:
- post的key/value傳參
- post的json傳參
示例一:post的key/value傳參
@PostMapping("/exchangeTest1") public String test1(HttpServletRequest request, String name) { System.out.println("接受的參數(shù):" + name); System.out.println("接受的請(qǐng)求頭:" + request.getHeader("token")); return "Hello " + name + " !"; }
調(diào)用示例:
@GetMapping("/exchange1") public void hello1() { String url = "http://127.0.0.1:8006/exchangeTest1"; // 請(qǐng)求體,一定要用LinkedMultiValueMap,用hashmap或者javabean會(huì)直接轉(zhuǎn)換成json的 MultiValueMap map = new LinkedMultiValueMap(); map.add("name", "薩瓦迪卡"); // 請(qǐng)求頭(這里有一點(diǎn)需要注意,HttpHeaders和HttpEntity都是引入的spring的包) HttpHeaders headers = new HttpHeaders(); headers.add("token", "WDWADAWDWADAWSAXZCXZCXZEDF"); // 請(qǐng)求 HttpEntity<Map> requst = new HttpEntity<>(map, headers); ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, requst, String.class); System.out.println("響應(yīng)的結(jié)果:" + exchange.getBody()); }
調(diào)用結(jié)果:
示例二:post的json傳參
@PostMapping("/postTest2") public User test2(HttpServletRequest request, @RequestBody User user1) { System.out.println("接受的參數(shù):" + user1); System.out.println("接受的請(qǐng)求頭:" + request.getHeader("token")); User user = new User(); user.setId(1); user.setName("張三"); user.setSex("男性"); return user; }
調(diào)用示例:
@GetMapping("/exchange2") public void hello2() { String url = "http://127.0.0.1:8006/postTest2"; // 請(qǐng)求體,不要用LinkedMultiValueMap,否則會(huì)變成key/value傳參 User user = new User(); user.setId(1); user.setName("李四"); user.setSex("女性"); // 請(qǐng)求頭(這里有一點(diǎn)需要注意,HttpHeaders和HttpEntity都是引入的spring的包) HttpHeaders headers = new HttpHeaders(); headers.add("token", "WDWADAWDWADAWSAXZCXZCXZEDF"); // 請(qǐng)求 HttpEntity<User> requst = new HttpEntity<>(user, headers); ResponseEntity<User> exchange = restTemplate.exchange(url, HttpMethod.POST, requst, User.class); System.out.println("響應(yīng)的結(jié)果:" + exchange.getBody()); }
調(diào)用結(jié)果:
8.2、封裝通用util
可以寫一個(gè)通用的工具類,根據(jù)傳入的參數(shù)來(lái)確定請(qǐng)求的路徑和內(nèi)容。工具類肯定是想通過(guò)靜態(tài)方式來(lái)直接調(diào)用,而不是通過(guò)new Util()的方式來(lái)使用。但是靜態(tài)變量/類變量不是對(duì)象的屬性,而是一個(gè)類的屬性,spring則是基于對(duì)象層面上的依賴注入。所以我們不能@Autowired或者@resource一個(gè)靜態(tài)變量。但是靜態(tài)方法又不能調(diào)用非靜態(tài)的屬性,那么我們?cè)撛趺崔k呢?
@PostContruct是Java自帶的注解,在方法上加該注解會(huì)在項(xiàng)目啟動(dòng)的時(shí)候執(zhí)行該方法,也可以理解為在spring容器初始化的時(shí)候執(zhí)行該方法。
@Component public class HttpUtil { private static Logger logger = LoggerFactory.getLogger(HttpUtil.class); @Resource private RestTemplate restTemplate; private static HttpUtil httpUtil; @PostConstruct public void init(){ httpUtil = this; httpUtil.restTemplate = this.restTemplate; } public static <T> String httpRequest(String url, HttpMethod method, HttpEntity<T> entity,Class<T> responseType){ try { //發(fā)起一個(gè)POST請(qǐng)求 ResponseEntity<String> result = httpUtil.restTemplate.exchange(url, method, entity, responseType); return result.getBody(); } catch (Exception e) { logger.error("請(qǐng)求失?。?" + e.getMessage()); } return null; } }
這個(gè)時(shí)候我們就需要維護(hù)一個(gè)工具類的靜態(tài)實(shí)例,初始化的時(shí)候把restTemplate傳進(jìn)來(lái),這樣就可以直接調(diào)用HttpUtil.httpRequest()方法。
到此這篇關(guān)于詳解RestTemplate 用法的文章就介紹到這了,更多相關(guān)RestTemplate 用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)非阻塞式服務(wù)器的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的非阻塞式服務(wù)器,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05SpringBoot實(shí)戰(zhàn):Spring如何找到對(duì)應(yīng)轉(zhuǎn)換器優(yōu)雅使用枚舉參數(shù)
這篇文章主要介紹了SpringBoot實(shí)戰(zhàn)中Spring是如何找到對(duì)應(yīng)轉(zhuǎn)換器優(yōu)雅的使用枚舉參數(shù),文中附有詳細(xì)的實(shí)例代碼有需要的朋友可以參考下,希望可以有所幫助2021-08-08Spring動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源的實(shí)現(xiàn)方法,小編覺(jué)的挺不錯(cuò)的,現(xiàn)分享到腳本之家平臺(tái),需要的朋友可以參考下2018-01-01