SpringCloud之RestTemplate的用法精讲

冷不防 2021-11-23 03:06 388阅读 0赞

什么是RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。接下来我们就来看看这些操作方法的使用。

如何使用

首先我们创建一个普通的maven工程、然后分别创建eureka、provider、consumer三个SpringBoot子工程项目、然后分别在application.properties或者application.yml进行配置、配置参考可以参考上一个项目
服务中心与消费

创建好项目和我们可以看一下GET请求,在RestTemplate中GET请求有这几个方法
在这里插入图片描述
这个请求中有两类方法,分别是getForEntity和getForObject,每一个类都有三个重载方法

getForEntity

我们要了解RestTemplate 发送的是Http请求,那么响应数据一定是有响应头的,如果需要获取响应头的信息,那么就可以使用getForEntity来发送Http请求,此时返回的就是一个ResponseEntity的实例,这个实例包含看响应头和响应的数据,例如下面的这个接口是来自provider 中的:

  1. @RestController
  2. public class SayHelloController {
  3. @GetMapping("/hello")
  4. public String sayHello(String name) {
  5. return "hello " + name + " !";
  6. }
  7. }

我们需要在consumer 启动类中注入@RestTemplate

  1. @SpringBootApplication
  2. public class ConsumerApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(ConsumerApplication.class, args);
  5. }
  6. @Bean
  7. RestTemplate restTemplate() {
  8. return new RestTemplate();
  9. }
  10. }

然后在consumer中添加一个SayUserHelloController ,并且提供一个/hello 来消费我们provider提供的服务

  1. @RestController
  2. public class SayUseHelloController {
  3. @Autowired
  4. RestTemplate restTemplate;
  5. @Autowired
  6. DiscoveryClient discoveryClient;
  7. @GetMapping("/hello")
  8. public String hello(String name) {
  9. //拿到服务提供商
  10. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  11. //拿到第一个实例
  12. ServiceInstance instance = list.get(0);
  13. //得到主机号
  14. String host = instance.getHost();
  15. //得到端口号
  16. int port = instance.getPort();
  17. //拼接完整的请求url
  18. String url = "http://" + host + ":" + port + "/hello?name={1}";
  19. //restTemple 实际返回的是一个ResponseEntity 的实例
  20. ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, name);
  21. StrBuilder sb = new StrBuilder();
  22. //拿到状态码
  23. HttpStatus statusCode = responseEntity.getStatusCode();
  24. //拿到响应体
  25. String body= responseEntity.getBody();
  26. sb.append("statusCode:")
  27. .append(statusCode)
  28. .append("<br/>")
  29. .append("body:")
  30. .append(body)
  31. .append("<br/>");
  32. //拿到响应头header
  33. HttpHeaders headers = responseEntity.getHeaders();
  34. Set<String> keySet = headers.keySet();
  35. for (String s : keySet) {
  36. sb.append(s)
  37. .append(":")
  38. .append(headers.get(s))
  39. .append("</br>");
  40. }
  41. return sb.toString();
  42. }
  43. }

关于 DiscoveryClient 那一段本文先不做讨论,主要来看 getForEntity 方法。第一个参数是 url ,url 中有一个占位符 {1} ,如果有多个占位符分别用 {2} 、 {3} … 去表示,第二个参数是接口返回的数据类型,最后是一个可变长度的参数,用来给占位符填值。在返回的 ResponseEntity 中,可以获取响应头中的信息,其中 getStatusCode 方法用来获取响应状态码, getBody 方法用来获取响应数据, getHeaders 方法用来获取响应头,在浏览器中访问该接口,结果如下:
在这里插入图片描述

除了上面这一种方法外,还有两种方法也就是getForEntity另外两个重载方法,使用map

  1. Map<String,Object> map = new HashMap<>();
  2. map.put("name",name);
  3. ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, map);

还有一种就是使用Uri对象

  1. String url = "http://" + host + ":" + port + "/hello?name=" + URLEncoder.encode(name, "UTF-8");
  2. URI uri = URI.create(url);
  3. ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri,String.class);

注意这里使用URL如果是带了中文字符串那么需要使用UTF-8

getForObject

getForObject 方法和 getForEntity 方法类似,getForObject 方法也有三个重载的方法,参数和 getForEntity 一样,因此这里我就不重复介绍参数了,这里主要说下 getForObject 和 getForEntity 的差异,这两个的差异主要体现在返回值的差异上, getForObject 的返回值就是服务提供者返回的数据,使用 getForObject 无法获取到响应头。例如,还是上面的请求,利用 getForObject 来发送 HTTP 请求,结果如下:

  1. String url = "http://" + host + ":" + port + "/hello?name=" + URLEncoder.encode(name, "UTF-8");
  2. URI uri = URI.create(url);
  3. String s = restTemplate.getForObject(uri, String.class);

注意,这里返回的 s 就是 provider 的返回值,如果开发者只关心 provider 的返回值,并不关系 HTTP 请求的响应头,那么可以使用该方法。

POST请求
和 GET 请求相比,RestTemplate 中的 POST 请求多了一个类型的方法,如下:
在这里插入图片描述
可以看到,post 请求的方法类型除了 postForEntity 和 postForObject 之外,还有一个 postForLocation。这里的方法类型虽然有三种,但是这三种方法重载的参数基本是一样的,因此这里我还是以 postForEntity 方法为例,来剖析三个重载方法的用法,最后再重点说下 postForLocation 方法。

postForEntity
在 POST 请求中,参数的传递可以是 key/value 的形式,也可以是 JSON 数据,分别来看:
传递 key/value 形式的参数
首先在 provider 的 SayHelloController 类中再添加一个 POST 请求的接口,如下:

  1. @PostMapping("/hello2")
  2. public String sayHello2(String name) {
  3. return "Hello " + name + " !";
  4. }

然后在consumer 中去调用provider 中提供的/hello2

  1. @GetMapping("/hello5")
  2. public String hello5(String name) {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url = "http://" + host + ":" + port + "/hello2";
  8. MultiValueMap map = new LinkedMultiValueMap();
  9. map.add("name", name);
  10. ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, map, String.class);
  11. return responseEntity.getBody();
  12. }

在这里, postForEntity 方法第一个参数是请求地址,第二个参数 map 对象中存放着请求参数 key/value,第三个参数则是返回的数据类型。当然这里的第一个参数 url 地址也可以换成一个 Uri 对象,效果是一样的。这种方式传递的参数是以 key/value 形式传递的,在 post 请求中,也可以按照 get 请求的方式去传递 key/value 形式的参数,传递方式和 get 请求的传参方式基本一致,例如下面这样:

  1. @GetMapping("/hello6")
  2. public String hello6(String name) {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url = "http://" + host + ":" + port + "/hello2?name={1}";
  8. ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, null, String.class,name);
  9. return responseEntity.getBody();
  10. }

此时第二个参数可以直接传一个 null。

如何传递JSON数据

上面介绍的是 post 请求传递 key/value 形式的参数,post 请求也可以直接传递 json 数据,在 post 请求中,可以自动将一个对象转换成 json 进行传输,数据到达 provider 之后,再被转换为一个对象。具体操作步骤如下:

首先在 RestTemplate 项目中创建一个新的maven项目,叫做 commons ,然后在 commons 中创建一个 User 对象,如下:

  1. @Data
  2. public class UserDTO {
  3. private String nickname;
  4. private String address;
  5. }

然后分别在 provider 和 consumer 的 pom.xml 文件中添加对 commons 模块的依赖,如下:

  1. <dependency>
  2. <groupId>cn.com.scitc</groupId>
  3. <artifactId>commons</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. </dependency>

这样,在 provider 和 consumer 中就都能使用 UserDTO 对象了。首先在 provider 中创建一个添加用户的接口,如下:

  1. @Controller
  2. @ResponseBody
  3. public class UserController {
  4. @PostMapping("/user")
  5. public ResponseEntity<Object> userDTO(@RequestBody UserDTO userDTO) {
  6. return new ResponseEntity<Object>(userDTO, HttpStatus.OK);
  7. }
  8. }

这里的接口很简单,只需要将用户传来的 User 对象再原封不动地返回去就行了,然后在 consumer 中添加一个接口来测试这个接口,如下:

  1. @PostMapping("/hello7")
  2. public ResponseEntity<UserDTO> hello7(@RequestBody UserDTO userDTO) {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url = "http://" + host + ":" + port + "/user";
  8. UserDTO u = new UserDTO();
  9. u.setNickname(userDTO.getNickname());
  10. u.setAddress(userDTO.getAddress());
  11. ResponseEntity<UserDTO> responseEntity = restTemplate.postForEntity(url, u, UserDTO.class);
  12. return new ResponseEntity<UserDTO>(responseEntity.getBody(),HttpStatus.OK);
  13. }

我们访问localhost:4002/hello7并且传入如下参数

  1. {
  2. "nickname": "技术无止境",
  3. "address": "四川成都"
  4. }

服务返回结果如下:
在这里插入图片描述
看到这段代码有人要问了,这不和前面的一样吗?是的,唯一的区别就是第二个参数的类型不同,这个参数如果是一个 MultiValueMap 的实例,则以 key/value 的形式发送,如果是一个普通对象,则会被转成 json 发送。

postForObject

postForObject 和 postForEntity 基本一致,就是返回类型不同而已,这里不再赘述。

postForLocation

postForLocation 方法的返回值是一个 Uri 对象,因为 POST 请求一般用来添加数据,有的时候需要将刚刚添加成功的数据的 URL 返回来,此时就可以使用这个方法,一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。例如在 provider 中提供一个用户注册接口,再提供一个用户登录接口,如下:

  1. @RequestMapping("/register")
  2. public String register(UserDTO userDTO) throws UnsupportedEncodingException {
  3. return "redirect:/loginPage?username=" + URLEncoder.encode(userDTO.getNickname(),"UTF-8") + "&address=" + URLEncoder.encode(userDTO.getAddress(),"UTF-8");
  4. }
  5. @GetMapping("/loginPage")
  6. @ResponseBody
  7. public String loginPage(UserDTO userDTO) {
  8. return "loginPage:" + userDTO.getNickname() + ":" + userDTO.getAddress();
  9. }

注意:postForLocation 方法返回的 Uri 实际上是指响应头的 Location 字段,所以,provider 中 register 接口的响应头必须要有 Location 字段(即请求的接口实际上是一个重定向的接口),否则 postForLocation 方法的返回值为null,初学者很容易犯这个错误,如果这里出错,大家可以参考下我的源代码。

PUT请求

只要将 GET 请求和 POST 请求搞定了,接下来 PUT 请求就会容易很多了,PUT 请求本身方法也比较少,只有三个,如下:

在这里插入图片描述
这三个重载的方法其参数其实和 POST 是一样的,可以用 key/value 的形式传参,也可以用 JSON 的形式传参,无论哪种方式,都是没有返回值的,我这里就举两个例子给大家参考下:

首先在 provider 的 UserController 中添加如下两个数据更新接口:

  1. @PutMapping("/user/name")
  2. @ResponseBody
  3. public void updateUserByUsername(User User) {
  4. System.out.println(User);
  5. }
  6. @PutMapping("/user/address")
  7. @ResponseBody
  8. public void updateUserByAddress(@RequestBody User User) {
  9. System.out.println(User);
  10. }

这里两个接口,一个接收 key/value 形式的参数,另一个接收 JSON 参数。因为这里没有返回值,我直接把数据打印出来就行了。接下来在 consumer 中添加接口调用这里的服务,如下:

  1. @GetMapping("/hello9")
  2. public void hello9() {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url1 = "http://" + host + ":" + port + "/user/name";
  8. String url2 = "http://" + host + ":" + port + "/user/address";
  9. MultiValueMap map = new LinkedMultiValueMap();
  10. map.add("nickname","技术无止境");
  11. map.add("address", "四川成都");
  12. restTemplate.put(url1, map);
  13. UserDTO u = new UserDTO();
  14. u.setNickname("独孤剑圣");
  15. u.setAddress("北上广");
  16. restTemplate.put(url2, u);
  17. }

我们访问hello9接口返回结果如下:
在这里插入图片描述
DELETE请求
和 PUT 请求一样,DELETE 请求也是比较简单的,只有三个方法,如下:
在这里插入图片描述

不同于 POST 和 PUT ,DELETE 请求的参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递,当然,这里也是没有返回值的。我也举两个例子:
首先在 provider 的 UserController 中添加两个接口,如下:

  1. @DeleteMapping("/user/{id}")
  2. @ResponseBody
  3. public void deleteUserById(@PathVariable Integer id) {
  4. System.out.println(id);
  5. }
  6. @DeleteMapping("/user/")
  7. @ResponseBody
  8. public void deleteUserByUsername(String username) {
  9. System.out.println(username);
  10. }

两个接口,一个的参数在路径中,另一个的参数以 key/value 的形式传递,然后在 consumer 中,添加一个方法调用这两个接口,如下:

  1. @GetMapping("/hello10")
  2. public void hello10() {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url1 = "http://" + host + ":" + port + "/user/{1}";
  8. String url2 = "http://" + host + ":" + port + "/user/?nickname={nickname}";
  9. Map<String,String> map = new HashMap<>();
  10. map.put("nickname","技术无止境");
  11. restTemplate.delete(url1, 99);
  12. restTemplate.delete(url2, map);
  13. }

我们请求hello10返回结果如下:
在这里插入图片描述
99就是删除的id,技术无止境就是我们的nickname

如何设置请求头

有的时候我们会有一些特殊的需求,例如模拟 cookie ,此时就需要我们自定义请求头了。自定义请求头可以通过拦截器的方式来实现(下篇文章我们会详细的说这个拦截器)。定义拦截器、自动修改请求数据、一些身份认证信息等,都可以在拦截器中来统一处理。具体操作步骤如下:

首先在 provider 中定义一个接口,在接口中获取客户端传来的 cookie 数据,如下:

  1. @GetMapping("diyheader")
  2. public String diyHeader(HttpServletRequest request) {
  3. return request.getHeader("cookie");
  4. }

这里简单处理,将客户端传来的 cookie 拿出来后再返回给客户端,然后在 consumer 中添加如下接口来测试:

  1. @GetMapping("/hello11")
  2. public void hello11() {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url = "http://" + host + ":" + port + "/diyheader";
  8. restTemplate.setInterceptors(Collections.singletonList(new ClientHttpRequestInterceptor() {
  9. @Override
  10. public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException, IOException {
  11. HttpHeaders headers = request.getHeaders();
  12. headers.add("cookie","i love java");
  13. return execution.execute(request,body);
  14. }
  15. }));
  16. String s = restTemplate.getForObject(url, String.class);
  17. System.out.println(s);
  18. }

这里通过调用 RestTemplate 的 setInterceptors 方法来给它设置拦截器,拦截器也可以有多个,我这里只有一个。在拦截器中,将请求拿出来,给它设置 cookie ,然后调用 execute 方法让请求继续执行。此时,在 /diyheader 接口中就获取到cookie了。
访问hello11 执行结果:
在这里插入图片描述

通用方法 exchange
在 RestTemplate 中还有一个通用的方法 exchange。为什么说它通用呢?因为这个方法需要你在调用的时候去指定请求类型,即它既能做 GET 请求,也能做 POST 请求,也能做其它各种类型的请求。如果开发者需要对请求进行封装,使用它再合适不过了,举个简单例子:

  1. @GetMapping("/hello12")
  2. public void hello12() {
  3. List<ServiceInstance> list = discoveryClient.getInstances("provider");
  4. ServiceInstance instance = list.get(0);
  5. String host = instance.getHost();
  6. int port = instance.getPort();
  7. String url = "http://" + host + ":" + port + "/diyheader";
  8. HttpHeaders headers = new HttpHeaders();
  9. headers.add("cookie","i love gir");
  10. HttpEntity<MultiValueMap<String,String>> request = new HttpEntity<>(null,headers);
  11. ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, request, String.class);
  12. System.out.println(responseEntity.getBody());
  13. }

这里的参数和前面的也都差不多,注意就是多了一个请求类型的参数,然后创建一个 HttpEntity 作为参数来传递。 HttpEntity 在创建时候需要传递两个参数,第一个上文给了一个 null ,这个参数实际上就相当于 POST/PUT 请求中的第二个参数,有需要可以自行定义。HttpEntity 创建时的第二个参数就是请求头了,也就是说,如果使用 exchange 来发送请求,可以直接定义请求头,而不需要使用拦截器。

访问hello12执行结果:
在这里插入图片描述

总结

本文主要向大家介绍了 RestTemplate 这样一个 HTTP 请求工具类的常见用法,一些比较冷门的用法本文并未涉及,读者有兴趣可以自行查找资料学习。由于 Spring、SpringMVC、Spring Boot、Spring Cloud 这些家族成员一脉相承,因此在 SpringMVC 中支持良好的 RESTful 风格的接口在后续的各个组件中都继续支持,在微服务接口设计时,大部分接口也都满足 RESTful 风格,使用 RestTemplate 则可以非常方便地发送 RESTful 风格的请求,因此这个工具的使用是我们后面学习的基础,常见的用法一定要熟练掌握。

源码地址

github

发表评论

表情:
评论列表 (有 0 条评论,388人围观)

还没有评论,来说两句吧...

相关阅读

    相关 分布式redis复习

    引言 为什么写这篇文章? 博主的[《分布式之消息队列复习精讲》][Link 1]得到了大家的好评,内心诚惶诚恐,想着再出一篇关于复习精讲的文章。但是还是要说明一下,

    相关 SpringCloudResilience4J用法

    在微服务中,经常会出现一些故障,而一些故障会直接或者间接的拖垮其它的服务,造成服务器雪崩,系统就会死掉。 什么是服务雪崩?我们可以通过下面一张图来看: ![在这里插入图片