0%

Spring Boot2 实战系列之使用 RestTemplate

前言

在另一篇博文 Spring Boot2 实战系列之RESTful Web Service 中我们构建了一个 restful 风格的项目,并用 postman 来围绕 HTTP 动词 GET,POST,PUT,DELETE 对员工信息展开 CURD 操作。那么在实际项目中,如果我们想调用别人提供的 resultful api 来请求网络资源应该怎样做呢,很多人会想到用 httpclient,但是使用 httpclient 代码比较复杂,冗余代码多,相比之下,Spring 框架提供的 RestTemplate 类可用于在应用中调用 REST 服务, 它提供了许多比较简便的访问远程 HTTP 服务的方法,从而提高了客户端的开发效率。

RestTemplate 类是为调用REST服务而设计的,它的主要方法与 HTTP 协议方法紧密相连, 比如它有 headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。

下面通过一个页面操作例子来演示 RestTemplate 的使用。

注意:在启动该项目前,先启动另一篇博文中构建的 RESTful service 项目。

创建项目

项目结构图如下:

pom 依赖文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.yekongle</groupId>
<artifactId>springboot-restclient-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-restclient-sample</name>
<description>Rest client project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

代码编写

application.properties, 全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 容器配置
server.port=8090

# Thymeleaf 配置
# 模板文件位置
spring.thymeleaf.prefix=classpath:/templates/
# 文件后缀
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
# 编码
spring.thymeleaf.encoding=UTF-8
# 关闭缓存
spring.thymeleaf.cache=false

RestConfig.java, RestTemplate 配置,加入到 spring bean中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package top.yekongle.restclient.config;

import java.time.Duration;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(5000))
.build();
}

}

Employee.java, 员工实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package top.yekongle.restclient.entity;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import lombok.Data;

@Data
public class Employee {
private Long id;
private String name;
private String role;
private String uri;

// 构建资源定位符
public String getUri() {
return ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(this.getId()).toUri().toString();
}
}

EmployeeController.java, 控制器,对员工信息进行 CURD 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package top.yekongle.restclient.controller;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import lombok.extern.slf4j.Slf4j;
import top.yekongle.restclient.entity.Employee;

@Slf4j
@Controller
public class EmployeeController {

@Autowired
private RestTemplate restTemplate;

/**
* 通过 RestTemplate 获取员工列表
* */
@GetMapping("/employees")
public String employees(Model model) {
log.info("Get mapping");
final String uri = "http://localhost:8080/employees";
Employee[] employees = restTemplate.getForObject(uri, Employee[].class);
List<Employee> employeeList = Arrays.asList(employees);
log.info("employeeList:{}", employeeList.toString());

model.addAttribute("title", "员工列表");
model.addAttribute("employees", employeeList);
model.addAttribute("addEmployee", "新增员工");

return "index";
}

/**
* 通过 RestTemplate 删除员工信息
* */
@RequestMapping(value = "/employees/{id}")
public String deleteEmployee(@PathVariable Long id) {
final String uri = "http://localhost:8080/employees/{id}";
Map<String, String> params = new HashMap<String, String>();
params.put("id", String.valueOf(id));
restTemplate.delete(uri, params);
return "redirect:/employees";
}

/**
* 通过 RestTemplate 新建员工信息
* */
@PostMapping(value = "/employees")
public ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
log.info("Post mapping");
log.info("New employee:{}", newEmployee.toString());
final String uri = "http://localhost:8080/employees";
restTemplate.postForObject(uri, newEmployee, Employee.class);

return ResponseEntity.ok().build();
}

/**
* 通过 RestTemplate 更新员工信息
* */
@PutMapping(value = "/employees/{id}")
public ResponseEntity<?> updateEmployee(@RequestBody Employee updatedEmployee, @PathVariable Long id) {
log.info("Put mapping");
final String uri = "http://localhost:8080/employees/{id}";
Map<String, String> params = new HashMap<String, String>();
params.put("id", String.valueOf(id));
restTemplate.put(uri, updatedEmployee, params);

return ResponseEntity.ok().build();
}

}

index.html, 员工信息操作页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<html xmlns:th="http://www.thymeleaf.org">
<!-- Thymeleaf的命名空间,将静态页面转换为动态的视图 -->
<head>
<meta content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet" />
</head>
<body>

<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<!-- 需要动态处理的元素使用 th: 为前缀,通过 ${} 访问 model中的属性-->
<h3 class="panel-title" th:text="${title}">title</h3>
</div>
<div class="panel-body">
<!--数据判断-->
<div th:if="${not #lists.isEmpty(employees)}">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>职位</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--数据迭代-->
<tr th:each="employee: ${employees}">
<td th:text="${employee.id}" onclick="tdclick(this)">ID</td>
<td class="name" th:text="${employee.name}" onclick="tdclick(this)">Name</td>
<td class="role" th:text="${employee.role}" onclick="tdclick(this)">Role</td>
<td> <a class ="update" th:url="${employee.uri}">更改</a> | <a class ="delete" th:href="${employee.uri}">删除</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

<div class="panel panel-primary">
<div class="panel-heading">
<!-- 需要动态处理的元素使用 th: 为前缀,通过 ${} 访问 model中的属性-->
<h3 class="panel-title" th:text="${addEmployee}">addEmployee</h3>
</div>
<div class="panel-body">
<form>
<div class="row">
<div class="form-group col-md-6">
<label for="name">姓名</label> <input type="text"
class="form-control" id="name">
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label for="role">职位</label> <input type="text"
class="form-control" id="role">
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<button id ="submit" type="button" class="btn btn-primary">新增</button>
</div>
</div>
</form>
</div>
</div>

</div>

<script th:src="@{jquery.min.js}" type="text/javascript"></script>
<script th:src="@{bootstrap/js/bootstrap.min.js}"></script>

<!--在 javascript 中访问model属性-->
<script th:inline="javascript">
var title = [[${title}]];
console.log(title);

$("#submit").on("click", function () {
console.log("click");
var name = $("#name").val();
var role = $("#role").val();

$.ajax({
url: "/employees",
type : "POST",
contentType: "application/json;charset=utf-8",
data: JSON.stringify({'name': name, 'role': role}),
success: function (data) {
$.ajax({
url: "/employees",
type: "GET",
success: function (data) {
location.reload();
}
});
},
error: function () {
console.log("error");
}
});
});

$("a.update").each(function() {
$(this).on("click", function () {
var updateUrl = $(this).attr("url");
var name = $(this).parent().siblings(".name").html();
var role = $(this).parent().siblings(".role").html();
$.ajax({
url: updateUrl,
type: "PUT",
contentType: "application/json;charset=utf-8",
data: JSON.stringify({'name': name, 'role': role}),
success: function (data) {
alert("Update success!");
$.ajax({
url: "/employees",
type : "GET",
success: function (data) {
location.reload();
}
});
}
});
});
});

function tdclick(tdobject) {
var td=$(tdobject);
td.attr("onclick", "");
//1,取出当前td中的文本内容保存起来
var text=td.text();
//2,清空td里面的内容
td.html(""); //也可以用td.empty();
//3,建立一个文本框,也就是input的元素节点
var input=$("<input>");
//4,设置文本框的值是保存起来的文本内容
input.attr("value", text);
input.bind("blur",function(){
var inputnode=$(this);
var inputtext=inputnode.val();
var tdNode=inputnode.parent();
tdNode.html(inputtext);
tdNode.click(tdclick);
td.attr("onclick", "tdclick(this)");
});

input.keyup(function(event){
var myEvent =event||window.event;
var kcode=myEvent.keyCode;
if(kcode==13){
var inputnode=$(this);
var inputtext=inputnode.val();
var tdNode=inputnode.parent();
tdNode.html(inputtext);
tdNode.click(tdclick);
}
});

//5,将文本框加入到td中
td.append(input);
var t =input.val();
input.val("").focus().val(t);

//6,清除点击事件
td.unbind("click");
}
</script>

</body>
</html>

运行演示

首先启动 Restful service 项目,默认在 8080 端口
然后启动 Restful client 项目,在 8090 端口
访问 http://localhost:8090/employees, 获取员工信息列表

双击员工信息字段进行更改,然后点击右侧 “更改”

新增员工信息

删除员工信息

项目已上传至 Github: https://github.com/yekongle/springboot-code-samples/tree/master/springboot-restclient-sample , 希望对小伙伴们有帮助哦。

参考链接:
https://www.cnblogs.com/f-anything/p/10084215.html

坚持原创技术分享,您的支持将鼓励我继续创作!