Appearance
Spring MVC DispatcherServlet 请求处理流程详解 🚀
什么是 DispatcherServlet?为什么需要它?
在传统的 Web 开发中,每个 URL 请求都需要单独配置一个 Servlet 来处理,这会导致配置文件冗长、难以维护。想象一下,如果你的网站有 100 个不同的页面,你就需要配置 100 个 Servlet!
NOTE
DispatcherServlet 是 Spring MVC 的核心组件,它采用了前端控制器模式(Front Controller Pattern),作为一个统一的入口点来处理所有的 HTTP 请求,然后将请求分发给相应的处理器。
设计哲学:一个入口,统一调度 ✨
- 之前:每个请求 → 独立的 Servlet → 配置复杂
- 现在:所有请求 → DispatcherServlet → 智能分发 → 对应的 Controller
DispatcherServlet 请求处理的六大步骤
让我们通过一个完整的时序图来理解 DispatcherServlet 是如何处理请求的:
步骤详解
1️⃣ WebApplicationContext 绑定
IMPORTANT
WebApplicationContext 是 Spring 容器在 Web 环境下的扩展,包含了所有的 Bean 定义和配置信息。
kotlin
// 传统 Servlet 中获取 Spring 容器很麻烦
class TraditionalServlet : HttpServlet() {
override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
// 需要手动获取 Spring 容器
val context = WebApplicationContextUtils
.getWebApplicationContext(servletContext)
val userService = context?.getBean(UserService::class.java)
}
}
kotlin
@RestController
class UserController {
// Spring 自动注入,无需手动获取容器
@Autowired
private lateinit var userService: UserService
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): User {
return userService.findById(id)
}
}
2️⃣ 本地化和主题解析器绑定
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
// 配置本地化解析器
@Bean
fun localeResolver(): LocaleResolver {
val resolver = SessionLocaleResolver()
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE)
return resolver
}
// 配置拦截器来处理本地化
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(LocaleChangeInterceptor().apply {
paramName = "lang" // 通过 ?lang=en 切换语言
})
}
}
3️⃣ 文件上传处理
TIP
当请求包含文件上传时,DispatcherServlet 会将普通的 HttpServletRequest 包装成 MultipartHttpServletRequest。
kotlin
@RestController
class FileUploadController {
@PostMapping("/upload")
fun uploadFile(
@RequestParam("file") file: MultipartFile,
@RequestParam("description") description: String
): ResponseEntity<String> {
if (file.isEmpty) {
return ResponseEntity.badRequest()
.body("文件不能为空")
}
try {
// 保存文件
val fileName = "${System.currentTimeMillis()}_${file.originalFilename}"
val targetFile = File("uploads/$fileName")
file.transferTo(targetFile)
return ResponseEntity.ok("文件上传成功: $fileName")
} catch (e: Exception) {
return ResponseEntity.status(500)
.body("文件上传失败: ${e.message}")
}
}
}
4️⃣ 处理器映射和执行
这是最核心的步骤!DispatcherServlet 需要找到能处理当前请求的 Controller。
kotlin
@RestController
@RequestMapping("/api/products")
class ProductController {
@Autowired
private lateinit var productService: ProductService
// GET /api/products - 获取所有产品
@GetMapping
fun getAllProducts(): List<Product> {
return productService.findAll()
}
// GET /api/products/123 - 获取特定产品
@GetMapping("/{id}")
fun getProduct(@PathVariable id: Long): ResponseEntity<Product> {
val product = productService.findById(id)
return if (product != null) {
ResponseEntity.ok(product)
} else {
ResponseEntity.notFound().build()
}
}
// POST /api/products - 创建新产品
@PostMapping
fun createProduct(@RequestBody @Valid product: Product): Product {
return productService.save(product)
}
}
5️⃣ 拦截器链执行
NOTE
拦截器允许我们在请求处理的前后添加自定义逻辑,比如权限检查、日志记录等。
kotlin
@Component
class AuthenticationInterceptor : HandlerInterceptor {
// 在 Controller 方法执行前调用
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val token = request.getHeader("Authorization")
if (token.isNullOrBlank()) {
response.status = HttpStatus.UNAUTHORIZED.value()
response.writer.write("缺少认证令牌")
return false // 阻止继续执行
}
// 验证令牌逻辑
if (!isValidToken(token)) {
response.status = HttpStatus.FORBIDDEN.value()
response.writer.write("无效的认证令牌")
return false
}
return true // 允许继续执行
}
// 在 Controller 方法执行后,视图渲染前调用
override fun postHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
modelAndView: ModelAndView?
) {
// 可以修改 ModelAndView
modelAndView?.addObject("timestamp", System.currentTimeMillis())
}
private fun isValidToken(token: String): Boolean {
// 实际的令牌验证逻辑
return token.startsWith("Bearer ") && token.length > 10
}
}
6️⃣ 视图解析和渲染
对于返回视图的 Controller(非 REST API),需要进行视图解析:
kotlin
@Controller
class WebController {
@GetMapping("/welcome")
fun welcome(model: Model): String {
model.addAttribute("message", "欢迎使用 Spring MVC!")
model.addAttribute("currentTime", LocalDateTime.now())
return "welcome" // 返回视图名称
}
@GetMapping("/user/{id}")
fun userProfile(@PathVariable id: Long, model: Model): String {
val user = userService.findById(id)
if (user == null) {
return "redirect:/error" // 重定向到错误页面
}
model.addAttribute("user", user)
return "user/profile" // 返回 user/profile.html
}
}
kotlin
@RestController
class ApiController {
@GetMapping("/api/welcome")
fun welcome(): Map<String, Any> {
return mapOf(
"message" to "欢迎使用 Spring MVC API!",
"timestamp" to System.currentTimeMillis()
)
}
// 直接返回对象,不需要视图解析
@GetMapping("/api/user/{id}")
fun getUserApi(@PathVariable id: Long): ResponseEntity<User> {
val user = userService.findById(id)
return ResponseEntity.ok(user)
}
}
异常处理机制
IMPORTANT
DispatcherServlet 通过 HandlerExceptionResolver 来处理请求过程中抛出的异常。
kotlin
@ControllerAdvice
class GlobalExceptionHandler {
// 处理参数验证异常
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationException(
ex: MethodArgumentNotValidException
): ResponseEntity<Map<String, String>> {
val errors = mutableMapOf<String, String>()
ex.bindingResult.fieldErrors.forEach { error ->
errors[error.field] = error.defaultMessage ?: "验证失败"
}
return ResponseEntity.badRequest().body(errors)
}
// 处理资源未找到异常
@ExceptionHandler(NoHandlerFoundException::class)
fun handleNotFound(ex: NoHandlerFoundException): ResponseEntity<String> {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("请求的资源不存在: ${ex.requestURL}")
}
// 处理通用异常
@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<String> {
// 记录错误日志
logger.error("未处理的异常", ex)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("服务器内部错误,请稍后重试")
}
companion object {
private val logger = LoggerFactory.getLogger(GlobalExceptionHandler::class.java)
}
}
DispatcherServlet 配置参数
TIP
在 Spring Boot 中,大部分配置都有默认值,但了解这些参数有助于深入理解和自定义行为。
kotlin
@Configuration
class DispatcherServletConfig {
@Bean
fun dispatcherServletRegistration(): ServletRegistrationBean<DispatcherServlet> {
val registration = ServletRegistrationBean(DispatcherServlet(), "/")
// 设置初始化参数
registration.addInitParameter("contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
registration.addInitParameter("contextConfigLocation",
"com.example.config.WebConfig")
registration.addInitParameter("throwExceptionIfNoHandlerFound", "true")
registration.setLoadOnStartup(1)
return registration
}
}
实际业务场景示例
让我们看一个完整的电商订单处理流程:
完整的订单处理示例
kotlin
@RestController
@RequestMapping("/api/orders")
@Validated
class OrderController {
@Autowired
private lateinit var orderService: OrderService
@Autowired
private lateinit var inventoryService: InventoryService
// 创建订单
@PostMapping
@Transactional
fun createOrder(
@RequestBody @Valid orderRequest: CreateOrderRequest,
request: HttpServletRequest
): ResponseEntity<OrderResponse> {
try {
// 1. 验证库存
if (!inventoryService.checkStock(orderRequest.items)) {
return ResponseEntity.badRequest()
.body(OrderResponse.error("库存不足"))
}
// 2. 创建订单
val order = orderService.createOrder(orderRequest)
// 3. 扣减库存
inventoryService.reduceStock(orderRequest.items)
// 4. 记录操作日志
val clientIp = getClientIpAddress(request)
logger.info("订单创建成功: ${order.id}, 客户端IP: $clientIp")
return ResponseEntity.ok(OrderResponse.success(order))
} catch (e: InsufficientStockException) {
return ResponseEntity.badRequest()
.body(OrderResponse.error("库存不足: ${e.message}"))
} catch (e: Exception) {
logger.error("创建订单失败", e)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(OrderResponse.error("系统错误,请稍后重试"))
}
}
// 获取订单详情
@GetMapping("/{orderId}")
fun getOrder(
@PathVariable orderId: Long,
@RequestHeader("User-Id") userId: Long
): ResponseEntity<Order> {
val order = orderService.findByIdAndUserId(orderId, userId)
return if (order != null) {
ResponseEntity.ok(order)
} else {
ResponseEntity.notFound().build()
}
}
private fun getClientIpAddress(request: HttpServletRequest): String {
val xForwardedFor = request.getHeader("X-Forwarded-For")
return if (!xForwardedFor.isNullOrBlank()) {
xForwardedFor.split(",")[0].trim()
} else {
request.remoteAddr
}
}
companion object {
private val logger = LoggerFactory.getLogger(OrderController::class.java)
}
}
// 数据传输对象
data class CreateOrderRequest(
@field:NotEmpty(message = "订单项不能为空")
val items: List<OrderItem>,
@field:NotBlank(message = "收货地址不能为空")
val shippingAddress: String,
@field:DecimalMin(value = "0.01", message = "订单金额必须大于0")
val totalAmount: BigDecimal
)
data class OrderResponse(
val success: Boolean,
val message: String,
val data: Order? = null
) {
companion object {
fun success(order: Order) = OrderResponse(true, "订单创建成功", order)
fun error(message: String) = OrderResponse(false, message)
}
}
核心价值总结
DispatcherServlet 的核心价值
- 统一入口:所有请求都通过一个入口处理,便于统一管理和控制
- 智能分发:根据 URL 模式自动找到对应的处理器
- 生命周期管理:完整的请求处理生命周期,支持拦截器和异常处理
- 松耦合设计:各个组件职责分离,易于扩展和测试
最佳实践建议
开发建议
- 异常处理:使用
@ControllerAdvice
统一处理异常 - 参数验证:使用
@Valid
和 Bean Validation 进行参数校验 - 日志记录:在关键节点记录操作日志,便于问题排查
- 性能监控:使用拦截器记录请求处理时间
- 安全考虑:在拦截器中进行权限验证和防护
通过理解 DispatcherServlet 的工作原理,我们能够更好地设计和开发 Spring MVC 应用,充分利用其强大的功能来构建健壮、可维护的 Web 应用程序! 🎉