Appearance
Spring MVC Controller Advice 深度解析 🎯
什么是 Controller Advice?
在 Spring MVC 中,@ControllerAdvice
是一个强大的注解,它允许我们定义全局的异常处理、数据绑定和模型属性设置。简单来说,它就像是一个"全局助手",可以为所有的 Controller 提供统一的处理逻辑。
TIP
把 @ControllerAdvice
想象成一个"超级管家",它可以统一处理所有 Controller 中的异常、数据绑定等通用逻辑,避免在每个 Controller 中重复编写相同的代码。
为什么需要 Controller Advice? 🤔
传统方式的痛点
在没有 @ControllerAdvice
之前,我们通常需要在每个 Controller 中重复编写异常处理逻辑:
kotlin
@RestController
class UserController {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): User {
return userService.findById(id)
}
// 每个Controller都需要重复编写异常处理
@ExceptionHandler(UserNotFoundException::class)
fun handleUserNotFound(ex: UserNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(404)
.body(ErrorResponse("用户不存在", ex.message))
}
}
@RestController
class OrderController {
@GetMapping("/orders/{id}")
fun getOrder(@PathVariable id: Long): Order {
return orderService.findById(id)
}
// 同样的异常处理逻辑又要写一遍!
@ExceptionHandler(UserNotFoundException::class)
fun handleUserNotFound(ex: UserNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(404)
.body(ErrorResponse("用户不存在", ex.message))
}
}
kotlin
// 全局异常处理器
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException::class)
fun handleUserNotFound(ex: UserNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(404)
.body(ErrorResponse("用户不存在", ex.message))
}
}
@RestController
class UserController {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): User {
return userService.findById(id) // 异常会被全局处理器捕获
}
}
@RestController
class OrderController {
@GetMapping("/orders/{id}")
fun getOrder(@PathVariable id: Long): Order {
return orderService.findById(id) // 异常会被全局处理器捕获
}
}
Controller Advice 的核心功能 ⚡
1. 全局异常处理 (@ExceptionHandler
)
kotlin
@RestControllerAdvice
class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException::class)
fun handleBusinessException(ex: BusinessException): ResponseEntity<ErrorResponse> {
return ResponseEntity.badRequest()
.body(ErrorResponse(
code = ex.errorCode,
message = ex.message ?: "业务处理失败"
))
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationException(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
val errors = ex.bindingResult.fieldErrors.map {
"${it.field}: ${it.defaultMessage}"
}
return ResponseEntity.badRequest()
.body(ErrorResponse(
code = "VALIDATION_ERROR",
message = "参数校验失败",
details = errors
))
}
// 处理系统异常
@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<ErrorResponse> {
// 记录日志
logger.error("系统异常", ex)
return ResponseEntity.status(500)
.body(ErrorResponse(
code = "SYSTEM_ERROR",
message = "系统内部错误"
))
}
}
2. 全局数据绑定 (@InitBinder
)
kotlin
@ControllerAdvice
class GlobalDataBindingAdvice {
// 全局日期格式处理
@InitBinder
fun initDateBinder(binder: WebDataBinder) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
dateFormat.isLenient = false
binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}
// 字符串去空格处理
@InitBinder
fun initStringBinder(binder: WebDataBinder) {
binder.registerCustomEditor(String::class.java, StringTrimmerEditor(true))
}
}
3. 全局模型属性 (@ModelAttribute
)
kotlin
@ControllerAdvice
class GlobalModelAdvice {
// 为所有Controller添加当前用户信息
@ModelAttribute("currentUser")
fun getCurrentUser(request: HttpServletRequest): User? {
val token = request.getHeader("Authorization")
return if (token != null) {
userService.getUserFromToken(token)
} else null
}
// 添加系统配置信息
@ModelAttribute("systemInfo")
fun getSystemInfo(): SystemInfo {
return SystemInfo(
version = "1.0.0",
environment = environment.getProperty("spring.profiles.active") ?: "unknown"
)
}
}
@ControllerAdvice vs @RestControllerAdvice 🆚
NOTE
@RestControllerAdvice
是 @ControllerAdvice
和 @ResponseBody
的组合注解,专门用于 REST API 的全局处理。
kotlin
// 等价写法
@ControllerAdvice
@ResponseBody
class GlobalExceptionHandler {
// ...
}
// 简化写法
@RestControllerAdvice
class GlobalExceptionHandler {
// ...
}
精确控制作用范围 🎯
@ControllerAdvice
支持多种方式来精确控制其作用范围:
1. 基于注解类型
kotlin
// 只对标注了 @RestController 的控制器生效
@ControllerAdvice(annotations = [RestController::class])
class RestControllerAdvice {
@ExceptionHandler(Exception::class)
fun handleRestException(ex: Exception): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(500)
.body(ErrorResponse("REST API 错误", ex.message))
}
}
2. 基于包路径
kotlin
// 只对指定包下的控制器生效
@ControllerAdvice("com.example.api.controller")
class ApiControllerAdvice {
@ExceptionHandler(ApiException::class)
fun handleApiException(ex: ApiException): ResponseEntity<ErrorResponse> {
return ResponseEntity.badRequest()
.body(ErrorResponse("API 调用失败", ex.message))
}
}
3. 基于类型
kotlin
// 只对实现了特定接口或继承特定类的控制器生效
@ControllerAdvice(assignableTypes = [BaseController::class, ApiController::class])
class SpecificControllerAdvice {
@ExceptionHandler(BusinessException::class)
fun handleBusinessException(ex: BusinessException): ResponseEntity<ErrorResponse> {
return ResponseEntity.badRequest()
.body(ErrorResponse("业务异常", ex.message))
}
}
执行顺序与优先级 📊
Spring 在处理异常和模型属性时有特定的执行顺序:
IMPORTANT
- 异常处理:本地
@ExceptionHandler
优先于全局的 - 模型属性:全局
@ModelAttribute
优先于本地的 - 数据绑定:全局
@InitBinder
优先于本地的
实战案例:构建完整的异常处理体系 🛠️
让我们构建一个完整的异常处理体系:
完整的异常处理示例
kotlin
// 1. 定义统一的错误响应格式
data class ErrorResponse(
val code: String,
val message: String,
val timestamp: Long = System.currentTimeMillis(),
val details: List<String>? = null
)
// 2. 定义业务异常基类
open class BusinessException(
val errorCode: String,
message: String,
cause: Throwable? = null
) : RuntimeException(message, cause)
// 3. 具体的业务异常
class UserNotFoundException(userId: Long) : BusinessException(
"USER_NOT_FOUND",
"用户不存在: $userId"
)
class InsufficientBalanceException(required: BigDecimal, available: BigDecimal) : BusinessException(
"INSUFFICIENT_BALANCE",
"余额不足,需要: $required,可用: $available"
)
// 4. 全局异常处理器
@RestControllerAdvice
class GlobalExceptionHandler {
private val logger = LoggerFactory.getLogger(GlobalExceptionHandler::class.java)
// 处理业务异常
@ExceptionHandler(BusinessException::class)
fun handleBusinessException(ex: BusinessException): ResponseEntity<ErrorResponse> {
logger.warn("业务异常: {}", ex.message)
return ResponseEntity.badRequest()
.body(ErrorResponse(
code = ex.errorCode,
message = ex.message ?: "业务处理失败"
))
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationException(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
val errors = ex.bindingResult.fieldErrors.map {
"${it.field}: ${it.defaultMessage}"
}
return ResponseEntity.badRequest()
.body(ErrorResponse(
code = "VALIDATION_ERROR",
message = "参数校验失败",
details = errors
))
}
// 处理HTTP方法不支持异常
@ExceptionHandler(HttpRequestMethodNotSupportedException::class)
fun handleMethodNotSupported(ex: HttpRequestMethodNotSupportedException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(405)
.body(ErrorResponse(
code = "METHOD_NOT_ALLOWED",
message = "不支持的HTTP方法: ${ex.method}"
))
}
// 处理系统异常
@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<ErrorResponse> {
logger.error("系统异常", ex)
return ResponseEntity.status(500)
.body(ErrorResponse(
code = "SYSTEM_ERROR",
message = "系统内部错误"
))
}
}
// 5. 使用示例
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): User {
// 如果用户不存在,会抛出 UserNotFoundException
// 异常会被 GlobalExceptionHandler 捕获并处理
return userService.findById(id) ?: throw UserNotFoundException(id)
}
@PostMapping
fun createUser(@Valid @RequestBody request: CreateUserRequest): User {
// 如果参数校验失败,会抛出 MethodArgumentNotValidException
// 异常会被 GlobalExceptionHandler 捕获并处理
return userService.create(request)
}
}
最佳实践与注意事项 ⚠️
1. 合理组织异常处理器
kotlin
// 按功能模块分组
@RestControllerAdvice("com.example.user")
class UserModuleExceptionHandler {
// 用户模块相关异常处理
}
@RestControllerAdvice("com.example.order")
class OrderModuleExceptionHandler {
// 订单模块相关异常处理
}
@RestControllerAdvice
class GlobalExceptionHandler {
// 通用异常处理
}
2. 避免过度使用
WARNING
不要在 @ControllerAdvice
中处理所有异常。只处理真正需要全局处理的异常,特定业务逻辑的异常应该在对应的 Controller 中处理。
3. 性能考虑
kotlin
// ❌ 避免复杂的选择器,会影响性能
@ControllerAdvice(
annotations = [RestController::class, Controller::class],
basePackages = ["com.example.api", "com.example.web"],
assignableTypes = [BaseController::class]
)
class ComplexAdvice
// ✅ 使用简单明确的选择器
@RestControllerAdvice("com.example.api")
class ApiExceptionHandler
总结 📝
@ControllerAdvice
是 Spring MVC 中一个非常强大的功能,它帮助我们:
- ✅ 统一异常处理:避免在每个 Controller 中重复编写异常处理逻辑
- ✅ 全局数据绑定:统一处理数据格式转换和校验
- ✅ 模型属性共享:为所有 Controller 提供通用的模型数据
- ✅ 代码复用:减少重复代码,提高维护性
- ✅ 关注点分离:将横切关注点从业务逻辑中分离出来
TIP
记住:@ControllerAdvice
就像是一个"全局管家",它让我们的代码更加整洁、可维护,是构建健壮 Spring MVC 应用的重要工具! 🎉