Springboot 系列 (18) - 在 Spring Boot 项目里使用 RestTemplate 访问 REST 服务


RestTemplate 是从 Spring 3.0 开始支持的一个 HTTP 请求工具,它提供了常见的 REST 请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。

RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

RestTemplate 的方法列表如下:
方法 描述
getForObject 使用 GET 请求获取返回数据。
getForEntity 使用 GET 请求获取 ResponseEntity(包含状态、标题和返回数据)。
postForObject 使用 POST 请求创建新资源,并从响应中返回数据。
postForEntity 使用 POST 请求创建新资源,并从响应中返回数据。
postForLocation 使用 POST 请求创建新资源,并从响应中返回 Location 信息。
patchForObject 使用 PATCH 请求更新资源,并从响应中返回数据。请注意,JDK HttpURLConnection 不支持该补丁,Apache HttpComponents 和其他组件支持 PATCH。
headForHeaders 使用 HEAD 请求获取一个资源的 header 信息。
put 使用 PUT 请求创建或更新一个资源。
delete 使用 DELETE 请求删除指定 URI 处的资源。
optionsForAllow 使用 ALLOW 请求为资源检索允许的 HTTP 方法。
exchange 执行请求的通用方法,调用的时候指定请求类型。
execute 执行请求的最通用方法,通过回调接口完全控制请求准备和响应提取。


上述方法大致可以分为三组:

    (1) getForObject ~ optionsForAllow 分为一组,这类方法是常规的 Rest API(GET、POST、DELETE 等)方法调用;
    (2) exchange:接收一个 RequestEntity 参数,可以自己设置 HTTP method,URL,headers 和 body,返回 ResponseEntity;
    (3) execute:通过 callback 接口,可以对请求和返回做更加全面的自定义控制。

本文使用 RestTemplate 的几个常用的方法,创建一个实例访问 REST 服务。


1. 开发环境

    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample18
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample18;
        (2) Spring Boot Web 配置;
        
    具体操作请参考 “Springboot 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。

    SpringbootExample18 和 SpringbootExample02 相比,SpringbootExample18 不导入 Thymeleaf 依赖包,不配置 jQuery、Bootstrap、模版文件(templates/*.html)和国际化。


3. 配置 RestTemplate

    RestTemplate 默认配置是使用了 JDK 自带的 HttpURLConnection 作为底层 HTTP 客户端实现。可以通过 setRequestFactory 属性来切换使用不同的 HTTP 库,如 Apache HttpComponents、OkHttp、Netty 等。

    本文提供了三种配置方式:默认配置、HttpClient 配置、OkHttp 配置,一般情况下三选一就可以满足需要了。


    1) 默认配置

        默认配置的依赖已经包含在 spring-boot-starter-web 中,无需添加其它依赖。

        创建 src/main/java/com/example/config/RestTemplateConfig.java 文件

 1             package com.example.config;
 2 
 3             import org.springframework.context.annotation.Bean;
 4             import org.springframework.context.annotation.Configuration;
 5             import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6             import org.springframework.web.client.RestTemplate;
 7 
 8             @Configuration
 9             public class RestTemplateConfig {
10 
11                 @ConditionalOnMissingBean(RestTemplate.class)
12                 @Bean
13                 public RestTemplate restTemplate(){
14                     RestTemplate restTemplate = new RestTemplate();
15                     return restTemplate;
16                 }
17             }


    2) HttpClient 配置

        (1) 修改 pom.xml,导入 HttpClient 依赖包

 1             <project ... >
 2                 ...
 3                 <dependencies>
 4                     ...
 5 
 6                     <dependency>
 7                         <groupId>org.apache.httpcomponents</groupId>
 8                         <artifactId>httpclient</artifactId>
 9                         <version>4.5.13</version>
10                     </dependency>
11 
12                     ...
13                 </dependencies>
14 
15                 ...
16             </project>


            在IDE中项目列表 -> SpringbootExample18 -> 点击鼠标右键 -> Maven -> Reload Project

        (2) 创建 src/main/java/com/example/config/RestTemplateConfig.java 文件

 1             package com.example.config;
 2 
 3             import org.springframework.context.annotation.Bean;
 4             import org.springframework.context.annotation.Configuration;
 5             import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6             import org.springframework.http.client.ClientHttpRequestFactory;
 7             import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 8             import org.apache.http.client.config.RequestConfig;
 9             import org.apache.http.impl.client.CloseableHttpClient;
10             import org.apache.http.impl.client.HttpClientBuilder;
11             import org.springframework.web.client.RestTemplate;
12 
13             @Configuration
14             public class RestTemplateConfig {
15 
16                 @ConditionalOnMissingBean(RestTemplate.class)
17                 @Bean
18                 public RestTemplate restTemplate(){
19                     RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
20                     return restTemplate;
21                 }
22 
23                 private ClientHttpRequestFactory getClientHttpRequestFactory() {
24                     int timeout = 5000;
25                     RequestConfig config = RequestConfig.custom()
26                             .setConnectTimeout(timeout)
27                             .setConnectionRequestTimeout(timeout)
28                             .setSocketTimeout(timeout)
29                             .build();
30                     CloseableHttpClient client = HttpClientBuilder
31                             .create()
32                             .setDefaultRequestConfig(config)
33                             .build();
34                     return new HttpComponentsClientHttpRequestFactory(client);
35                 }
36 
37             }


    3) OkHttp 配置

        (1) 修改 pom.xml,导入 OkHttp 依赖包

 1             <project ... >
 2                 ...
 3                 <dependencies>
 4                     ...
 5 
 6                     <dependency>
 7                         <groupId>com.squareup.okhttp3</groupId>
 8                         <artifactId>okhttp</artifactId>
 9                         <version>4.3.1</version>
10                     </dependency>
11 
12                     ...
13                 </dependencies>
14 
15                 ...
16             </project>


            在IDE中项目列表 -> SpringbootExample18 -> 点击鼠标右键 -> Maven -> Reload Project

        (2) 创建 src/main/java/com/example/config/RestTemplateConfig.java 文件

 1             package com.example.config;
 2 
 3             import java.util.concurrent.TimeUnit;
 4             import org.springframework.context.annotation.Bean;
 5             import org.springframework.context.annotation.Configuration;
 6             import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 7             import org.springframework.http.client.ClientHttpRequestFactory;
 8             import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
 9             import org.springframework.web.client.RestTemplate;
10             import okhttp3.OkHttpClient;
11 
12             @Configuration
13             public class RestTemplateConfig3 {
14 
15                 @ConditionalOnMissingBean(RestTemplate.class)
16                 @Bean
17                 public RestTemplate restTemplate(){
18                     RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
19                     return restTemplate;
20                 }
21 
22                 private ClientHttpRequestFactory getClientHttpRequestFactory(){
23                     OkHttpClient okHttpClient = new OkHttpClient.Builder()
24                             .connectTimeout(5, TimeUnit.SECONDS)
25                             .writeTimeout(5, TimeUnit.SECONDS)
26                             .readTimeout(5, TimeUnit.SECONDS)
27                             .build();
28                     return new OkHttp3ClientHttpRequestFactory(okHttpClient);
29                 }
30             }


4. 示例

    示例需要用到实体类,为了简化实体类的书写,这里使用了 lombok (https://github.com/projectlombok/lombok)。

    1) 修改 pom.xml,导入 lombok 依赖包

 1         <project ... >
 2             ...
 3             <dependencies>
 4                 ...
 5 
 6                 <dependency>
 7                     <groupId>org.projectlombok</groupId>
 8                     <artifactId>lombok</artifactId>
 9                     <version>1.18.8</version>
10                 </dependency>
11 
12                 ...
13             </dependencies>
14 
15             ...
16         </project>


        lombok 提供了一些简化实体类定义的注解,常用的注解如下:

            @Getter 使用方法同上,区别在于生成的是 getter 方法。
            @Setter 注解在类或字段,注解在类时为所有字段生成 setter 方法,注解在字段上时只为该字段生成setter 方法。
            @ToString 注解在类,添加 toString 方法。
            @NoArgsConstructor 注解在类,生成无参的构造方法。
            @AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。
            @RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
            @EqualsAndHashCode 注解在类,生成 hashCode 和 equals 方法。
            @Data 注解在类,生成 setter/getter、equals、canEqual、hashCode、toString 方法,如为 final 属性,则不会为该属性生成 setter 方法。
            @Slf4j 注解在类,生成 log 变量,严格意义来说是常量。

        IDEA 还需要安装 lombok 插件,这里以 Windows 版的 IDEA 为例,安装步骤如下:

            菜单 File -> Settings -> 选中左侧 Plugin ->  搜索 "lombok" -> Install lombok plugin -> Restart IDEA

        在IDE中项目列表 -> SpringbootExample18 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 创建 src/main/java/com/example/entity/User.java 文件

 1         package com.example.entity;
 2 
 3         import lombok.Data;
 4         import lombok.NoArgsConstructor;
 5         import lombok.experimental.Accessors;
 6         import java.io.Serializable;
 7 
 8         @NoArgsConstructor // 无参构造函数
 9         @Data // 提供类的 get、set、equals、hashCode、canEqual、toString 方法
10         @Accessors(chain = true)
11         public class User implements Serializable {
12             private Integer id;
13             private String username;
14             private String password;
15 
16         }


    3) 创建 src/main/java/com/example/controller/ApiController.java 文件

 1         package com.example.controller;
 2 
 3         import javax.servlet.http.HttpServletResponse;
 4 
 5         import org.springframework.stereotype.Controller;
 6         import org.springframework.web.bind.annotation.PathVariable;
 7         import org.springframework.web.bind.annotation.RequestMapping;
 8         import org.springframework.web.bind.annotation.RequestMethod;
 9         import org.springframework.web.bind.annotation.ResponseBody;
10 
11         import com.example.entity.User;
12 
13         @Controller
14         @RequestMapping(value = "/api")
15         public class ApiController {
16 
17             @ResponseBody
18             @RequestMapping(value = "/user/get", method = RequestMethod.GET)
19             public User getUser(){
20                 User user = new User();
21                 user.setId(1);
22                 user.setUsername("admin");
23                 user.setPassword("123456");
24                 return user;
25             }
26 
27             @ResponseBody
28             @RequestMapping(value = "/user/post", method = RequestMethod.POST)
29             public User postUser(HttpServletResponse response, User user){
30                 response.setHeader("Location", "http://localhost:9090/test");
31                 return user;
32             }
33 
34             @RequestMapping(value = "/user/post/location", method = RequestMethod.POST)
35             public void postLocation(HttpServletResponse response, User user) {
36                 response.setHeader("Location", "http://localhost:9090/test");
37             }
38 
39             @ResponseBody
40             @RequestMapping(value = "/user/put", method = RequestMethod.PUT)
41             public void putUser(User user){
42                 System.out.println("PUT: " + user.toString());
43             }
44 
45             @ResponseBody
46             @RequestMapping(value = "/user/delete/{id}", method = RequestMethod.DELETE)
47             public void deleteUser(@PathVariable Integer id){
48                 System.out.println("DELETE: " + id);
49             }
50         }


    4) 修改 src/main/java/com/example/controller/IndexController.java 文件

  1         package com.example.controller;
  2 
  3         import java.net.URI;
  4         import java.util.Set;
  5         import javax.servlet.http.HttpServletRequest;
  6 
  7         import org.springframework.beans.factory.annotation.Autowired;
  8         import org.springframework.http.*;
  9         import org.springframework.stereotype.Controller;
 10         import org.springframework.util.LinkedMultiValueMap;
 11         import org.springframework.util.MultiValueMap;
 12         import org.springframework.web.bind.annotation.PathVariable;
 13         import org.springframework.web.bind.annotation.RequestMapping;
 14         import org.springframework.web.bind.annotation.ResponseBody;
 15         import org.springframework.web.client.RestTemplate;
 16 
 17         import com.example.entity.User;
 18 
 19         @Controller
 20         public class IndexController {
 21             @Autowired
 22             private RestTemplate restTemplate;
 23 
 24             @ResponseBody
 25             @RequestMapping("/test")
 26             public String test() {
 27                 return "Test Page";
 28             }
 29 
 30             @ResponseBody
 31             @RequestMapping("/user/get/object")
 32             public String getUserObject(HttpServletRequest request) {
 33                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
 34                 url += request.getContextPath() + "/api/user/get";
 35             // System.out.println("url: " + url);
 36 
 37                 User user = restTemplate.getForObject(url, User.class);
 38                 return user.toString();
 39             }
 40 
 41             @ResponseBody
 42             @RequestMapping("/user/get/entity")
 43             public String getUserEntity(HttpServletRequest request) {
 44                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
 45                 url += request.getContextPath() + "/api/user/get";
 46 
 47                 ResponseEntity<User> responseEntity = restTemplate.getForEntity(url, User.class);
 48 
 49                 String str = "HTTP status code: " + responseEntity.getStatusCode() + "<br>";
 50                 str += "HTTP status value: " +  responseEntity.getStatusCodeValue() + "<br>";
 51                 str += "HTTP headers: " +  responseEntity.getHeaders() + "<br>";
 52                 str += "HTTP body: " + responseEntity.getBody().toString() + "<br>";
 53 
 54                 return str;
 55             }
 56 
 57             @ResponseBody
 58             @RequestMapping("/user/get/options")
 59             public String getUserOptions(HttpServletRequest request) {
 60                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
 61                 url += request.getContextPath() + "/api/user/get";
 62 
 63                 Set<HttpMethod> options = restTemplate.optionsForAllow(url);
 64 
 65                 return options.toString();
 66             }
 67 
 68             @ResponseBody
 69             @RequestMapping("/user/get/headers")
 70             public String getUserHeader(HttpServletRequest request) {
 71                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
 72                 url += request.getContextPath() + "/api/user/get";
 73 
 74                 HttpHeaders httpHeaders = restTemplate.headForHeaders(url);
 75 
 76                 return httpHeaders.toString();
 77             }
 78 
 79             @ResponseBody
 80             @RequestMapping("/user/post/object")
 81             public String postUserObject(HttpServletRequest request) {
 82                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
 83                 url += request.getContextPath() + "/api/user/post";
 84 
 85                 // 请求头设置, x-www-form-urlencoded格式的数据
 86                 HttpHeaders headers = new HttpHeaders();
 87                 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 88 
 89                 // 提交参数设置
 90                 MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
 91                 map.add("id", "2");
 92                 map.add("username", "user");
 93                 map.add("password", "123123");
 94 
 95                 // 组装请求体
 96                 HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(map, headers);
 97 
 98                 // 发起请求
 99                 User user = restTemplate.postForObject(url, httpEntity, User.class);
100                 return user.toString();
101             }
102 
103             @ResponseBody
104             @RequestMapping("/user/post/entity")
105             public String postUserEntity(HttpServletRequest request) {
106                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
107                 url += request.getContextPath() + "/api/user/post";
108 
109                 // 请求头设置, x-www-form-urlencoded格式的数据
110                 HttpHeaders headers = new HttpHeaders();
111                 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
112 
113                 // 提交参数设置
114                 MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
115                 map.add("id", "2");
116                 map.add("username", "user");
117                 map.add("password", "123123");
118 
119                 // 组装请求体
120                 HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(map, headers);
121 
122                 // 发起请求
123                 ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, httpEntity, User.class);
124                 String str = "HTTP status code: " + responseEntity.getStatusCode() + "<br>";
125                 str += "HTTP status value: " +  responseEntity.getStatusCodeValue() + "<br>";
126                 str += "HTTP headers: " +  responseEntity.getHeaders() + "<br>";
127                 str += "HTTP body: " + responseEntity.getBody().toString() + "<br>";
128 
129                 return str;
130             }
131 
132             @ResponseBody
133             @RequestMapping("/user/post/location")
134             public String postLocation(HttpServletRequest request) {
135                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
136                 url += request.getContextPath() + "/api/user/post/location";
137 
138                 User user = new User();
139                 user.setId(3);
140                 user.setUsername("test");
141                 user.setPassword("666666");
142 
143                 URI uri = restTemplate.postForLocation(url, user);
144                 return "Url: " + uri;
145             }
146 
147             @ResponseBody
148             @RequestMapping("/user/put")
149             public String putUser(HttpServletRequest request) {
150                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
151                 url += request.getContextPath() + "/api/user/put";
152 
153                 User user = new User();
154                 user.setId(5);
155                 user.setUsername("put123");
156                 user.setPassword("123456");
157 
158                 restTemplate.put(url, user);
159                 return "PUT: " + user.toString();
160             }
161 
162             @ResponseBody
163             @RequestMapping("/user/delete/{id}")
164             public String deleteUser(HttpServletRequest request, @PathVariable Integer id) {
165                 String url = "http://" + request.getServerName() + ":" + request.getServerPort();
166                 url += request.getContextPath() + "/api/user/delete/" + id;
167 
168                 System.out.println("url: " + url);
169 
170                 restTemplate.delete(url);
171                 return "Delete: " + id;
172             }
173 
174         }


    5) 运行测试

        浏览器访问 http://localhost:9090/user/get/object,页面显示如下:

            User(id=1, username=admin, password=123456)

        访问 http://localhost:9090/user/get/entity,页面显示如下:

            HTTP status code: 200 OK
            HTTP status value: 200
            HTTP headers: [Connection:"keep-alive", Content-Type:"application/json", Date:"Wed, 22 Jun 2022 10:51:57 GMT", Keep-Alive:"timeout=60", Transfer-Encoding:"chunked"]
            HTTP body: User(id=1, username=admin, password=123456)

        访问 http://localhost:9090/user/get/options,页面显示如下:

            [GET, HEAD, OPTIONS]

        访问 http://localhost:9090/user/get/headers,页面显示如下:

            [Connection:"keep-alive", Content-Length:"47", Content-Type:"application/json", Date:"Wed, 22 Jun 2022 10:53:12 GMT", Keep-Alive:"timeout=60"]

        访问 http://localhost:9090/user/post/object,页面显示如下:

            User(id=2, username=user, password=123123)

        访问 http://localhost:9090/user/post/entity,页面显示如下:

            HTTP status code: 200 OK
            HTTP status value: 200
            HTTP headers: [Connection:"keep-alive", Content-Type:"application/json", Date:"Wed, 22 Jun 2022 10:54:43 GMT", Keep-Alive:"timeout=60", Location:"http://localhost:9090/test", Transfer-Encoding:"chunked"]
            HTTP body: User(id=2, username=user, password=123123)

        访问 http://localhost:9090/user/post/location,页面显示如下:

            Url: http://localhost:9090/test

        访问 http://localhost:9090/user/put,页面显示如下:

            PUT: User(id=5, username=put123, password=123456)

        访问 http://localhost:9090/user/delete/2,页面显示如下:

            Delete: 2



本文资料参考来源:

1. https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/integration.html#rest-client-access
2. https://www.jianshu.com/p/58949f8334f5
3. https://www.jianshu.com/p/2a59bb937d21
4. https://zhuanlan.zhihu.com/p/258121569

posted @ 2022-06-22 19:33  垄山小站  阅读(879)  评论(0编辑  收藏  举报