SpringBoot 2.x系列之搭建RESTful 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服务我们来个简单入门。

简单入门

实例代码对应的仓库地址:

github.com/dragon8844/…

gitee.com/drag0n/spri…

引入依赖

在pom.xml的文件中引入相关依赖:

            org.springframework.boot        spring-boot-starter-web                    org.projectlombok        lombok        true                    org.springframework.boot        spring-boot-starter-test        test                                    org.junit.jupiter                junit-jupiter-api                                    junit        junit        test    复制代码

添加配置

在resources目录下创建应用的配置文件application.yml,添加如下配置内容:

# 用来修改web服务的端口server:  port: 8080复制代码

配置文件中配置web服务的端口,如果不配置,默认使用也是8080端口。

编写代码

定义User请求VO

@Datapublic class UserReqVO implements Serializable {    private String username;    private String password;}复制代码

定义User响应VO

@Datapublic class UserRespVO implements Serializable {    private Integer id;    private String username;    private Date createTime;}复制代码

编写Controller用来暴露API

@RestController@RequestMapping(value = "v1")@Slf4jpublic class UserController {    @PostMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)    public R 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 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 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 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@Slf4jpublic 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 类如下代码所示:

@RestControllerpublic 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@Mappingpublic @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)@Documentedpublic @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 selectById(@PathVariable Integer id){复制代码

{id}和方法上的参数id绑定。

@RequestParam注解的作用与@PathVariable 注解类似,也是用于获取请求中的参数,但是它面向类似 url?id=XXX 这种路径形式。

该注解的定义如下代码所示,相较 @PathVariable 注解,它只是多了一个设置默认值的 defaultValue 属性。

@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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 insert(@RequestBody UserReqVO userReqVO){复制代码

小结

如何通过Spring Boot构建RESTful Web服务的内容就这么多,我们首先介绍对RESUful理解,然后通过一个简单的入门讲解了如何通过Spring Boot来构建一个标准的RESTful API。并且在入门教程中,我们体验到了Spring Boot提供的强大的注解,帮我们很简单就实现了该功能。后面我们对Spring Boot提供的注解进行了总结汇总,详细介绍了每个注解的具体功能和使用方式。

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章