3、Spring Boot与Web开发:RESTful API设计、@RestController、@RequestMapping、参数接收与校验
好,咱们今天聊聊Web开发中最核心的一块——RESTful API。说实话,我见过太多项目,API设计得乱七八糟,前端和后端互相甩锅。其实只要掌握几个关键点,这事儿就能捋顺。
3.1 什么是RESTful?别把它想得太玄乎
RESTful说白了就是一种API的设计风格。它不是协议,也不是标准,就是一套约定。核心思想就一句话:把后端操作映射成对资源的增删改查。
举个例子。你要操作用户数据,传统做法可能是这样:
/getUser?id=1—— 查用户/createUser—— 创建用户/deleteUser?id=1—— 删用户
RESTful风格会怎么设计?
GET /users/1—— 查用户POST /users—— 创建用户DELETE /users/1—— 删用户
看出来没?资源是名词(users),操作是HTTP方法(GET/POST/PUT/DELETE)。我个人习惯把URL看作「名词路径」,把HTTP方法看作「动词」。这样设计出来的API,看一眼就知道在干什么。
核心原则:
- 使用名词复数表示资源集合:
/users、/orders - 使用HTTP方法表示操作:GET查、POST增、PUT全量改、PATCH部分改、DELETE删
- 资源间的关系用路径层级表示:
/users/1/orders - 查询参数用于过滤、排序、分页:
/users?page=1&size=20&sort=name
我的经验:别把动词塞进URL里。我见过有人写 /getAllUsers、/deleteUserById,这其实违背了RESTful的初衷。用HTTP方法区分操作,URL只描述资源。
3.2 @RestController —— 你的API入口
在Spring Boot里,写RESTful API最常用的就是@RestController。它其实是@Controller + @ResponseBody的组合。什么意思呢?就是每个方法的返回值,都会直接序列化成JSON(或XML)写入HTTP响应体。
我记得刚用Spring Boot那会儿,有人问我:为什么不用@Controller?我说可以啊,但你每个方法都得加@ResponseBody,多麻烦。用@RestController,省心。
@RestController
@RequestMapping("/api/users")
public class UserController {
// 这里所有方法都默认返回JSON
// 不需要在每个方法上加 @ResponseBody
}
注意:如果你某个方法需要返回视图(比如JSP页面),那就不能用@RestController了,得用@Controller。我曾经在一个纯前后端分离的项目里,不小心把返回视图的方法也写进了@RestController,结果前端拿到了一堆JSON字符串……嗯,排查了半天。
3.3 @RequestMapping 及其变体
@RequestMapping是Spring MVC中最基础的映射注解。它可以加在类上,也可以加在方法上。但说实话,我平时用得更多的是它的变体:
| 注解 | 等价于 | 用途 |
|---|---|---|
@GetMapping |
@RequestMapping(method = GET) |
查询资源 |
@PostMapping |
@RequestMapping(method = POST) |
创建资源 |
@PutMapping |
@RequestMapping(method = PUT) |
全量更新资源 |
@DeleteMapping |
@RequestMapping(method = DELETE) |
删除资源 |
@PatchMapping |
@RequestMapping(method = PATCH) |
部分更新资源 |
你想想看,用@GetMapping是不是比@RequestMapping(method = RequestMethod.GET)简洁多了?代码可读性也更好。
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<User> getAllUsers() {
// 查询所有用户
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// 根据ID查询用户
}
@PostMapping
public User createUser(@RequestBody User user) {
// 创建用户
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
// 全量更新用户
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
// 删除用户
}
}
我的习惯:类上统一用@RequestMapping定义基础路径,方法上用具体的Mapping注解。这样整个Controller的URL结构一目了然。
3.4 参数接收 —— 五花八门的方式
写API,免不了要接收前端传过来的参数。Spring Boot提供了好几种方式,我挑最常用的几个说说。
3.4.1 @PathVariable —— 路径参数
用于接收URL路径中的变量。比如/users/123中的123。
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// id = 123
}
3.4.2 @RequestParam —— 查询参数
用于接收URL问号后面的参数。比如/users?page=1&size=20。
@GetMapping
public List<User> getUsers(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
// page = 1, size = 20
}
3.4.3 @RequestBody —— 请求体参数
用于接收JSON格式的请求体。POST、PUT、PATCH请求常用。
@PostMapping
public User createUser(@RequestBody @Valid User user) {
// user 对象由JSON自动反序列化
}
3.4.4 @RequestHeader —— 请求头参数
用于接收HTTP头中的信息,比如Token、语言偏好等。
@GetMapping("/me")
public User getCurrentUser(@RequestHeader("Authorization") String token) {
// 从token中解析用户信息
}
避坑指南:我曾经遇到过一个bug,前端传的参数名是user_name,后端用@RequestParam("userName")接收,结果一直拿不到值。后来发现是参数名没对上。记住:@RequestParam默认要求参数名完全匹配。如果前端传的是下划线风格,后端可以用@RequestParam("user_name"),或者配置Spring Boot的spring.jackson.property-naming-strategy来统一转换。
3.5 参数校验 —— 别让脏数据进系统
说实话,我最怕的就是不做参数校验的项目。前端传个空字符串、负数、超长文本,后端直接往数据库里塞……后果可想而知。
Spring Boot提供了@Valid + Bean Validation(JSR-380)来做参数校验。你只需要在实体类的字段上加注解,然后在Controller方法参数上加@Valid就行。
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20个字符")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
// getter/setter 省略
}
Controller里这样用:
@PostMapping
public User createUser(@RequestBody @Valid User user) {
// 如果校验失败,Spring会自动返回400 Bad Request
// 并附带详细的错误信息
return userService.save(user);
}
注意:默认的校验失败返回信息是英文的,而且格式不太友好。我建议你全局处理一下校验异常,返回统一格式的中文错误信息。后面我会专门讲全局异常处理,这里先留个悬念。
常用校验注解一览
| 注解 | 作用 |
|---|---|
@NotBlank |
字符串不能为null,且去掉前后空格后长度大于0 |
@NotEmpty |
集合、数组、字符串不能为null,且长度大于0 |
@NotNull |
任何类型都不能为null |
@Size |
限制字符串、集合、数组的长度范围 |
@Min / @Max |
限制数字的最小/最大值 |
@Email |
校验邮箱格式 |
@Pattern |
正则表达式校验 |
我的建议:别把所有校验都堆在实体类上。对于特定场景的校验(比如创建用户时密码不能为空,但更新用户时密码可以为空),可以用分组校验(@Validated + groups)。或者干脆在Service层做二次校验。我一般会在Controller层做基础格式校验,在Service层做业务逻辑校验,两层把关,基本不会出问题。
3.6 一个完整的例子
说了这么多,咱们来个完整的例子串一下。
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 路径参数:/api/users/123
return userService.findById(id);
}
@PostMapping
public User createUser(@RequestBody @Valid User user) {
// 请求体:{"name":"张三","age":25,"email":"zhangsan@example.com"}
return userService.save(user);
}
@GetMapping
public List<User> searchUsers(@RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") int page) {
// 查询参数:/api/users?name=张&page=1
return userService.search(name, page);
}
}
你看,一个完整的RESTful API就出来了。路径清晰,方法明确,参数校验也到位。前端拿到这个API文档,基本不用问后端怎么调。
嗯,这一章的内容就到这里。下一章咱们聊聊全局异常处理和统一响应格式——这可是让API更「专业」的关键一步。