请求映射与参数绑定
一、URL 路径映射
@RequestMapping 基本用法
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping("/list")
public String list() {
return "user-list";
}
// 访问: /api/users/list
}@GetMapping 等快捷注解
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/list") // GET /api/users/list
public List<User> list() { return users; }
@GetMapping("/{id}") // GET /api/users/1
public User get(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping // POST /api/users
public User create(@RequestBody @Valid User user) {
return userService.save(user);
}
@PutMapping("/{id}") // PUT /api/users/1
public User update(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
@DeleteMapping("/{id}") // DELETE /api/users/1
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}@PathVariable 路径变量
java
@GetMapping("/users/{id}/posts/{postId}")
public String getPost(@PathVariable Long id, @PathVariable Long postId) {
return "user " + id + "'s post " + postId;
}
// 指定参数名
@GetMapping("/users/{userId}")
public User getUser(@PathVariable("userId") Long id) {
return userService.findById(id);
}
// @PathVariable 是必须的吗?
// 默认必须,可以通过 required = false 设置可选
@GetMapping("/users/{id:\\d+}") // 正则约束,只能是数字
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}二、请求参数绑定
@RequestParam 请求参数
java
// GET /api/users?page=1&size=10&sort=name,desc
@GetMapping("/users")
public Page<User> list(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String sort
) {
return userService.findAll(page, size, sort);
}
// 复杂参数:数组、List
@GetMapping("/search")
public List<User> search(
@RequestParam List<String> names, // ?names=张三&names=李四
@RequestParam String[] roles // ?roles=admin&roles=user
) {
return userService.findByNamesAndRoles(names, roles);
}
// 文件上传
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
return file.getOriginalFilename();
}@RequestHeader 请求头
java
@GetMapping("/info")
public String getInfo(
@RequestHeader("Authorization") String token,
@RequestHeader(value = "Accept", defaultValue = "*/*") String accept,
@RequestHeader Map<String, String> headers // 所有请求头
) {
return token;
}@CookieValue Cookie
java
@GetMapping("/analytics")
public String analytics(
@CookieValue("JSESSIONID") String sessionId,
@CookieValue(value = "theme", defaultValue = "light") String theme
) {
return "Session: " + sessionId;
}@RequestAttribute 请求属性
java
// 获取请求转发前设置的属性
@GetMapping("/attr")
public String attr(@RequestAttribute("user") User user) {
return user.getName();
}三、@RequestBody 与 @ResponseBody
@RequestBody 接收请求体
java
// JSON 请求体 → Java 对象
@PostMapping("/users")
public User create(@RequestBody User user) {
return userService.save(user);
}
// JSON → Map
@PostMapping("/data")
public Map<String, Object> create(@RequestBody Map<String, Object> data) {
return data;
}
// JSON → List
@PostMapping("/users/batch")
public List<User> batchCreate(@RequestBody List<User> users) {
return userService.saveAll(users);
}@ResponseBody 返回响应体
java
// 返回 Java 对象 → JSON
@ResponseBody
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// @RestController 相当于类上标注了 @ResponseBody
@RestController
@RequestMapping("/api/users")
public class UserController {
// 所有方法都相当于标注了 @ResponseBody
}请求体转换
HTTP 请求体(JSON)
↓
HttpMessageConverter.read()
↓
User 对象
↓
Controller 方法参数
响应返回值(User)
↓
HttpMessageConverter.write()
↓
HTTP 响应体(JSON)内置 HttpMessageConverter
| Converter | 用途 |
|---|---|
| StringHttpMessageConverter | String ↔ text/plain |
| MappingJackson2HttpMessageConverter | JSON ↔ Object |
| MappingJackson2XmlHttpMessageConverter | XML ↔ Object |
| FormHttpMessageConverter | Form ↔ MultiValueMap |
| ByteArrayHttpMessageConverter | byte[] ↔ application/octet-stream |
四、复杂对象绑定
嵌套对象
java
public class User {
private String name;
private Address address; // 嵌套对象
// getters/setters
}
public class Address {
private String province;
private String city;
private String street;
// getters/setters
}yaml
# 请求参数格式
user.name=张三
user.address.province=广东
user.address.city=深圳
user.address.street=科技园java
@PostMapping("/user")
public User create(User user) {
return userService.save(user);
}嵌套 List
java
public class Company {
private String name;
private List<Department> departments;
// getters/setters
}
public class Department {
private String name;
private List<Employee> employees;
// getters/setters
}yaml
# 请求参数格式
company.name=总公司
company.departments[0].name=技术部
company.departments[0].employees[0].name=张三
company.departments[0].employees[1].name=李四
company.departments[1].name=财务部五、数据校验
@Valid 与 @Validated
java
// @Valid:JSR-303 标准
@PostMapping("/users")
public User create(@Valid @RequestBody User user) {
return userService.save(user);
}
// @Validated:Spring 的分组校验
@PostMapping("/users")
public User create(@Validated({CreateGroup.class}) @RequestBody User user) {
return userService.save(user);
}校验注解
java
public class User {
@NotNull(message = "姓名不能为空")
@Size(min = 2, max = 20, message = "姓名长度2-20")
private String name;
@NotNull
@Min(0)
@Max(150)
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@DecimalMin(value = "0.01", message = "金额最小0.01")
private BigDecimal salary;
@Past(message = "生日必须是过去的时间")
private LocalDate birthday;
@Future(message = "截止日期必须是未来时间")
private LocalDate deadline;
@AssertTrue(message = "必须同意协议")
private boolean agreed;
// 分组
@NotNull(groups = {UpdateGroup.class})
private Long id;
}分组校验
java
// 定义分组
public interface CreateGroup { }
public interface UpdateGroup { }
// 使用分组
@PostMapping("/create")
public User create(@Validated(CreateGroup.class) @RequestBody User user) {
return userService.save(user);
}
@PutMapping("/update")
public User update(@Validated(UpdateGroup.class) @RequestBody User user) {
return userService.update(user);
}校验异常处理
java
@ControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> handleValidException(MethodArgumentNotValidException e) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
List<String> errors = new ArrayList<>();
for (FieldError error : e.getBindingResult().getFieldErrors()) {
errors.add(error.getField() + ": " + error.getDefaultMessage());
}
result.put("errors", errors);
return result;
}
}六、自定义参数转换器
Converter<S, T>
java
// String → LocalDate
@Component
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, formatter);
}
}
// 注册
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLocalDateConverter());
}
}@InitBinder 数据绑定
java
@Controller
public class UserController {
// 局部绑定器,只对该 Controller 生效
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("id"); // 禁止绑定 id
binder.setRequiredFields("name", "email"); // 必填字段
}
}FormattingConversionService
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 日期格式化
registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
// 数字格式化
registry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
}
}
// 使用
public class User {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
@NumberFormat(pattern = "#,###.##")
private BigDecimal salary;
}七、文件上传
单文件上传
java
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new RuntimeException("文件为空");
}
String filename = file.getOriginalFilename();
String path = "uploads/" + UUID.randomUUID() + "-" + filename;
file.transferTo(new File(path));
return path;
}多文件上传
java
@PostMapping("/upload/multiple")
public List<String> uploadMultiple(
@RequestParam("files") MultipartFile[] files
) {
return Arrays.stream(files)
.map(file -> {
// 处理每个文件
return file.getOriginalFilename();
})
.collect(Collectors.toList());
}
// 或使用 List
@PostMapping("/upload/list")
public List<String> uploadList(
@RequestParam("files") List<MultipartFile> files
) {
return files.stream()
.map(MultipartFile::getOriginalFilename)
.collect(Collectors.toList());
}配置上传
yaml
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB
file-size-threshold: 2KB八、@ModelAttribute
绑定请求参数到 Model
java
@Controller
public class UserController {
// 在所有方法执行前先执行
@ModelAttribute
public void populateModel(Model model) {
model.addAttribute("common", "共享数据");
}
// 绑定请求参数到对象
@GetMapping("/user")
public String getUser(@ModelAttribute("user") User user) {
return "user-detail";
}
}用于方法参数
java
@PostMapping("/user")
public String create(@ModelAttribute User user) {
// 相当于 @RequestBody + 校验
userService.save(user);
return "redirect:/users";
}九、请求参数汇总
| 注解 | 用途 | 来源 |
|---|---|---|
| @RequestParam | 请求参数 | Spring |
| @PathVariable | 路径变量 | Spring |
| @RequestBody | 请求体 | Spring |
| @ResponseBody | 响应体 | Spring |
| @RequestHeader | 请求头 | Spring |
| @CookieValue | Cookie | Spring |
| @RequestAttribute | 请求属性 | Spring |
| @SessionAttribute | Session属性 | Spring |
| @ModelAttribute | 模型属性 | Spring |
| @Valid | 启动校验 | JSR-303 |
十、面试高频问题
Q1: @RequestParam 和 @PathVariable 区别?
@PathVariable:URL 路径的一部分,如/users/{id}@RequestParam:URL 查询参数,如/users?id=1
Q2: @RequestBody 如何处理 JSON?
- Content-Type 必须是
application/json - Spring 使用
MappingJackson2HttpMessageConverter反序列化 - 需要 Jackson 在类路径
Q3: GET 请求能带请求体吗?
- 技术上可以,但不符合 HTTP 规范
- 部分服务器可能忽略 GET 请求体
- 建议用
@RequestParam或@PathVariable
Q4: 如何实现参数校验?
@Valid或@Validated标注参数- 对象属性加校验注解
- 异常处理器捕获
MethodArgumentNotValidException
Q5: 文件上传大小限制?
yaml
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB十一、下一章预告
下一章我们将学习 Spring MVC 响应处理与视图解析:
- 返回视图 vs 返回 JSON
- 视图解析器配置
- Thymeleaf 模板引擎
- 重定向与转发