12、使用Spring Boot构建REST服务

使用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应用程序打下坚实的基础。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值