RESTful
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。本篇博客主要讲述使用Spring MVC
开发RESTful
风格的API
。
一、传统API和RESTful API
传统的API
和RESTful API
如下表所示:
行为 | 传统API | RESTful API | 方法 |
---|---|---|---|
查询 | /user/query?name=lemon |
/user?name=lemon |
GET |
详情 | /user/getInfo?id=1 |
/user/1 |
GET |
创建 | /user/create?name=lemon |
/user |
POST |
修改 | /user/update?id=1&name=tom |
/user/1 |
POST |
删除 | /user/delete?id=1 |
/user/1 |
GET |
RESTful
风格的API
有如下几个特点:
-
使用
URL
描述资源 -
使用
HTTP
方法描述行为,使用HTTP
状态码来表示不同的结果 -
使用
JSON
进行数据交互 -
RESTful
只是一种风格,并不是一种强制的标准
二、常用注解介绍
这里介绍几个常用的注解:
-
@RestController
标明此Controller
提供RESTful API
-
@RequestMapping
及其变体(@GetMapping
、PostMapping
等),映射HTTP
请求到Java
方法 -
@RequestParam
映射请求参数到Java
方法的参数 -
@PathVariable
映射URL
片段到Java
方法的参数 -
@PageableDefault
指定默认分页参数 -
@JsonView
按照指定方式序列化Java
对象
代码案例:这里有User
和UserController
以及UserControllerTest
三个类,其中UserControllerTest
的四个测试方法分别对应UserController
类中的四个方法。
User
类
package com.lemon.security.web.dto;
import lombok.Data;
/**
* @author lemon
* @date 2018/3/22 下午3:40
*/
@Data
public class User {
private String username;
private String password;
}
UserController
类
package com.lemon.security.web.controller;
import com.lemon.security.web.dto.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author lemon
* @date 2018/3/22 下午3:39
*/
@RestController
public class UserController {
@RequestMapping(value = "/user1", method = RequestMethod.GET)
public List<User> query1() {
return generateUsers();
}
@GetMapping("/user2")
public List<User> query2(@RequestParam String username) {
System.out.println(username);
return generateUsers();
}
@GetMapping("/user3/{username}")
public List<User> query3(@PathVariable String username) {
System.out.println(username);
return generateUsers();
}
@GetMapping("/user4")
public List<User> query4(@PageableDefault(page = 1, size = 2, sort = "username") Pageable pageable) {
System.out.println(pageable.getPageNumber());
System.out.println(pageable.getPageSize());
System.out.println(pageable.getSort());
return generateUsers();
}
private List<User> generateUsers() {
List<User> users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
}
UserControllerTest
类
package com.lemon.security.web;
import com.lemon.security.web.application.MainApplication;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* @author lemon
* @date 2018/3/22 下午3:14
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class UserControllerTest {
// 注入一个web应用环境(容器)
@Autowired
private WebApplicationContext webApplicationContext;
// MVC环境对象
private MockMvc mockMvc;
@Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void query1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
@Test
public void query2() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user2")
.param("username", "lemon")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
@Test
public void query3() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user3/lemon")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
@Test
public void query4() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user4")
.param("size", "3")
.param("page", "1")
.param("sort", "username,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
}
第三个类,也就是UserControllerTest
是RESTful API
的测试类,现在对其进行简单介绍:
由于RESSTful
风格的API
不能通过浏览器地址栏来进行测试,因为地址栏发送的请求都是GET
类型的,而RESTful API
正是通过请求方法来判断请求行为是查询、修改、删除、增加中的哪一种的,所以测试RESSTful
风格的API
都是通过编码来进行测试的。
-
通过
@Autowired WebApplicationContext webApplicationContext
:注入web
环境的ApplicationContext
容器; -
然后通过
MockMvcBuilders.webAppContextSetup(webApplicationContext).build()
创建一个MockMvc
的MVC
环境进行测试; -
MockMvcRequestBuilders.get()
方法是发送一个GET
请求,param()
是设置请求参数,contentType()
是我设置内容类型(JSON
格式),andExpect()
方法是希望得到什么样的测试结果,MockMvcResultMatchers()
是返回结果的匹配是否正确。jsonPath()
方法是解析返回的JSON
数据,关于它的介绍可以在github
上找到。
运行上面的四个测试方法都可以通过测试。对于@PathVariable
再写一个测试案例:
Controller
方法:
@GetMapping("/getInfo/{id:\\d+}")
public User getInfo(@PathVariable String id) {
System.out.println("查询的对象ID为:".concat(id));
User user = new User();
user.setUsername("lemon");
return user;
}
上面的方法URL
片段进行了正则表达式的验证,ID
只能是数字。
- 测试方法:
@Test
public void getInfo() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/getInfo/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
}
接下来详细介绍@JsonView这个注解的使用。
@JsonView
的使用步骤
-
使用接口来声明多个视图
-
在值对象的
get
方法上指定视图 -
在
Controller
方法上指定视图
对于上面的步骤,进行如下解释如下:
一般对Java
对象进行序列化Json
的时候,会考虑到只序列化部分字段,那么就可以使用@JsonView
这个注解。在这里使用User
实体类进行举例,首先,在实体类上定义两个接口,第一个接口是简单视图(UserSimpleView
),表示之序列化username
这个字段,而第二个接口是详情视图(UserDetailView extends UserSimpleView
),表示不仅序列化username
字段,还序列化password
字段。然后使用@JsonView
注解将两个视图绑定到对应的字段的get
方法上面,由于UserDetailView
继承了UserSimpleView
这个视图,所以在Controller
方法上使用UserDetailView
视图的时候,会同时序列化两个字段,而使用UserSimpleView
的时候仅仅只会序列化username
这一个字段。下面进行代码展示:
- User类
package com.lemon.security.web.dto;
import com.fasterxml.jackson.annotation.JsonView;
/**
* @author lemon
* @date 2018/3/22 下午3:40
*/
public class User {
public interface UserSimpleView {}
public interface UserDetailView extends UserSimpleView {}
private String username;
private String password;
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- UserController的两个方法
@GetMapping("/getSimpleUser")
@JsonView(User.UserSimpleView.class)
public User getSimpleUser() {
User user = new User();
user.setUsername("lemon");
user.setPassword("123456");
return user;
}
@GetMapping("/getDetailUser")
@JsonView(User.UserDetailView.class)
public User getDetailUser() {
User user = new User();
user.setUsername("lemon");
user.setPassword("123456");
return user;
}
从上面的步骤分析可知,第一个方法返回的user对象在序列化为json的时候,只会序列化username字段,而第二个方法则会同时序列化两个字段。
- 两个测试方法
@Test
public void getSimpleUser() throws Exception {
String result = mockMvc.perform(MockMvcRequestBuilders.get("/getSimpleUser")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void getDetailUser() throws Exception {
String result = mockMvc.perform(MockMvcRequestBuilders.get("/getDetailUser")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
两个方法打印的结果分别为:
{"username":"lemon"}
{"username":"lemon","password":"123456"}
三、编写RESTful API
1、用户详情请求(GET)
对于RESTful API
,一般都不再使用传统的参数传递,而是使用资源映射的方式,也就是使用@PathVariable
,为了保持文档的完整性,这里再次使用上面已经举过的案例:
Controller
方法:
@GetMapping("/getInfo/{id:\\d+}")
public User getInfo(@PathVariable String id) {
System.out.println("查询的对象ID为:".concat(id));
User user = new User();
user.setUsername("lemon");
return user;
}
上面的方法URL
片段进行了正则表达式的验证,ID
只能是数字。
- 测试方法:
@Test
public void getInfo() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/getInfo/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
}
2、用户创建请求(POST)
这里主要介绍三个知识点:
-
@RequestBody
映射请求体到Java
方法参数 -
@Valid
注解和BindingResult
验证请求参数的合法性并处理校验结果 -
@RequestBody
是将前台传递过来的JSON
字符串转换成Java
对象,
1)第一个知识点的案例,将JSON
字符串映射到Java
对象中
在之前的User
类上加上一个id
字段,然后进行下面的测试。
Controller
方法:用户创建的方法
@PostMapping("/user1")
public User create1(@RequestBody User user) {
System.out.println(ReflectionToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE));
user.setId(1);
return user;
}
测试方法:测试创建用户方法
@Test
public void create1() throws Exception {
String content = "{\"username\":\"lemon\",\"password\":\"123456\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/user1")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));
}
测试方法传递过来的数据是一个JSON
字符串,正是@RequestBody
注解将JSON
字符串转化成为Java
对象。
2)第二个知识点的案例,@Valid
注解和BindingResult
验证请求参数的合法性并处理校验结果
当使用Java
类来接受参数的是,往往需要对参数进行校验,而校验一般都是使用Hibernate
提供的校验器来进行校验,在Java
实体类的字段上,我们常常加上@NotBlank
、@NotNull
、@Null
、@Min
、@Max
、@NotEmpty
等注解进行校验规则定义,然后在Controller
方法参数前加上@Valid
注解来进行校验,校验的错误结果存储在BindingResult
对象内。这里我向后台传递一个JSON
字符串,人为使得username
和password
两个字段为null
。这里仅仅简单介绍表单验证的注解,下一篇博客将重点介绍。接下来请看案例:
- User类字段
private Integer id;
@NotEmpty(message = "用户名不能为空")
private String username;
@NotEmpty(message = "密码不能为空")
private String password;
private Date birthday;
- UserController的create2()方法
@PostMapping("/user2")
public User create2(@Valid @RequestBody User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
}
System.out.println(ReflectionToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE));
user.setId(2);
return user;
}
- 测试方法
@Test
public void create2() throws Exception {
String content = "{\"username\":null,\"password\":null}";
mockMvc.perform(MockMvcRequestBuilders.post("/user2")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(2));
}
运行结果为:
用户名不能为空
密码不能为空
com.lemon.security.web.dto.User@58d79479[
id=<null>
username=<null>
password=<null>
birthday=<null>
]
3、用户修改和删除请求(PUT、DELETE)
由于RESTful
风格的API
是基于方法来进行区分的,所以设计到数据的修改和删除使用的方法是PUT
和DELETE
,接下来使用案例的方式介绍修改和删除API
的开发。
- 测试方法:
@Test
public void update() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
}
@Test
public void delete() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk());
}
Controller
方法:
@PutMapping("/user/{id:\\d+}")
public User update(@PathVariable Integer id) {
User user = new User();
user.setId(id);
System.out.println("模拟修改");
user.setUsername("lemon");
return user;
}
@DeleteMapping("/user/{id:\\d+}")
public void delete(@PathVariable Integer id) {
System.out.println("模拟修改,修改ID:".concat(String.valueOf(id)));
}
回顾一下RESTful风格的API,都是使用URL描述资源,使用请求方法来区别不同的API。这极大程度地简化了API开发的流程,推荐使用。
Spring Security技术栈开发企业级认证与授权系列文章列表:
Spring Security技术栈学习笔记(一)环境搭建
Spring Security技术栈学习笔记(二)RESTful API详解
Spring Security技术栈学习笔记(三)表单校验以及自定义校验注解开发
Spring Security技术栈学习笔记(四)RESTful API服务异常处理
Spring Security技术栈学习笔记(五)使用Filter、Interceptor和AOP拦截REST服务
Spring Security技术栈学习笔记(六)使用REST方式处理文件服务
Spring Security技术栈学习笔记(七)使用Swagger自动生成API文档
Spring Security技术栈学习笔记(八)Spring Security的基本运行原理与个性化登录实现
Spring Security技术栈学习笔记(九)开发图形验证码接口
Spring Security技术栈学习笔记(十)开发记住我功能
Spring Security技术栈学习笔记(十一)开发短信验证码登录
Spring Security技术栈学习笔记(十二)将短信验证码验证方式集成到Spring Security
Spring Security技术栈学习笔记(十三)Spring Social集成第三方登录验证开发流程介绍
Spring Security技术栈学习笔记(十四)使用Spring Social集成QQ登录验证方式
Spring Security技术栈学习笔记(十五)解决Spring Social集成QQ登录后的注册问题
Spring Security技术栈学习笔记(十六)使用Spring Social集成微信登录验证方式
示例代码下载地址:
项目已经上传到码云,欢迎下载,内容所在文件夹为
chapter002
。
更多干货分享,欢迎关注我的微信公众号:爪哇论剑(微信号:itlemon)
转载:https://blog.csdn.net/Lammonpeter/article/details/79655671