Skip to content

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 的核心价值

  1. 统一入口:所有请求都通过一个入口处理,便于统一管理和控制
  2. 智能分发:根据 URL 模式自动找到对应的处理器
  3. 生命周期管理:完整的请求处理生命周期,支持拦截器和异常处理
  4. 松耦合设计:各个组件职责分离,易于扩展和测试

最佳实践建议

开发建议

  • 异常处理:使用 @ControllerAdvice 统一处理异常
  • 参数验证:使用 @Valid 和 Bean Validation 进行参数校验
  • 日志记录:在关键节点记录操作日志,便于问题排查
  • 性能监控:使用拦截器记录请求处理时间
  • 安全考虑:在拦截器中进行权限验证和防护

通过理解 DispatcherServlet 的工作原理,我们能够更好地设计和开发 Spring MVC 应用,充分利用其强大的功能来构建健壮、可维护的 Web 应用程序! 🎉