Skip to content

请求映射与参数绑定

一、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;
}
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用途
StringHttpMessageConverterString ↔ text/plain
MappingJackson2HttpMessageConverterJSON ↔ Object
MappingJackson2XmlHttpMessageConverterXML ↔ Object
FormHttpMessageConverterForm ↔ MultiValueMap
ByteArrayHttpMessageConverterbyte[] ↔ 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
@CookieValueCookieSpring
@RequestAttribute请求属性Spring
@SessionAttributeSession属性Spring
@ModelAttribute模型属性Spring
@Valid启动校验JSR-303

十、面试高频问题

Q1: @RequestParam 和 @PathVariable 区别?

  • @PathVariable:URL 路径的一部分,如 /users/{id}
  • @RequestParam:URL 查询参数,如 /users?id=1

Q2: @RequestBody 如何处理 JSON?

  1. Content-Type 必须是 application/json
  2. Spring 使用 MappingJackson2HttpMessageConverter 反序列化
  3. 需要 Jackson 在类路径

Q3: GET 请求能带请求体吗?

  • 技术上可以,但不符合 HTTP 规范
  • 部分服务器可能忽略 GET 请求体
  • 建议用 @RequestParam@PathVariable

Q4: 如何实现参数校验?

  1. @Valid@Validated 标注参数
  2. 对象属性加校验注解
  3. 异常处理器捕获 MethodArgumentNotValidException

Q5: 文件上传大小限制?

yaml
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB

十一、下一章预告

下一章我们将学习 Spring MVC 响应处理与视图解析

  • 返回视图 vs 返回 JSON
  • 视图解析器配置
  • Thymeleaf 模板引擎
  • 重定向与转发

基于 MIT 许可发布