Appearance
Spring MVC Controller 声明与配置详解 🎯
前言:为什么需要 Controller?
在 Web 开发中,我们需要一个"交通指挥员"来处理用户的各种请求。想象一下,如果没有 Controller:
- 用户访问
/users
时,谁来处理这个请求? - 如何将业务逻辑与 Web 层分离?
- 怎样统一管理所有的 HTTP 请求处理?
Spring MVC 的 Controller 就是这样一个核心组件,它承担着请求分发和业务协调的重要职责。
NOTE
Controller 在 MVC 架构中扮演着"控制器"的角色,负责接收用户请求、调用业务逻辑、返回响应结果。
Controller 的声明方式
1. 基础声明:@Controller 注解
Spring MVC 使用 @Controller
注解来标识一个类为控制器组件:
kotlin
@Controller
class UserController {
@GetMapping("/users")
fun getAllUsers(): String {
// 处理获取用户列表的逻辑
return "users" // 返回视图名称
}
}
TIP
@Controller
是一个特殊的 @Component
,它不仅标识了组件的身份,还表明了该类在 Web 层中的特殊作用。
2. RESTful API:@RestController 注解
对于现代 Web 应用,我们更多使用 RESTful API:
kotlin
@Controller
class UserController {
@GetMapping("/api/users")
@ResponseBody
fun getAllUsers(): List<User> {
// 每个方法都需要 @ResponseBody
return userService.findAll()
}
@PostMapping("/api/users")
@ResponseBody
fun createUser(@RequestBody user: User): User {
return userService.save(user)
}
}
kotlin
@RestController
@RequestMapping("/api")
class UserController {
@GetMapping("/users")
fun getAllUsers(): List<User> {
// 自动序列化为 JSON
return userService.findAll()
}
@PostMapping("/users")
fun createUser(@RequestBody user: User): User {
return userService.save(user)
}
}
IMPORTANT
@RestController
= @Controller
+ @ResponseBody
,它会自动将返回值序列化为 JSON 格式,非常适合构建 RESTful API。
自动检测配置
组件扫描的威力
Spring 通过组件扫描自动发现和注册 Controller:
kotlin
@Configuration
@ComponentScan("com.example.web")
class WebConfiguration {
// Spring 会自动扫描指定包下的所有 @Controller
}
kotlin
@SpringBootApplication
class Application {
// @SpringBootApplication 包含了 @ComponentScan
// 默认扫描当前包及其子包
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
扫描过程可视化
实际应用示例
让我们通过一个完整的用户管理系统来看看 Controller 的实际应用:
kotlin
@RestController
@RequestMapping("/api/users")
@Validated
class UserController(
private val userService: UserService
) {
@GetMapping
fun getAllUsers(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "10") size: Int
): ResponseEntity<Page<User>> {
val users = userService.findAll(PageRequest.of(page, size))
return ResponseEntity.ok(users)
}
@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 user: User
): ResponseEntity<User> {
val savedUser = userService.save(user)
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser)
}
@PutMapping("/{id}")
fun updateUser(
@PathVariable id: Long,
@Valid @RequestBody user: User
): ResponseEntity<User> {
return if (userService.existsById(id)) {
val updatedUser = userService.save(user.copy(id = id))
ResponseEntity.ok(updatedUser)
} else {
ResponseEntity.notFound().build()
}
}
@DeleteMapping("/{id}")
fun deleteUser(@PathVariable id: Long): ResponseEntity<Void> {
return if (userService.existsById(id)) {
userService.deleteById(id)
ResponseEntity.noContent().build()
} else {
ResponseEntity.notFound().build()
}
}
}
TIP
注意上面代码中的几个最佳实践:
- 使用
ResponseEntity
精确控制 HTTP 响应 - 通过
@Valid
进行参数校验 - 合理使用 HTTP 状态码
AOP 代理的特殊考虑 ⚠️
为什么需要关注 AOP 代理?
当我们在 Controller 上使用 @Transactional
等 AOP 注解时,Spring 会创建代理对象:
kotlin
@RestController
@Transactional
class UserController(
private val userService: UserService
) {
@PostMapping("/users")
fun createUser(@RequestBody user: User): User {
// 这个方法会在事务中执行
return userService.save(user)
}
}
代理类型的选择
重要提醒
从 Spring 6.0 开始,如果 Controller 实现了接口并使用接口代理,Spring MVC 可能无法正确检测到基于 @RequestMapping
的控制器。
解决方案对比:
kotlin
interface UserApi {
@GetMapping("/users")
fun getAllUsers(): List<User>
}
@RestController
class UserController : UserApi {
override fun getAllUsers(): List<User> {
// Spring 6.0+ 可能检测不到这个映射
return userService.findAll()
}
}
kotlin
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
class TransactionConfig
kotlin
@Controller
interface UserApi {
@GetMapping("/users")
fun getAllUsers(): List<User>
}
@RestController
class UserController : UserApi {
override fun getAllUsers(): List<User> {
return userService.findAll()
}
}
代理机制可视化
最佳实践总结 ✅
1. 选择合适的注解
- @Controller:返回视图模板的传统 Web 应用
- @RestController:RESTful API 应用(推荐)
2. 合理组织包结构
src/main/kotlin/com/example/
├── controller/ # 控制层
│ ├── UserController.kt
│ └── OrderController.kt
├── service/ # 业务层
└── repository/ # 数据访问层
3. 统一异常处理
kotlin
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(ValidationException::class)
fun handleValidation(ex: ValidationException): ResponseEntity<ErrorResponse> {
return ResponseEntity.badRequest()
.body(ErrorResponse("参数校验失败", ex.message))
}
}
4. 合理使用 AOP
kotlin
@RestController
class UserController {
@PostMapping("/users")
@Transactional
fun createUserWithProfile(@RequestBody request: CreateUserRequest): User {
// 复杂的用户创建逻辑,需要事务保证
val user = userService.createUser(request.user)
profileService.createProfile(user.id, request.profile)
return user
}
}
总结 🎉
Spring MVC Controller 的声明看似简单,但背后蕴含着丰富的设计思想:
- 约定优于配置:通过注解简化配置
- 组件化设计:Controller 作为 Web 层的核心组件
- 灵活的代理机制:支持 AOP 增强功能
- 自动化管理:通过组件扫描实现自动注册
IMPORTANT
理解 Controller 的声明机制不仅有助于编写更好的代码,更能帮助我们深入理解 Spring MVC 的整体架构设计。
掌握了这些知识,你就能够:
- ✅ 正确声明和配置 Controller
- ✅ 避免 AOP 代理的常见陷阱
- ✅ 构建结构清晰的 Web 应用
- ✅ 遵循 Spring MVC 的最佳实践