Skip to content

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 的声明看似简单,但背后蕴含着丰富的设计思想:

  1. 约定优于配置:通过注解简化配置
  2. 组件化设计:Controller 作为 Web 层的核心组件
  3. 灵活的代理机制:支持 AOP 增强功能
  4. 自动化管理:通过组件扫描实现自动注册

IMPORTANT

理解 Controller 的声明机制不仅有助于编写更好的代码,更能帮助我们深入理解 Spring MVC 的整体架构设计。

掌握了这些知识,你就能够:

  • ✅ 正确声明和配置 Controller
  • ✅ 避免 AOP 代理的常见陷阱
  • ✅ 构建结构清晰的 Web 应用
  • ✅ 遵循 Spring MVC 的最佳实践