Skip to content

Spring MVC Controller 方法参数详解 🚀

概述

在 Spring MVC 中,Controller 方法可以接收多种类型的参数,这些参数由 Spring 框架自动解析和注入。理解这些参数类型及其用途,是掌握 Spring MVC 开发的关键基础。

NOTE

Spring MVC 的方法参数解析机制让开发者能够以声明式的方式获取请求数据,无需手动从 HttpServletRequest 中提取信息,大大简化了 Web 开发的复杂度。

核心设计理念 💡

为什么需要多样化的方法参数?

在传统的 Servlet 开发中,我们需要这样获取请求数据:

java
// 传统方式:繁琐且容易出错
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    String name = request.getParameter("name"); 
    String ageStr = request.getParameter("age"); 
    int age = Integer.parseInt(ageStr); // [!code error] // 需要手动类型转换
    
    HttpSession session = request.getSession(); 
    String userId = (String) session.getAttribute("userId"); 
    
    // 大量的样板代码...
}
kotlin
@GetMapping("/user")
fun getUser(
    @RequestParam name: String, 
    @RequestParam age: Int, // [!code ++] // 自动类型转换
    @SessionAttribute userId: String, // [!code ++] // 直接获取 Session 属性
    request: HttpServletRequest // [!code ++] // 需要时仍可获取原始对象
): String {
    // 专注业务逻辑,无需样板代码
    return "user-profile"
}

TIP

Spring MVC 的参数解析机制遵循"约定优于配置"的原则,通过注解和类型推断,让代码更简洁、更易读。

方法参数分类详解 📋

1. 基础 Web 对象参数

这些参数提供对底层 Servlet API 的直接访问:

kotlin
@RestController
class WebObjectController {
    
    @GetMapping("/servlet-objects")
    fun handleServletObjects(
        request: HttpServletRequest,           // 原始请求对象
        response: HttpServletResponse,         // 原始响应对象
        session: HttpSession,                  // HTTP 会话
        webRequest: WebRequest,                // Spring 封装的请求对象
        locale: Locale,                        // 当前请求的语言环境
        timeZone: TimeZone                     // 当前请求的时区
    ): Map<String, Any> {
        return mapOf(
            "requestURI" to request.requestURI,
            "sessionId" to session.id,
            "locale" to locale.toString(),
            "timeZone" to timeZone.id
        )
    }
}

WARNING

HttpSession 参数会强制创建会话,如果会话不存在的话。在无状态的 REST API 中要谨慎使用。

2. 请求数据提取参数

@RequestParam - 获取请求参数

kotlin
@RestController
class RequestParamController {
    
    // 基础用法
    @GetMapping("/search")
    fun search(@RequestParam keyword: String): List<String> { 
        return searchService.search(keyword)
    }
    
    // 可选参数
    @GetMapping("/products")
    fun getProducts(
        @RequestParam(required = false, defaultValue = "1") page: Int, 
        @RequestParam(required = false, defaultValue = "10") size: Int
    ): Page<Product> {
        return productService.getProducts(page, size)
    }
    
    // 使用 Optional(JDK 8+)
    @GetMapping("/filter")
    fun filterProducts(
        @RequestParam category: Optional<String>, 
        @RequestParam minPrice: Optional<BigDecimal> 
    ): List<Product> {
        return productService.filter(
            category.orElse(null),
            minPrice.orElse(BigDecimal.ZERO)
        )
    }
}

@PathVariable - 获取路径变量

kotlin
@RestController
@RequestMapping("/api/users")
class UserController {
    
    @GetMapping("/{userId}")
    fun getUser(@PathVariable userId: Long): User { 
        return userService.findById(userId)
    }
    
    @GetMapping("/{userId}/orders/{orderId}")
    fun getUserOrder(
        @PathVariable userId: Long, 
        @PathVariable orderId: Long
    ): Order {
        return orderService.findByUserAndId(userId, orderId)
    }
    
    // 路径变量名与参数名不同时
    @GetMapping("/profile/{user-id}")
    fun getUserProfile(
        @PathVariable("user-id") userId: Long
    ): UserProfile {
        return userService.getProfile(userId)
    }
}

@RequestHeader - 获取请求头

kotlin
@RestController
class HeaderController {
    
    @PostMapping("/api/data")
    fun processData(
        @RequestHeader("Content-Type") contentType: String, 
        @RequestHeader("Authorization") authToken: String, 
        @RequestHeader(value = "X-Request-ID", required = false) requestId: String? 
    ): ResponseEntity<String> {
        
        // 验证内容类型
        if (!contentType.startsWith("application/json")) {
            return ResponseEntity.badRequest().body("Invalid content type")
        }
        
        // 处理认证
        if (!authService.validateToken(authToken)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
        }
        
        return ResponseEntity.ok("Data processed successfully")
    }
}

3. 请求体处理参数

@RequestBody - 处理 JSON/XML 请求体

kotlin
data class CreateUserRequest(
    val username: String,
    val email: String,
    val age: Int
)

@RestController
class UserApiController {
    
    @PostMapping("/api/users")
    fun createUser(@RequestBody request: CreateUserRequest): User { 
        // Spring 自动将 JSON 转换为 Kotlin 对象
        return userService.createUser(request)
    }
    
    @PutMapping("/api/users/{id}")
    fun updateUser(
        @PathVariable id: Long,
        @RequestBody updateRequest: UpdateUserRequest
    ): User {
        return userService.updateUser(id, updateRequest)
    }
}

HttpEntity - 同时访问请求头和请求体

kotlin
@PostMapping("/api/upload")
fun uploadFile(entity: HttpEntity<ByteArray>): ResponseEntity<String> { 
    val headers = entity.headers
    val body = entity.body
    
    val contentType = headers.contentType
    val contentLength = headers.contentLength
    
    // 处理文件上传逻辑
    fileService.saveFile(body, contentType)
    
    return ResponseEntity.ok("File uploaded successfully")
}

4. 数据绑定和验证参数

@ModelAttribute - 表单数据绑定

kotlin
data class UserForm(
    var username: String = "",
    var email: String = "",
    var age: Int = 0
)

@Controller
class FormController {
    
    @GetMapping("/register")
    fun showRegisterForm(model: Model): String {
        model.addAttribute("userForm", UserForm()) 
        return "register"
    }
    
    @PostMapping("/register")
    fun processRegistration(
        @ModelAttribute userForm: UserForm, 
        bindingResult: BindingResult
    ): String {
        
        if (bindingResult.hasErrors()) {
            return "register" // 返回表单页面显示错误
        }
        
        userService.registerUser(userForm)
        return "redirect:/success"
    }
}

5. 会话和请求属性参数

kotlin
@Controller
class SessionController {
    
    @PostMapping("/login")
    fun login(
        @RequestParam username: String,
        @RequestParam password: String,
        session: HttpSession
    ): String {
        
        val user = authService.authenticate(username, password)
        if (user != null) {
            session.setAttribute("currentUser", user) 
            return "redirect:/dashboard"
        }
        
        return "login"
    }
    
    @GetMapping("/profile")
    fun showProfile(
        @SessionAttribute("currentUser") user: User
    ): String {
        // 直接获取会话中的用户对象
        return "profile"
    }
    
    @GetMapping("/admin")
    fun adminPanel(
        @RequestAttribute("userRole") role: String
    ): String {
        // 获取请求属性(通常由过滤器或拦截器设置)
        if (role != "ADMIN") {
            throw AccessDeniedException("Admin access required")
        }
        return "admin-panel"
    }
}

实际应用场景示例 🎯

场景1:RESTful API 开发

kotlin
@RestController
@RequestMapping("/api/v1/products")
class ProductApiController(
    private val productService: ProductService
) {
    
    @GetMapping
    fun getProducts(
        @RequestParam(defaultValue = "0") page: Int,
        @RequestParam(defaultValue = "20") size: Int,
        @RequestParam(required = false) category: String?,
        @RequestParam(required = false) minPrice: BigDecimal?,
        @RequestParam(required = false) maxPrice: BigDecimal?
    ): Page<Product> {
        val filter = ProductFilter(category, minPrice, maxPrice)
        return productService.findProducts(filter, PageRequest.of(page, size))
    }
    
    @GetMapping("/{id}")
    fun getProduct(@PathVariable id: Long): Product {
        return productService.findById(id)
            ?: throw ProductNotFoundException("Product not found: $id")
    }
    
    @PostMapping
    fun createProduct(
        @RequestBody @Valid request: CreateProductRequest,
        @RequestHeader("Authorization") authToken: String
    ): ResponseEntity<Product> {
        
        // 验证权限
        authService.validateAdminToken(authToken)
        
        val product = productService.createProduct(request)
        return ResponseEntity.status(HttpStatus.CREATED).body(product)
    }
}

场景2:文件上传处理

kotlin
@Controller
class FileUploadController {
    
    @PostMapping("/upload")
    fun uploadFile(
        @RequestParam("file") file: MultipartFile, 
        @RequestParam("description") description: String,
        @SessionAttribute("currentUser") user: User,
        redirectAttributes: RedirectAttributes
    ): String {
        
        if (file.isEmpty) {
            redirectAttributes.addFlashAttribute("error", "Please select a file") 
            return "redirect:/upload"
        }
        
        try {
            fileService.saveFile(file, description, user)
            redirectAttributes.addFlashAttribute("success", "File uploaded successfully") 
        } catch (e: Exception) {
            redirectAttributes.addFlashAttribute("error", "Upload failed: ${e.message}")
        }
        
        return "redirect:/files"
    }
}

参数解析流程图 📊

最佳实践建议 ✅

1. 参数验证

kotlin
data class CreateUserRequest(
    @field:NotBlank(message = "用户名不能为空")
    @field:Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    val username: String,
    
    @field:Email(message = "邮箱格式不正确")
    val email: String,
    
    @field:Min(value = 18, message = "年龄不能小于18岁")
    val age: Int
)

@PostMapping("/users")
fun createUser(
    @RequestBody @Valid request: CreateUserRequest, 
    bindingResult: BindingResult
): ResponseEntity<*> {
    
    if (bindingResult.hasErrors()) {
        val errors = bindingResult.fieldErrors.map { 
            mapOf("field" to it.field, "message" to it.defaultMessage)
        }
        return ResponseEntity.badRequest().body(mapOf("errors" to errors))
    }
    
    val user = userService.createUser(request)
    return ResponseEntity.ok(user)
}

2. 可选参数处理

kotlin
@GetMapping("/search")
fun search(
    @RequestParam keyword: String,
    @RequestParam(defaultValue = "1") page: Int, 
    @RequestParam(defaultValue = "10") size: Int, 
    @RequestParam(required = false) category: String? 
): SearchResult {
    return searchService.search(keyword, page, size, category)
}
kotlin
@GetMapping("/search")
fun searchWithOptional(
    @RequestParam keyword: String,
    @RequestParam page: Optional<Int>, 
    @RequestParam size: Optional<Int>, 
    @RequestParam category: Optional<String> 
): SearchResult {
    return searchService.search(
        keyword, 
        page.orElse(1), 
        size.orElse(10), 
        category.orElse(null)
    )
}

3. 错误处理

kotlin
@ControllerAdvice
class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidationErrors(ex: MethodArgumentNotValidException): ResponseEntity<*> {
        val errors = ex.bindingResult.fieldErrors.map {
            mapOf("field" to it.field, "message" to it.defaultMessage)
        }
        return ResponseEntity.badRequest().body(mapOf("errors" to errors))
    }
    
    @ExceptionHandler(MissingServletRequestParameterException::class)
    fun handleMissingParam(ex: MissingServletRequestParameterException): ResponseEntity<*> {
        return ResponseEntity.badRequest()
            .body("缺少必需参数: ${ex.parameterName}")
    }
}

常见问题与解决方案 ❓

问题1:参数类型转换失败

WARNING

当请求参数无法转换为目标类型时,Spring 会抛出 TypeMismatchException

解决方案:

kotlin
@InitBinder
fun initBinder(binder: WebDataBinder) {
    // 自定义日期格式转换
    val dateFormat = SimpleDateFormat("yyyy-MM-dd")
    binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}

问题2:中文参数乱码

解决方案:

kotlin
// 在 application.yml 中配置
server:
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

总结 🎉

Spring MVC 的方法参数机制提供了强大而灵活的请求数据处理能力:

  • 简化开发:通过注解声明式地获取请求数据
  • 类型安全:自动类型转换和验证
  • 可扩展性:支持自定义参数解析器
  • 最佳实践:遵循约定优于配置的原则

TIP

掌握这些参数类型的使用,能让你的 Spring MVC 应用更加简洁、健壮和易于维护。在实际开发中,根据具体场景选择合适的参数类型,是提高开发效率的关键。