Appearance
Spring MVC 注解控制器:让 Web 开发变得简单优雅 🎯
什么是注解控制器?为什么需要它?
在传统的 Web 开发中,我们需要继承特定的基类或实现复杂的接口来处理 HTTP 请求。这种方式不仅代码冗长,而且缺乏灵活性。Spring MVC 的注解控制器就是为了解决这些痛点而生的!
NOTE
注解控制器是 Spring MVC 提供的一种基于注解的编程模型,它让我们可以用简洁的注解来处理 Web 请求,而不需要继承任何基类或实现特定接口。
核心设计哲学 💡
Spring MVC 注解控制器的设计哲学可以概括为:约定优于配置,注解胜于继承。它通过以下方式简化了 Web 开发:
- 声明式编程:用注解声明意图,而不是编写大量样板代码
- 灵活的方法签名:方法参数和返回值可以根据需要灵活定义
- 松耦合设计:不依赖特定的基类或接口
核心注解详解
@Controller vs @RestController
kotlin
@Controller
class HelloController {
@GetMapping("/hello")
fun handle(model: Model): String {
model["message"] = "Hello World!"
return "index" // 返回视图名称
}
}
kotlin
@RestController
class ApiController {
@GetMapping("/api/hello")
fun handle(): Map<String, String> {
return mapOf("message" to "Hello World!")
// 直接返回 JSON 数据
}
}
TIP
选择建议:
- 使用
@Controller
当你需要返回视图页面时 - 使用
@RestController
当你构建 REST API 时
请求映射注解家族
HTTP 方法映射
kotlin
@RestController
@RequestMapping("/api/users")
class UserController {
@GetMapping // 等价于 @RequestMapping(method = [RequestMethod.GET])
fun getAllUsers(): List<User> {
// 获取所有用户
return userService.findAll()
}
@GetMapping("/{id}")
fun getUserById(@PathVariable id: Long): User {
return userService.findById(id)
}
@PostMapping
fun createUser(@RequestBody user: User): User {
return userService.save(user)
}
@PutMapping("/{id}")
fun updateUser(
@PathVariable id: Long,
@RequestBody user: User
): User {
return userService.update(id, user)
}
@DeleteMapping("/{id}")
fun deleteUser(@PathVariable id: Long) {
userService.deleteById(id)
}
}
方法参数绑定的魔法 ✨
Spring MVC 支持多种参数类型的自动绑定:
kotlin
@RestController
class DemoController {
@GetMapping("/demo")
fun demoMethod(
// 路径变量
@PathVariable id: Long,
// 查询参数
@RequestParam name: String,
@RequestParam(defaultValue = "0") page: Int,
// 请求体
@RequestBody user: User,
// 请求头
@RequestHeader("User-Agent") userAgent: String,
// Cookie
@CookieValue("sessionId") sessionId: String,
// HttpServletRequest 和 HttpServletResponse
request: HttpServletRequest,
response: HttpServletResponse,
// Model(用于传递数据到视图)
model: Model
): String {
// 处理逻辑
return "success"
}
}
IMPORTANT
Spring MVC 会根据方法参数的类型和注解,自动进行参数绑定和类型转换。这大大简化了请求处理的代码。
实际业务场景示例
让我们通过一个完整的用户管理 API 来看看注解控制器的实际应用:
完整的用户管理控制器示例
kotlin
@RestController
@RequestMapping("/api/users")
@Validated
class UserController(
private val userService: UserService
) {
/**
* 获取用户列表(支持分页和搜索)
*/
@GetMapping
fun getUsers(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "10") size: Int,
@RequestParam(required = false) keyword: String?
): ResponseEntity<PagedResponse<User>> {
val users = userService.findUsers(page, size, keyword)
return ResponseEntity.ok(users)
}
/**
* 根据 ID 获取用户
*/
@GetMapping("/{id}")
fun getUserById(@PathVariable id: Long): ResponseEntity<User> {
return userService.findById(id)?.let {
ResponseEntity.ok(it)
} ?: ResponseEntity.notFound().build()
}
/**
* 创建新用户
*/
@PostMapping
fun createUser(
@Valid @RequestBody createUserRequest: CreateUserRequest
): ResponseEntity<User> {
val user = userService.createUser(createUserRequest)
return ResponseEntity.status(HttpStatus.CREATED).body(user)
}
/**
* 更新用户信息
*/
@PutMapping("/{id}")
fun updateUser(
@PathVariable id: Long,
@Valid @RequestBody updateUserRequest: UpdateUserRequest
): ResponseEntity<User> {
val updatedUser = userService.updateUser(id, updateUserRequest)
return ResponseEntity.ok(updatedUser)
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
fun deleteUser(@PathVariable id: Long): ResponseEntity<Void> {
userService.deleteUser(id)
return ResponseEntity.noContent().build()
}
/**
* 批量操作示例
*/
@PostMapping("/batch")
fun batchCreateUsers(
@Valid @RequestBody users: List<CreateUserRequest>
): ResponseEntity<List<User>> {
val createdUsers = userService.batchCreateUsers(users)
return ResponseEntity.status(HttpStatus.CREATED).body(createdUsers)
}
}
// 数据传输对象
data class CreateUserRequest(
@field:NotBlank(message = "用户名不能为空") // [!code highlight]
val username: String,
@field:Email(message = "邮箱格式不正确") // [!code highlight]
val email: String,
@field:Size(min = 6, message = "密码长度至少6位") // [!code highlight]
val password: String
)
data class UpdateUserRequest(
val username: String?,
val email: String?
)
data class PagedResponse<T>(
val content: List<T>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int
)
请求处理流程可视化
异常处理最佳实践
kotlin
@RestControllerAdvice
class GlobalExceptionHandler {
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationException(
ex: MethodArgumentNotValidException
): ResponseEntity<ErrorResponse> {
val errors = ex.bindingResult.fieldErrors.map {
"${it.field}: ${it.defaultMessage}"
}
return ResponseEntity.badRequest().body(
ErrorResponse("参数验证失败", errors)
)
}
/**
* 处理资源未找到异常
*/
@ExceptionHandler(UserNotFoundException::class)
fun handleUserNotFound(ex: UserNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity.notFound().build()
}
/**
* 处理通用异常
*/
@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<ErrorResponse> {
return ResponseEntity.internalServerError().body(
ErrorResponse("服务器内部错误", listOf(ex.message ?: "未知错误"))
)
}
}
data class ErrorResponse(
val message: String,
val errors: List<String>,
val timestamp: Long = System.currentTimeMillis()
)
配置和最佳实践
启用 Web MVC 配置
kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
/**
* 配置跨域
*/
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
}
/**
* 配置拦截器
*/
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(LoggingInterceptor())
.addPathPatterns("/api/**")
}
}
对比:传统方式 vs 注解方式
java
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String pathInfo = req.getPathInfo();
if (pathInfo == null || pathInfo.equals("/")) {
// 获取所有用户
handleGetAllUsers(req, resp);
} else {
// 根据 ID 获取用户
String[] pathParts = pathInfo.split("/");
if (pathParts.length == 2) {
Long id = Long.parseLong(pathParts[1]);
handleGetUserById(id, req, resp);
}
}
}
private void handleGetAllUsers(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
// 大量样板代码...
resp.setContentType("application/json");
PrintWriter out = resp.getWriter();
// JSON 序列化代码...
}
}
kotlin
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
@GetMapping
fun getAllUsers(): List<User> = userService.findAll()
@GetMapping("/{id}")
fun getUserById(@PathVariable id: Long): User =
userService.findById(id)
}
TIP
可以看到,注解方式将原本需要几十行的代码简化为几行,大大提高了开发效率和代码可读性!
总结与最佳实践建议
核心优势 ✅
- 简洁性:用注解替代继承,代码更简洁
- 灵活性:方法签名可以根据需要灵活定义
- 可读性:注解清晰表达了方法的意图
- 可测试性:不依赖容器,更容易进行单元测试
最佳实践建议
开发建议
- 合理使用 @RequestMapping 的层级结构:在类级别定义公共路径前缀
- 充分利用参数验证:使用
@Valid
和 Bean Validation 注解 - 统一异常处理:使用
@RestControllerAdvice
进行全局异常处理 - RESTful 设计:遵循 REST 风格的 URL 设计和 HTTP 方法使用
- 合理使用响应状态码:通过
ResponseEntity
返回合适的 HTTP 状态码
IMPORTANT
Spring MVC 的注解控制器不仅仅是技术工具,更是一种编程思想的体现。它让我们能够专注于业务逻辑,而不是底层的 HTTP 处理细节。
通过掌握注解控制器,你将能够快速构建出结构清晰、易于维护的 Web 应用程序。记住,好的代码不仅要能工作,更要让人容易理解和维护! 🚀