在SpringCloud項目中,當我們需要遠程調用一個HTTP接口時,我們經常會用到RestTemplate這個類。這個類是Spring框架提供的一個工具類。它是一個同步的Rest API客戶端,提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
說明:
在項目架構中,我們基本都可以通過它來進行不同服務與服務之間的調用。在SpringCloud中我們依然可以使用HttpClient進行服務與服務調用,只不過如果采用HttpClient調用的話,會有一些弊端。例如:如果同一個服務有多個負載的話,采用HttpClient調用時,沒有辦法處理負載均衡的問題。還有另一個問題就是HttpClient只是提供了核心調用的方法並沒有對調用進行封裝,所以在使用上不太方便,需要自己對HttpClient進行簡單的封裝。
在SpringCloud中為了解決服務與服務調用的問題,於是提供了兩種方式來進行調用:RestTemplate和Feign。
一、RestTemplate常用方法
上面的方法我們大致可以分為三組:
- getForObject --- optionsForAllow分為一組,這類方法是常規的Rest API(GET、POST、DELETE等)方法調用;
- exchange:接收一個RequestEntity 參數,可以自己設置HTTP method, URL, headers和body。返回ResponseEntity。
- execute:通過callback 接口,可以對請求和返回做更加全面的自定義控制。
一般情況下,我們使用第一組和第二組方法就夠了。
二、RestTemplate簡單使用
1. RestTemplate的創建
使用RestTemplate前,必須先創建實例化。
@Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); return restTemplate; } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000); factory.setConnectTimeout(15000); //設置代理 //factory.setProxy(null); return factory; }
RestTemplate
默認使用
SimpleClientHttpRequestFactory
和
DefaultResponseErrorHandler
來分別處理
HTTP
的創建和錯誤,但也可以通過
setRequestFactory
和
setErrorHandler
來覆蓋。
通過上面代碼配置后,我們直接在代碼中注入RestTemplate就可以使用了。
2. 服務間的調用
服務端Controller:
@RestController @RequestMapping("/server") public class Controller { @GetMapping("/get") public Object get() { Map<String, String> map = new HashMap<String, String>(); map.put("code", "0"); map.put("msg", "success"); map.put("data", "吉林烏拉"); return map; } }
服務端配置文件:application.yml
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
spring:
application:
name: test-server
server:
port: 8082
客戶端Controller:
@RestController @RequestMapping("/client") public class Controller { @GetMapping("/get") public Object get() { Map<String, String> map = new HashMap<String, String>(); map.put("code", "0"); map.put("msg", "success"); map.put("data", "吉林烏拉"); return map; } }
客戶端配置文件application.yml:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
spring:
application:
name: test-client
server:
port: 8081
注意:需要在啟動類上添加@EnableEurekaClient注解,將兩個服務注冊到eureka中。
(1) 調用方式一
@RestController @RequestMapping("/client") public class Controller { @Autowired private RestTemplate template; @GetMapping("/get") public Object get() { String result = template.getForObject("http://127.0.0.1:8082/server/get", String.class); return result; } }
弊端:server端的接口地址直接寫死了,這樣當服務接口變更時,是需要更改客戶端代碼的。
(2) 調用方式二:
注冊中心這時可以派上用場了。因為注冊中心知道所有服務的地址,我們通過注冊中心先知道server端的接口地址,然后再調用。
@RestController @RequestMapping("/client") public class Controller { @Autowired private RestTemplate template; @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/get") public Object get() { ServiceInstance serviceInstance = loadBalancerClient.choose("test-server"); String url = String.format("http://%s:%s/server/get", serviceInstance.getHost(), serviceInstance.getPort()); String result = template.getForObject(url, String.class); return result; } }
SpringClourd中提供了LoadBalancerClient接口。通過這個接口我們可以通過用戶中心的Application的名字(即application.yml中的spring.application.name屬性值)來獲取該服務的地址和端口。
弊端:每次調用服務時都要先通過Application的名字來獲取ServiceInstance對象,然后才可以發起接口調用。實際上在SpringCloud中為我們提供了@LoadBalanced注解,只要將該注解添加到RestTemplate中的獲取的地方就可以了。
(3) 調用方式三
在RestTemplate實例化時添加@LoadBalanced注解
@Bean @LoadBalanced public RestTemplate initRestTemplate() { return new RestTemplate(); }
客戶端Controller:
@RestController @RequestMapping("/client") public class Controller { @Autowired private RestTemplate template; @GetMapping("/get") public Object get() { //注意,應用可能存在上下文配置(server.context-path屬性值), 如果有則需要加上, 假如上下文是web-test,則url為 //http://TEST-SERVER/web-test/server/get //String url = String.format("http://%s/server/get", "test-server"); String result = template.getForObject(url, String.class); return result; } }
三、RestTemplate請求參數傳遞
使用RestTemplate post請求的時候主要可以通過三種方式實現:
- 調用postForObject方法
- 使用postForEntity方法
- 調用exchange方法
postForObject和postForEntity方法的區別主要在於可以在postForEntity方法中設置header的屬性,當需要指定header的屬性值的時候,使用postForEntity方法。exchange方法和postForEntity類似,但是更靈活,exchange還可以調用get、put、delete請求。使用這三種方法調用post請求傳遞參數,Map不能定義為以下兩種類型(url使用占位符進行參數傳遞時除外):
Map<String, Object> paramMap = new HashMap<String, Object>(); Map<String, Object> paramMap = new LinkedHashMap<String, Object>();
前人經過測試,發現以上兩種類型不能被后台接收到,當把Map類型換成LinkedMultiValueMap后,參數成功傳遞到后台:
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
正確傳遞參數如下:
RestTemplate template = new RestTemplate(); String url = "http://192.168.2.40:8081/channel/channelHourData/getHourNewUserData"; // 封裝參數,千萬不要替換為Map與HashMap,否則參數無法傳遞 MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>(); paramMap.add("dt", "20180416"); // 1、使用postForObject請求接口
//第一參數表示要調用的服務的地址 第二個參數表示請求參數 第三個參數表示返回的消息體的數據類型
String result = template.postForObject(url, paramMap, String.class); System.out.println("result1==================" + result); // 2、使用postForEntity請求接口 HttpHeaders headers = new HttpHeaders(); HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers); ResponseEntity<String> response2 = template.postForEntity(url, httpEntity, String.class); System.out.println("result2====================" + response2.getBody()); // 3、使用exchange請求接口 ResponseEntity<String> response3 = template.exchange(url, HttpMethod.POST, httpEntity, String.class); System.out.println("result3====================" + response3.getBody());
get方式傳參說明:如果是get請求,又想要把參數封裝到map里面進行傳遞的話,Map需要使用HashMap,且url需要使用占位符
RestTemplate restTemplate2 = new RestTemplate(); String url = "http://127.0.0.1:8081/interact/getData?dt={dt}&ht={ht}"; // 封裝參數,這里是HashMap Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put("dt", "20181116"); paramMap.put("ht", "10"); //1、使用getForObject請求接口 String result1 = template.getForObject(url, String.class, paramMap); System.out.println("result1====================" + result1); //2、使用exchange請求接口 HttpHeaders headers = new HttpHeaders(); headers.set("id", "lidy"); HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(null,headers); ResponseEntity<String> response2 = template.exchange(url, HttpMethod.GET, httpEntity, String.class,paramMap); System.out.println("result2====================" + response2.getBody());
delete請求, 請求方式使用 HttpMethod.DELETE
StringBuffer url = new StringBuffer(baseUrl).append("/user/delete/{id}"); Map<String, Object> paramMap = new HashMap<>(); paramMap.put("id", id); ResponseEntity<String > response = restTemplate.exchange(url.toString(), HttpMethod.DELETE, null, String .class, paramMap); String result = response.getBody();
put請求, 請求方式使用 HttpMethod.PUT
StringBuffer url = new StringBuffer(baseUrl) .append("/user/edit?tmp=1") .append("&id={id}") .append("&userName={userName}") .append("&nickName={nickName}") .append("&realName={realName}") .append("&sex={sex}") .append("&birthday={birthday}"); Map<String, Object> paramMap = new HashMap<>(); paramMap.put("userId", userInfoDTO.getId()); paramMap.put("userName", userInfoDTO.getUserName()); paramMap.put("nickName", userInfoDTO.getNickName()); paramMap.put("realName", userInfoDTO.getRealName()); paramMap.put("sex", userInfoDTO.getSex()); paramMap.put("birthday", userInfoDTO.getBirthday()); ResponseEntity<String > response = restTemplate.exchange(url.toString(), HttpMethod.PUT, null, String .class, paramMap); String result = response.getBody();