Appearance
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 应用更加简洁、健壮和易于维护。在实际开发中,根据具体场景选择合适的参数类型,是提高开发效率的关键。