使用REST


使用Spring MVC开发Web应用程序的主要工作就是编写Controller逻辑。在Web应用中,除了需要使用MVC给用户显示页面外,还有一类API接口,我们称之为REST,通常输入输出都是JSON,便于第三方调用或者使用页面JavaScript与之交互。

直接在Controller中处理JSON是可以的,因为Spring MVC的@GetMapping@PostMapping都支持指定输入和输出的格式。如果我们想接收JSON,输出JSON,那么可以这样写:

  1. @PostMapping(value = "/rest",
  2. consumes = "application/json;charset=UTF-8",
  3. produces = "application/json;charset=UTF-8")
  4. @ResponseBody
  5. public String rest(@RequestBody User user) {
  6. return "{\"restSupport\":true}";
  7. }

对应的Maven工程需要加入Jackson这个依赖:com.fasterxml.jackson.core:jackson-databind:2.14.0

注意到@PostMapping使用consumes声明能接收的类型,使用produces声明输出的类型,并且额外加了@ResponseBody表示返回的String无需额外处理,直接作为输出内容写入HttpServletResponse。输入的JSON则根据注解@RequestBody直接被Spring反序列化为User这个JavaBean。

使用curl命令测试一下:

  1. $ curl -v -H "Content-Type: application/json" -d '{"email":"bob@example.com"}' http://localhost:8080/rest
  2. > POST /rest HTTP/1.1
  3. > Host: localhost:8080
  4. > User-Agent: curl/7.64.1
  5. > Accept: */*
  6. > Content-Type: application/json
  7. > Content-Length: 27
  8. >
  9. < HTTP/1.1 200
  10. < Content-Type: application/json;charset=utf-8
  11. < Content-Length: 20
  12. < Date: Sun, 10 May 2020 09:56:01 GMT
  13. <
  14. {"restSupport":true}

输出正是我们写入的字符串。

直接用Spring的Controller配合一大堆注解写REST太麻烦了,因此,Spring还额外提供了一个@RestController注解,使用@RestController替代@Controller后,每个方法自动变成API接口方法。我们还是以实际代码举例,编写ApiController如下:

  1. @RestController
  2. @RequestMapping("/api")
  3. public class ApiController {
  4. @Autowired
  5. UserService userService;
  6. @GetMapping("/users")
  7. public List<User> users() {
  8. return userService.getUsers();
  9. }
  10. @GetMapping("/users/{id}")
  11. public User user(@PathVariable("id") long id) {
  12. return userService.getUserById(id);
  13. }
  14. @PostMapping("/signin")
  15. public Map<String, Object> signin(@RequestBody SignInRequest signinRequest) {
  16. try {
  17. User user = userService.signin(signinRequest.email, signinRequest.password);
  18. return Map.of("user", user);
  19. } catch (Exception e) {
  20. return Map.of("error", "SIGNIN_FAILED", "message", e.getMessage());
  21. }
  22. }
  23. public static class SignInRequest {
  24. public String email;
  25. public String password;
  26. }
  27. }

编写REST接口只需要定义@RestController,然后,每个方法都是一个API接口,输入和输出只要能被Jackson序列化或反序列化为JSON就没有问题。我们用浏览器测试GET请求,可直接显示JSON响应:

user-api

要测试POST请求,可以用curl命令:

  1. $ curl -v -H "Content-Type: application/json" -d '{"email":"bob@example.com","password":"bob123"}' http://localhost:8080/api/signin
  2. > POST /api/signin HTTP/1.1
  3. > Host: localhost:8080
  4. > User-Agent: curl/7.64.1
  5. > Accept: */*
  6. > Content-Type: application/json
  7. > Content-Length: 47
  8. >
  9. < HTTP/1.1 200
  10. < Content-Type: application/json
  11. < Transfer-Encoding: chunked
  12. < Date: Sun, 10 May 2020 08:14:13 GMT
  13. <
  14. {"user":{"id":1,"email":"bob@example.com","password":"bob123","name":"Bob",...

注意观察上述JSON的输出,User能被正确地序列化为JSON,但暴露了password属性,这是我们不期望的。要避免输出password属性,可以把User复制到另一个UserBean对象,该对象只持有必要的属性,但这样做比较繁琐。另一种简单的方法是直接在Userpassword属性定义处加上@JsonIgnore表示完全忽略该属性:

  1. public class User {
  2. ...
  3. @JsonIgnore
  4. public String getPassword() {
  5. return password;
  6. }
  7. ...
  8. }

但是这样一来,如果写一个register(User user)方法,那么该方法的User对象也拿不到注册时用户传入的密码了。如果要允许输入password,但不允许输出password,即在JSON序列化和反序列化时,允许写属性,禁用读属性,可以更精细地控制如下:

  1. public class User {
  2. ...
  3. @JsonProperty(access = Access.WRITE_ONLY)
  4. public String getPassword() {
  5. return password;
  6. }
  7. ...
  8. }

同样的,可以使用@JsonProperty(access = Access.READ_ONLY)允许输出,不允许输入。

练习

使用REST - 图2下载练习:使用REST实现API (推荐使用IDE练习插件快速下载)

小结

使用@RestController可以方便地编写REST服务,Spring默认使用JSON作为输入和输出。

要控制序列化和反序列化,可以使用Jackson提供的@JsonIgnore@JsonProperty注解。

读后有收获可以支付宝请作者喝咖啡:

使用REST - 图3