基于springboot创建RESTful风格接口
RESTful API风格
特点:
- URL描述资源
- 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果
- 使用json交互数据
- RESTful只是一种风格,并不是强制的标准
一、查询请求
1.编写单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
//查询
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("username", "jojo")
.param("age", "18")
.param("ageTo", "60")
.param("xxx", "yyy")
// .param("size", "15")
// .param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回
System.out.println(result);//[{"username":null},{"username":null},{"username":null}]
}
}
2.使用注解声明RestfulAPI
常用注解
@RestController 标明此Controller提供RestAPI
@RequestMapping及其变体。映射http请求url到java方法
@RequestParam 映射请求参数到java方法的参数
@PageableDefault 指定分页参数默认值
@PathVariable 映射url片段到java方法的参数
在url声明中使用正则表达式
@JsonView控制json输出内容
查询请求:
@RestController
public class UserController {
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List<User> users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
}
①当前端传递的参数和后台自己定义的参数不一致时,可以使用name属性来标记:
(@RequestParam(name="username",required=false,defaultValue="hcx") String nickname
②前端不传参数时,使用默认值 defaultValue="hcx"
③当查询参数很多时,可以使用对象接收
④使用Pageable作为参数接收,前台可以传递分页相关参数
pageSize,pageNumber,sort;
也可以使用@PageableDefault指定默认的参数值。
@PageableDefault(page=2,size=17,sort="username,asc")
//查询第二页,查询17条,按照用户名升序排列
3.jsonPath表达式书写
github链接:https://github.com/json-path/JsonPath
二、编写用户详情服务
@PathVariable 映射url片段到java方法的参数
在url声明中使用正则表达式
@JsonView控制json输出内容
单元测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
//获取用户详情
@Test
public void whenGetInfoSuccess() throws Exception {
String result = mockMvc.perform(get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result); //{"username":"tom","password":null}
}
后台代码:
@RestController
@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了
public class UserController {
@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom");
return user;
}
当希望对传递进来的参数作一些限制时,可以使用正则表达式:
//测试提交错误信息
@Test
public void whenGetInfoFail() throws Exception {
mockMvc.perform(get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is4xxClientError());
}
后台代码:
@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,使用正则表达式
@JsonView(User.UserDetailView.class)
public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom");
return user;
}
使用@JsonView控制json输出内容
1.场景:在以上两个方法中,查询集合和查询用户详细信息时,期望查询用户集合时不返回密码给前端,而在查询单个用户信息时才返回。
2.使用步骤:
①使用接口来声明多个视图
②在值对象的get方法上指定视图
③在Controller方法上指定视图
在user实体中操作:
package com.hcx.web.dto;
import java.util.Date;
import javax.validation.constraints.Past;
import org.hibernate.validator.constraints.NotBlank;
import com.fasterxml.jackson.annotation.JsonView;
import com.hcx.validator.MyConstraint;
public class User {
public interface UserSimpleView{};
//有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来
public interface UserDetailView extends UserSimpleView{};
@MyConstraint(message="这是一个测试")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
private String id;
@Past(message="生日必须是过去的时间")
private Date birthday;
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@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;
}
}
在具体的方法中操作Controller:
@GetMapping
@JsonView(User.UserSimpleView.class)
public List<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List<User> users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,就需要使用正则表达式
@JsonView(User.UserDetailView.class)
public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom");
return user;
}
单元测试:
//查询
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("username", "jojo")
.param("age", "18")
.param("ageTo", "60")
.param("xxx", "yyy")
//.param("size", "15")
//.param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回
System.out.println(result);//[{"username":null},{"username":null},{"username":null}]
}
//获取用户详情
@Test
public void whenGetInfoSuccess() throws Exception {
String result = mockMvc.perform(get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result); //{"username":"tom","password":null}
}
代码重构:
1.@RequestMapping(value="/user",method=RequestMethod.GET)
替换成:
@GetMapping("/user")
2.在每个url中都重复声明了/user,此时就可以提到类中声明
@RestController
@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了
public class UserController {
@GetMapping
@JsonView(User.UserSimpleView.class)
public List<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List<User> users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
@GetMapping("/{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom");
return user;
}
}
三、处理创建请求
1.@RequestBody 映射请求体到java方法的参数
单元测试:
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());//1524741370816
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}
}
Controller:要使用@RequestBody才可以接收前端传递过来的参数
@PostMapping
public User create(@RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
user.setId("1");
return user;
}
2.日期类型参数的处理
对于日期的处理应该交给前端或app端,所以统一使用时间戳
前端或app端拿到时间戳,由他们自己决定转换成什么格式,而不是由后端转好直接给前端。
前端传递给后台直接传时间戳:
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());//1524741370816
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}(后台返回的时间戳)
}
Controller:
@PostMapping
public User create(@RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018(Date类型)
user.setId("1");
return user;
}
3.@Valid注解和BindingResult验证请求参数的合法性并处理校验结果
1.hibernate.validator中的常用验证注解:
①在实体中添加相应验证注解:
@NotBlank
private String password;
②后台接收参数时加@Valid注解
@PostMapping
public User create(@Valid @RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1");
return user;
}
2.BindingResult:带着错误信息进入方法体
@PostMapping
public User create(@Valid @RequestBody User user,BindingResult errors) {
if(errors.hasErrors()) {
//有错误返回true
errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
//may not be empty
}
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1");
return user;
}
四、处理用户信息修改
1.自定义消息
@Test
public void whenUpdateSuccess() throws Exception {
//一年之后的时间
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
System.out.println(date.getTime());//1524741370816
String content = "{\"id\":\"1\",\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}
}
Controller:
@PutMapping("/{id:\\d+}")
public User update(@Valid @RequestBody User user,BindingResult errors) {
/*if(errors.hasErrors()) {
//有错误返回true
errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
//may not be empty
}*/
if(errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> {
//FieldError fieldError = (FieldError)error;
//String message = fieldError.getField()+" "+error.getDefaultMessage();
System.out.println(error.getDefaultMessage());
//密码不能为空
//生日必须是过去的时间
//birthday must be in the past
//password may not be empty
}
);
}
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1");
return user;
}
实体:
public class User {
public interface UserSimpleView{};
//有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来
public interface UserDetailView extends UserSimpleView{};
@MyConstraint(message="这是一个测试")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
private String id;
@Past(message="生日必须是过去的时间")
private Date birthday;
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@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;
}
}
2.自定义校验注解
创建一个注解MyConstraint:
@Target({ElementType.METHOD,ElementType.FIELD})//可以标注在方法和字段上
@Retention(RetentionPolicy.RUNTIME)//运行时注解
@Constraint(validatedBy = MyConstraintValidator.class)//validatedBy :当前的注解需要使用什么类去校验,即校验逻辑
public @interface MyConstraint {
String message();//校验不通过要发送的信息
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
校验类:MyConstraintValidator:
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
/*ConstraintValidator<A, T>
参数一:验证的注解
参数二:验证的类型
ConstraintValidator<MyConstraint, String> 当前注解只能放在String类型字段上才会起作用
*/
@Autowired
private HelloService helloService;
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println("my validator init");
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
helloService.greeting("tom");
System.out.println(value);
return false;//false:校验失败;true:校验成功
}
}
五、处理删除
单元测试:
@Test
public void whenDeleteSuccess() throws Exception {
mockMvc.perform(delete("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
Controller:
@DeleteMapping("/{id:\\d+}")
public void delete(@PathVariable String id) {
System.out.println(id);
}
六、RESTful API错误处理
1.Spring Boot中默认的错误处理机制
Spring Boot中默认的错误处理机制,
对于浏览器是响应一个html错误页面,
对于app是返回错误状态码和一段json字符串
2.自定义异常处理
①针对浏览器发出的请求
在src/mian/resources文件夹下创建文件夹error编写错误页面
对应的错误状态码就会去到对应的页面
只会对浏览器发出的请求有作用,对app发出的请求,错误返回仍然是错误码和json字符串
②针对客户端app发出的请求
自定义异常:
package com.hcx.exception;
public class UserNotExistException extends RuntimeException{
private static final long serialVersionUID = -6112780192479692859L;
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public UserNotExistException(String id) {
super("user not exist");
this.id = id;
}
}
在Controller中抛自己定义的异常
//发生异常时,抛自己自定义的异常
@GetMapping("/{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User getInfo1(@PathVariable String id) {
throw new UserNotExistException(id);
}
默认情况下,springboot不读取id的信息
抛出异常时,进入该方法进行处理:
ControllerExceptionHandler:
@ControllerAdvice //只负责处理异常
public class ControllerExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, Object> handleUserNotExistException(UserNotExistException ex){
Map<String, Object> result = new HashMap<>();
//把需要的信息放到异常中
result.put("id", ex.getId());
result.put("message", ex.getMessage());
return result;
}
}