概览
前面的几篇文章,介绍了如使用Spring Boot来访问数据库,我们已经掌握了访问数据访问层组件的实现方法。接下来我们将会讨论另外一个组件,即如何使用Spring Boot来构建Web服务层。
为什么需要构建Web服务层?
看过前面文章的同学们知道,目前我们用Spring Boot构建的应用可以访问数据库,进行数据的存储和提取。但是我们的应用还没有对外提供服务的能力,在当下的分布式系统及微服务架构中,RESTful风格是一种主流的 Web 服务表现方式。接下来重点讨论下对RESTful的理解
RESTful理解
RESTful的API你可能经常听到,那具体是什么含义,什么样的API才满足RESTful的API呢,可能就比较迷惑了,
REST(Representational State Transfer,表述性状态转移)表述性状态转移这个确实抽象,它本质上只是一种架构风格而不是一种规范,这种架构风格把位于服务器端的访问入口看作一个资源,每个资源都使用 URI(Universal Resource Identifier,统一资源标识符) 得到一个唯一的地址,且在传输协议上使用标准的 HTTP 方法,比如最常见的 GET、PUT、POST 和 DELETE。满足这样风格的API,我们称之为RESTful API。举个例子,说明下满足RESTful风格API如何定义:
URL | HTTP请求方法 | 描述 |
---|---|---|
http://www/example.com/v1/users | GET | 获取User对象列表 |
http://www/example.com/v1/user/{id} | GET | 根据id查询User对象 |
http://www/example.com/v1/user | POST | 新增User对象 |
http://www/example.com/v1/user | PUT | 更新User对象 |
http://www/example.com/v1/user/{id} | DELETE | 根据ID删除User对象 |
另一方面,客户端与服务器端的数据交互涉及序列化、反序列号问题。关于序列化完成业务对象在网络环境上的传输的实现方式有很多,常见的有文本和二进制两大类。
目前 JSON 是一种被广泛采用的序列化方式,本课程中所有的代码实例我们都将 JSON 作为默认的序列化方式。
说了对RESTful理解后,使用Spring Boot如何构建RESTful风格的Web服务我们来个简单入门。
简单入门
实例代码对应的仓库地址:
引入依赖
在pom.xml的文件中引入相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--简化代码,自动生成get、set-->
<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.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
复制代码
添加配置
在resources目录下创建应用的配置文件application.yml,添加如下配置内容:
# 用来修改web服务的端口
server:
port: 8080
复制代码
配置文件中配置web服务的端口,如果不配置,默认使用也是8080端口。
编写代码
定义User请求VO
@Data
public class UserReqVO implements Serializable {
private String username;
private String password;
}
复制代码
定义User响应VO
@Data
public class UserRespVO implements Serializable {
private Integer id;
private String username;
private Date createTime;
}
复制代码
编写Controller用来暴露API
@RestController
@RequestMapping(value = "v1")
@Slf4j
public class UserController {
@PostMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
public R<Boolean> insert(@RequestBody UserReqVO userReqVO){
log.info("接受的参数,username:{}, password:{}", userReqVO.getUsername(), userReqVO.getPassword());
//调用Service层保存user
return R.ok(true);
}
@GetMapping(value = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<UserRespVO> selectById(@PathVariable Integer id){
log.info("接受的参数id:{}",id);
//模拟生成UserRespVO对象,实际应该是调用Service获取
UserRespVO userRespVO = new UserRespVO();
userRespVO.setCreateTime(new Date());
userRespVO.setId(id);
userRespVO.setUsername("张三");
return R.ok(userRespVO);
}
@PutMapping(value = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<Boolean> updateById(@PathVariable Integer id, @RequestBody UserReqVO userReqVO){
log.info("接受的参数,username:{}, password:{}",userReqVO.getUsername(), userReqVO.getPassword());
//调用Service层更新user
return R.ok(true);
}
@DeleteMapping(value = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<Boolean> deleteById(@PathVariable Integer id){
log.info("接受的参数id:{}",id);
//调用Service层删除user
return R.ok(true);
}
}
复制代码
在Controller中我们针对User资源定义了4个API,分别对应User资源新增、根据id查询User、根据id更新User、根据id删除User。我们使用了一系列的注解@RestController、@RequestMapping、@PostMapping、@PutMapping、@GetMapping、@DeleteMapping、@RequestBody、@PathVariable等等,稍后会具体介绍各个注解的含义。
到这里,一个典型的 RESTful 服务已经开发完成了,现在我们可以通过 java –jar 命令直接运行 Spring Boot 应用程序了。那么如何验证我们写的API是否可用,我们将引入 Postman 来演示如何通过 HTTP 协议暴露的API进行远程服务访问。我们以查询User为例,使用Postman 访问“http://localhost:8080/v1/user/1”端点以得到响应结果。
单元测试
除了使用Postman第三方的工具进行测试,我们还可以编写针对Controller层的单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void insert() throws Exception {
UserReqVO userReqVO = new UserReqVO();
userReqVO.setPassword("123456");
userReqVO.setUsername("张三");
//设置值
ObjectMapper mapper = new ObjectMapper();
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson = ow.writeValueAsString(userReqVO);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/v1/user")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info("mvcResult:{}", mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8")));
}
@Test
public void selectById() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/v1/user/1"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info("mvcResult:{}", mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8")));
}
@Test
public void updateById() throws Exception{
UserReqVO userReqVO = new UserReqVO();
userReqVO.setPassword("123456");
userReqVO.setUsername("张三");
//设置值
ObjectMapper mapper = new ObjectMapper();
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson = ow.writeValueAsString(userReqVO);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/v1/user/1")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info("mvcResult:{}", mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8")));
}
@Test
public void deleteById()throws Exception{
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.delete("/v1/user/1"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info("mvcResult:{}", mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8")));
}
}
复制代码
注解汇总
在快速入门中,我们使用Spring Boot来暴露我们Web服务的时候,使用了很多注解,接下来我将会详细介绍下每个注解。
基础注解
在原有 Spring Boot 应用程序的基础上,我们可以通过构建一系列的 Controller 类暴露 RESTful 风格的 API。这里的 Controller 与 Spring MVC 中的 Controller 概念上一致,最简单的 Controller 类如下代码所示:
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Hello World!";
}
}
复制代码
上述代码使用了@RestController和@GetMapper这两个注解。
@RestController 注解继承自 Spring MVC 中的 @Controller 注解,顾名思义就是一个基于 RESTful 风格的 HTTP 端点,并且会自动使用 JSON 实现 HTTP 请求和响应的序列化/反序列化方式。
通过这个特性,在构建 RESTful 服务时,我们可以使用 @RestController 注解代替 @Controller 注解以简化开发。不必没有方法上添加@ResponseBody注解。
另外一个 @GetMapping 注解也与 Spring MVC 中的 @RequestMapping 注解类似。我们先来看看 @RequestMapping 注解的定义,该注解所提供的属性都比较容易理解,如下代码所示:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
复制代码
@GetMapping 的注解的定义与 @RequestMapping 非常类似,只是默认使用了 RequestMethod.GET 指定 HTTP 方法,如下代码所示:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(
method = {RequestMethod.GET}
)
public @interface GetMapping {}
复制代码
除了@GetMapping注解外,Spring Boot 2.X还提供了 @PostMapping、@PutMapping、@DeleteMapping、@PatchMapping,这些注解都是显示的指定了Http请求的方式,方便开发了开发人员的使用。当然也可以使用@RequestMapping注解达到相同的效果。
控制请求和响应的注解
Spring Boot 提供了一系列简单有用的注解来简化对请求输入的控制过程,常用的包括 @PathVariable、@RequestParam、@RequestBody和@ModelAttribute注解。
其中 @PathVariable 注解用于获取路径参数,即从类似 url/{id} 这种形式的路径中获取 {id} 参数的值。
该注解定义如下:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
复制代码
使用 @PathVariable 注解时,我们只需要指定一个参数的名称和URL中的参数值进行绑定,如下实例代码:
@GetMapping(value = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<UserRespVO> selectById(@PathVariable Integer id){
复制代码
{id}和方法上的参数id绑定。
@RequestParam注解的作用与@PathVariable 注解类似,也是用于获取请求中的参数,但是它面向类似 url?id=XXX 这种路径形式。
该注解的定义如下代码所示,相较 @PathVariable 注解,它只是多了一个设置默认值的 defaultValue 属性。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
复制代码
@RequestBody 注解用来处理 content-type 为 application/json 类型时的编码内容,通过 @RequestBody 注解可以将请求体中的 JSON 字符串绑定到相应的 JavaBean 上。
如下代码所示就是一个使用 @RequestBody 注解来控制输入的场景。
@PostMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
public R<Boolean> insert(@RequestBody UserReqVO userReqVO){
复制代码
小结
如何通过Spring Boot构建RESTful Web服务的内容就这么多,我们首先介绍对RESUful理解,然后通过一个简单的入门讲解了如何通过Spring Boot来构建一个标准的RESTful API。并且在入门教程中,我们体验到了Spring Boot提供的强大的注解,帮我们很简单就实现了该功能。后面我们对Spring Boot提供的注解进行了总结汇总,详细介绍了每个注解的具体功能和使用方式。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。
此外,关注公众号:黑色的灯塔,专注Java后端技术分享,涵盖Spring,Spring Boot,SpringCloud,Docker,Kubernetes中间件等技术。