使用Spring Boot构建REST服务
1. Spring Boot自动配置组件
Spring Boot提供了许多自动配置组件,这些组件在构建Web应用程序时非常有用。以下是一些重要的自动配置组件:
| 组件名称 | 描述 |
| — | — |
| characterEncodingFilter | 提供默认字符编码UTF - 8 |
| dispatcherServlet | Spring MVC应用程序中的前端控制器 |
| jacksonObjectMapper | 在REST服务中实现对象与JSON之间的转换 |
| messageConverters | 默认的消息转换器,用于对象与XML或JSON之间的相互转换 |
| multipartResolver | 支持Web应用程序中的文件上传 |
| mvcValidator | 支持HTTP请求的验证 |
| viewResolver | 将逻辑视图名称解析为物理视图 |
| propertySourcesPlaceholderConfigurer | 支持应用程序配置的外部化 |
| requestContextFilter | 请求的默认过滤器 |
| restTemplateBuilder | 用于调用REST服务 |
| tomcatEmbeddedServletContainerFactory | Tomcat是基于Spring Boot的Web应用程序的默认嵌入式Servlet容器 |
2. Spring Boot启动器项目
Spring Boot提供了一系列启动器项目,这些项目简化了依赖管理和配置。以下是一些重要的启动器项目:
| 启动器项目 | 描述 |
| — | — |
| spring - boot - starter - web - services | 用于开发基于XML的Web服务的启动器项目 |
| spring - boot - starter - web | 用于构建基于Spring MVC的Web应用程序或RESTful应用程序,默认使用Tomcat作为嵌入式Servlet容器 |
| spring - boot - starter - activemq | 支持在ActiveMQ上使用JMS进行基于消息的通信 |
| spring - boot - starter - integration | 支持Spring集成框架,提供企业集成模式的实现 |
| spring - boot - starter - test | 为各种单元测试框架(如JUnit、Mockito和Hamcrest匹配器)提供支持 |
| spring - boot - starter - jdbc | 支持使用Spring JDBC,默认配置Tomcat JDBC连接池 |
| spring - boot - starter - validation | 支持Java Bean验证API,默认实现为hibernate - validator |
| spring - boot - starter - hateoas | HATEOAS代表超媒体作为应用程序状态的引擎,使用HATEOAS的RESTful服务除了返回数据外,还会返回与当前上下文相关的额外资源链接 |
| spring - boot - starter - jersey | JAX - RS是开发REST API的Java EE标准,Jersey是默认实现,该启动器项目支持构建基于JAX - RS的REST API |
| spring - boot - starter - websocket | HTTP是无状态的,WebSockets允许在服务器和浏览器之间保持连接,该启动器项目支持Spring WebSockets |
| spring - boot - starter - aop | 支持面向切面编程,也支持AspectJ进行高级面向切面编程 |
| spring - boot - starter - amqp | 默认使用RabbitMQ,该启动器项目支持通过AMQP进行消息传递 |
| spring - boot - starter - security | 启用Spring Security的自动配置 |
| spring - boot - starter - data - jpa | 支持Spring Data JPA,默认实现为Hibernate |
| spring - boot - starter | Spring Boot应用程序的基础启动器,提供自动配置和日志支持 |
| spring - boot - starter - batch | 支持使用Spring Batch开发批处理应用程序 |
| spring - boot - starter - cache | 提供使用Spring框架进行缓存的基本支持 |
| spring - boot - starter - data - rest | 支持使用Spring Data REST暴露REST服务 |
3. REST概述
Representational State Transfer (REST) 是一种用于Web的架构风格,它规定了一组约束条件,以确保客户端(服务消费者和浏览器)能够以灵活的方式与服务器进行交互。
3.1 常用术语
- 服务器 :服务提供者,暴露可以被客户端消费的服务。
- 客户端 :服务消费者,可以是浏览器或其他系统。
- 资源 :任何信息都可以是资源,如人、图像、视频或要销售的产品。
- 表示 :资源的特定表示方式,例如产品资源可以使用JSON、XML或HTML表示,不同的客户端可能会请求资源的不同表示。
3.2 REST约束条件
- 客户端 - 服务器 :应该有一个服务器(服务提供者)和一个客户端(服务消费者),这使得服务器和客户端能够松散耦合,并在新技术出现时独立发展。
- 无状态 :每个服务应该是无状态的,后续请求不应依赖于先前请求中临时存储的某些数据,消息应该是自描述的。
- 统一接口 :每个资源都有一个资源标识符,例如在Web服务中,使用URI /users/Jack/todos/1,其中Jack是用户的名称,1是要检索的todo的ID。
- 可缓存 :服务响应应该是可缓存的,每个响应应该指示是否可缓存。
- 分层系统 :服务消费者不应假设与服务提供者有直接连接,由于请求可以被缓存,客户端可能从中间层获取缓存的响应。
- 通过表示操作资源 :资源可以有多种表示形式,应该可以通过包含任何这些表示形式的消息来修改资源。
- 超媒体作为应用程序状态的引擎(HATEOAS) :RESTful应用程序的消费者应该只知道一个固定的服务URL,所有后续资源应该可以从资源表示中包含的链接中发现。
以下是一个包含HATEOAS链接的响应示例,这是一个检索所有todos的请求的响应:
{
"_embedded": {
"todos": [
{
"user": "Jill",
"desc": "Learn Hibernate",
"done": false,
"_links": {
"self": {
"href": "http://localhost:8080/todos/1"
},
"todo": {
"href": "http://localhost:8080/todos/1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/todos"
},
"profile": {
"href": "http://localhost:8080/profile/todos"
},
"search": {
"href": "http://localhost:8080/todos/search"
}
}
}
这个响应包含了特定todos和搜索资源的链接,如果服务消费者想要进行搜索,可以从响应中获取搜索URL并发送搜索请求,这将减少服务提供者和服务消费者之间的耦合。
4. 第一个REST服务
我们将创建一个简单的REST服务,返回一个欢迎消息。首先,创建一个简单的POJO类WelcomeBean:
package com.mastering.spring.springboot.bean;
public class WelcomeBean {
private String message;
public WelcomeBean(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
}
4.1 返回字符串的简单方法
创建一个简单的REST控制器方法,返回一个字符串:
@RestController
public class BasicController {
@GetMapping("/welcome")
public String welcome() {
return "Hello World";
}
}
需要注意的是:
-
@RestController
:该注解是
@ResponseBody
和
@Controller
注解的组合,通常用于创建REST控制器。
-
@GetMapping("welcome")
:
@GetMapping
是
@RequestMapping(method = RequestMethod.GET)
的快捷方式,该注解是一种更易读的替代方式,带有此注解的方法将处理对
welcome
URI的GET请求。
如果运行
Application.java
作为Java应用程序,它将启动嵌入式Tomcat容器,我们可以在浏览器中访问相应的URL。
4.2 单元测试
编写一个单元测试来测试上述控制器方法:
@RunWith(SpringRunner.class)
@WebMvcTest(BasicController.class)
public class BasicControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void welcome() throws Exception {
mvc.perform(
MockMvcRequestBuilders.get("/welcome")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(
equalTo("Hello World")));
}
}
需要注意的是:
-
@RunWith(SpringRunner.class)
:
SpringRunner
是
SpringJUnit4ClassRunner
注解的快捷方式,用于启动一个简单的Spring上下文进行单元测试。
-
@WebMvcTest(BasicController.class)
:该注解与
SpringRunner
一起使用,用于编写Spring MVC控制器的简单测试,它只加载带有Spring - MVC相关注解的Bean。
-
@Autowired private MockMvc mvc
:自动注入
MockMvc
Bean,用于发送请求。
-
mvc.perform(MockMvcRequestBuilders.get("/welcome").accept(MediaType.APPLICATION_JSON))
:执行对
/welcome
的请求,接受的内容类型为
application/json
。
-
andExpect(status().isOk())
:期望响应状态为200(成功)。
-
andExpect(content().string(equalTo("Hello World")))
:期望响应内容为
Hello World
。
4.3 集成测试
编写一个集成测试:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BasicControllerIT {
private static final String LOCAL_HOST =
"http://localhost:";
@LocalServerPort
private int port;
private TestRestTemplate template = new TestRestTemplate();
@Test
public void welcome() throws Exception {
ResponseEntity<String> response = template
.getForEntity(createURL("/welcome"), String.class);
assertThat(response.getBody(), equalTo("Hello World"));
}
private String createURL(String uri) {
return LOCAL_HOST + port + uri;
}
}
需要注意的是:
-
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
:在Spring TestContext的基础上提供额外的功能,支持配置端口以完全运行容器和
TestRestTemplate
(用于执行请求)。
-
@LocalServerPort private int port
:
SpringBootTest
会确保容器运行的端口自动注入到
port
变量中。
-
private String createURL(String uri)
:该方法用于将本地主机URL和端口附加到URI以创建完整的URL。
-
private TestRestTemplate template = new TestRestTemplate()
:
TestRestTemplate
通常用于集成测试,它在
RestTemplate
的基础上提供了额外的功能,在集成测试上下文中特别有用,它不跟随重定向,以便我们可以断言响应位置。
-
template.getForEntity(createURL("/welcome"), String.class)
:执行对给定URI的GET请求。
-
assertThat(response.getBody(), equalTo("Hello World"))
:断言响应体内容为
Hello World
。
5. 返回对象的简单REST方法
创建一个返回JSON响应的方法:
@GetMapping("/welcome-with-object")
public WelcomeBean welcomeWithObject() {
return new WelcomeBean("Hello World");
}
发送测试请求,对于
http://localhost:8080/welcome-with-object
URL的响应如下:
{"message":"Hello World"}
这是Spring Boot自动配置的魔力,如果Jackson在应用程序的类路径中,Spring Boot会自动配置默认的对象与JSON之间的转换器。
5.1 单元测试
编写单元测试来检查JSON响应:
@Test
public void welcomeWithObject() throws Exception {
mvc.perform(
MockMvcRequestBuilders.get("/welcome-with-object")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello World")));
}
这个测试与前面的单元测试类似,只是使用
containsString
来检查内容是否包含子字符串
Hello World
。
5.2 集成测试
编写集成测试:
@Test
public void welcomeWithObject() throws Exception {
ResponseEntity<String> response =
template.getForEntity(createURL("/welcome-with-object"),
String.class);
assertThat(response.getBody(),
containsString("Hello World"));
}
这个方法与前面的集成测试类似,只是使用字符串方法断言子字符串。
6. 带有路径变量的GET方法
使用路径变量绑定URI中的值到控制器方法的变量:
private static final String helloWorldTemplate = "Hello World, %s!";
@GetMapping("/welcome-with-parameter/name/{name}")
public WelcomeBean welcomeWithParameter(@PathVariable String name) {
return new WelcomeBean(String.format(helloWorldTemplate, name));
}
需要注意的是:
-
@GetMapping("/welcome-with-parameter/name/{name}")
:
{name}
表示这是一个变量,URI中可以有多个变量模板。
-
welcomeWithParameter(@PathVariable String name)
:
@PathVariable
确保URI中的变量值绑定到
name
变量。
-
String.format(helloWorldTemplate, name)
:一个简单的字符串格式化方法,用于将模板中的
%s
替换为
name
。
发送测试请求,对于
http://localhost:8080/welcome-with-parameter/name/Buddy
URL的响应如下:
{"message":"Hello World, Buddy!"}
6.1 单元测试
编写单元测试:
@Test
public void welcomeWithParameter() throws Exception {
mvc.perform(
MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(
content().string(containsString("Hello World, Buddy")));
}
需要注意的是:
-
MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy")
:该请求匹配URI中的变量模板,传递的名称为
Buddy
。
-
.andExpect(content().string(containsString("Hello World, Buddy")))
:期望响应包含带有名称的消息。
6.2 集成测试
编写集成测试:
@Test
public void welcomeWithParameter() throws Exception {
ResponseEntity<String> response =
template.getForEntity(
createURL("/welcome-with-parameter/name/Buddy"), String.class);
assertThat(response.getBody(),
containsString("Hello World, Buddy"));
}
需要注意的是:
-
createURL("/welcome-with-parameter/name/Buddy")
:该URL匹配URI中的变量模板,传递的名称为
Buddy
。
-
assertThat(response.getBody(), containsString("Hello World, Buddy"))
:期望响应包含带有名称的消息。
7. 创建todo资源
接下来,我们将为一个基本的todo管理系统创建REST服务,包括以下功能:
- 检索给定用户的todo列表
- 检索特定todo的详细信息
- 为用户创建一个todo
7.1 请求方法、操作和URI
REST服务的最佳实践之一是根据执行的操作使用适当的HTTP请求方法。以下是根据操作选择的HTTP请求方法:
| HTTP请求方法 | 操作 |
| — | — |
| GET | 读取 - 检索资源的详细信息 |
| POST | 创建 - 创建新的项目或资源 |
| PUT | 更新/替换 |
| PATCH | 更新/修改资源的一部分 |
| DELETE | 删除 |
将我们要创建的服务映射到适当的请求方法和URI:
- 检索给定用户的todo列表:使用GET请求,URI为
/users/{name}/todos
,建议在URI中使用复数形式,使URI更易读。
- 检索特定todo的详细信息:使用GET请求,URI为
/users/{name}/todos/{id}
,与前面的URI保持一致。
- 为用户创建一个todo:使用POST请求,URI为
/users/{name}/todos
。
7.2 Bean和服务
为了能够检索和存储todo的详细信息,需要一个
Todo
Bean和一个服务来处理这些操作。
创建
Todo
Bean:
public class Todo {
private int id;
private String user;
private String desc;
private Date targetDate;
private boolean isDone;
public Todo() {}
public Todo(int id, String user, String desc,
Date targetDate, boolean isDone) {
super();
this.id = id;
this.user = user;
this.desc = desc;
this.targetDate = targetDate;
this.isDone = isDone;
}
// ALL Getters
}
创建
TodoService
:
@Service
public class TodoService {
private static List<Todo> todos = new ArrayList<Todo>();
private static int todoCount = 3;
static {
todos.add(new Todo(1, "Jack", "Learn Spring MVC",
new Date(), false));
todos.add(new Todo(2, "Jack", "Learn Struts", new Date(),
false));
todos.add(new Todo(3, "Jill", "Learn Hibernate", new Date(),
false));
}
public List<Todo> retrieveTodos(String user) {
List<Todo> filteredTodos = new ArrayList<Todo>();
for (Todo todo : todos) {
if (todo.getUser().equals(user))
filteredTodos.add(todo);
}
return filteredTodos;
}
public Todo addTodo(String name, String desc,
Date targetDate, boolean isDone) {
Todo todo = new Todo(++todoCount, name, desc, targetDate,
isDone);
todos.add(todo);
return todo;
}
}
通过以上步骤,我们可以使用Spring Boot构建一个简单的REST服务,并为其编写单元测试和集成测试,同时为一个基本的todo管理系统创建了相应的REST服务。后续可以进一步完善这些服务,使其更符合REST的约束条件。
使用Spring Boot构建REST服务
8. 实现todo资源的REST服务
8.1 检索给定用户的todo列表
根据前面的设计,我们使用GET请求和
/users/{name}/todos
URI来实现该功能。以下是控制器代码示例:
@RestController
@RequestMapping("/users")
public class TodoController {
@Autowired
private TodoService todoService;
@GetMapping("/{name}/todos")
public List<Todo> retrieveTodosForUser(@PathVariable String name) {
return todoService.retrieveTodos(name);
}
}
操作步骤如下:
1. 创建一个
TodoController
类,使用
@RestController
注解将其标记为REST控制器。
2. 使用
@RequestMapping("/users")
指定控制器的基础URI。
3. 注入
TodoService
实例,用于处理业务逻辑。
4. 创建
retrieveTodosForUser
方法,使用
@GetMapping("/{name}/todos")
注解处理GET请求,通过
@PathVariable
获取用户名称,并调用
TodoService
的
retrieveTodos
方法返回该用户的todo列表。
8.2 检索特定todo的详细信息
使用GET请求和
/users/{name}/todos/{id}
URI实现该功能,代码如下:
@GetMapping("/{name}/todos/{id}")
public Todo retrieveTodo(@PathVariable String name, @PathVariable int id) {
List<Todo> todos = todoService.retrieveTodos(name);
for (Todo todo : todos) {
if (todo.getId() == id) {
return todo;
}
}
return null;
}
操作步骤:
1. 在
TodoController
类中添加
retrieveTodo
方法。
2. 使用
@GetMapping("/{name}/todos/{id}")
注解处理GET请求,通过
@PathVariable
获取用户名称和todo的ID。
3. 调用
TodoService
的
retrieveTodos
方法获取该用户的todo列表。
4. 遍历列表,找到匹配ID的todo并返回。
8.3 为用户创建一个todo
使用POST请求和
/users/{name}/todos
URI实现创建功能,代码如下:
@PostMapping("/{name}/todos")
public Todo createTodo(@PathVariable String name, @RequestBody Todo todo) {
todo.setUser(name);
return todoService.addTodo(name, todo.getDesc(), todo.getTargetDate(), todo.isDone());
}
操作步骤:
1. 在
TodoController
类中添加
createTodo
方法,使用
@PostMapping("/{name}/todos")
注解处理POST请求。
2. 通过
@PathVariable
获取用户名称,使用
@RequestBody
将请求体中的JSON数据映射到
Todo
对象。
3. 设置
Todo
对象的用户名称。
4. 调用
TodoService
的
addTodo
方法创建新的todo并返回。
9. 测试todo资源的REST服务
9.1 单元测试
为
TodoController
编写单元测试,确保各个方法的正确性。以下是一个简单的单元测试示例:
@RunWith(SpringRunner.class)
@WebMvcTest(TodoController.class)
public class TodoControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private TodoService todoService;
@Test
public void testRetrieveTodosForUser() throws Exception {
String user = "Jack";
List<Todo> todos = new ArrayList<>();
todos.add(new Todo(1, user, "Learn Spring MVC", new Date(), false));
when(todoService.retrieveTodos(user)).thenReturn(todos);
mvc.perform(
MockMvcRequestBuilders.get("/users/" + user + "/todos")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)));
}
}
操作步骤:
1. 使用
@RunWith(SpringRunner.class)
和
@WebMvcTest(TodoController.class)
启动单元测试环境。
2. 注入
MockMvc
实例,用于发送模拟请求。
3. 使用
@MockBean
模拟
TodoService
,避免实际调用业务逻辑。
4. 在测试方法中,设置模拟数据和预期结果。
5. 使用
mvc.perform
发送GET请求,使用
andExpect
断言响应状态和响应内容。
9.2 集成测试
编写集成测试,确保整个系统的正确性。以下是一个集成测试示例:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TodoControllerIT {
private static final String LOCAL_HOST = "http://localhost:";
@LocalServerPort
private int port;
private TestRestTemplate template = new TestRestTemplate();
@Test
public void testRetrieveTodosForUser() throws Exception {
String user = "Jack";
ResponseEntity<List> response = template.exchange(
createURL("/users/" + user + "/todos"),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List>() {});
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
}
private String createURL(String uri) {
return LOCAL_HOST + port + uri;
}
}
操作步骤:
1. 使用
@RunWith(SpringRunner.class)
和
@SpringBootTest
启动集成测试环境。
2. 获取随机端口号,并创建
TestRestTemplate
实例用于发送请求。
3. 在测试方法中,使用
template.exchange
发送GET请求,指定请求方法、请求URL和响应类型。
4. 使用
assertThat
断言响应状态码。
10. 总结与展望
10.1 总结
通过以上步骤,我们使用Spring Boot成功构建了一个简单的REST服务,包括创建欢迎消息服务、返回对象的服务、带有路径变量的服务,以及为todo管理系统创建了一系列REST服务。同时,我们为这些服务编写了单元测试和集成测试,确保服务的正确性和稳定性。在实现过程中,我们遵循了REST的一些最佳实践,如使用适当的HTTP请求方法和规范的URI。
10.2 展望
目前实现的服务还没有完全遵循REST的所有约束条件,例如HATEOAS。后续可以进一步完善这些服务,添加HATEOAS支持,使服务更加符合REST的架构风格。同时,可以考虑添加更多的功能,如更新和删除todo的操作,以及对服务进行性能优化和安全加固。
以下是一个简单的mermaid流程图,展示了todo管理系统的REST服务调用流程:
graph LR
A[客户端] -->|GET /users/{name}/todos| B(检索用户todo列表)
A -->|GET /users/{name}/todos/{id}| C(检索特定todo详细信息)
A -->|POST /users/{name}/todos| D(创建todo)
B --> E(TodoService)
C --> E
D --> E
E --> F(Todo列表)
通过这个流程图,我们可以清晰地看到客户端与服务端之间的交互流程,以及服务端内部的业务处理逻辑。
通过本文的介绍,你可以掌握使用Spring Boot构建REST服务的基本方法和步骤,为开发更复杂的RESTful应用程序打下坚实的基础。
1583

被折叠的 条评论
为什么被折叠?



