在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();