Skip to content

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

可以看到,注解方式将原本需要几十行的代码简化为几行,大大提高了开发效率和代码可读性!

总结与最佳实践建议

核心优势 ✅

  1. 简洁性:用注解替代继承,代码更简洁
  2. 灵活性:方法签名可以根据需要灵活定义
  3. 可读性:注解清晰表达了方法的意图
  4. 可测试性:不依赖容器,更容易进行单元测试

最佳实践建议

开发建议

  1. 合理使用 @RequestMapping 的层级结构:在类级别定义公共路径前缀
  2. 充分利用参数验证:使用 @Valid 和 Bean Validation 注解
  3. 统一异常处理:使用 @RestControllerAdvice 进行全局异常处理
  4. RESTful 设计:遵循 REST 风格的 URL 设计和 HTTP 方法使用
  5. 合理使用响应状态码:通过 ResponseEntity 返回合适的 HTTP 状态码

IMPORTANT

Spring MVC 的注解控制器不仅仅是技术工具,更是一种编程思想的体现。它让我们能够专注于业务逻辑,而不是底层的 HTTP 处理细节。

通过掌握注解控制器,你将能够快速构建出结构清晰、易于维护的 Web 应用程序。记住,好的代码不仅要能工作,更要让人容易理解和维护! 🚀