1. 概述

前端控制器模式(Front Controller)中,单个控制器负责将所有传入的 HttpRequest 分发到应用的其他控制器和处理器。Spring 的 DispatcherServlet 正是这一模式的实现,它负责协调请求与正确处理器的匹配。

本文将深入剖析 DispatcherServlet 的请求处理流程,并演示如何实现参与该流程的核心接口。

2. DispatcherServlet 请求处理流程

本质上,DispatcherServlet 的职责是:接收请求 → 委托处理 → 通过配置的 HandlerAdapter 接口执行处理逻辑。这些接口通常与注解(如 @Controller@RequestMapping)配合使用,指定处理器、接口和响应对象。

DispatcherServlet 处理请求的核心步骤:

  1. 查找上下文:通过键 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 查找关联的 WebApplicationContext,供流程中所有组件使用
  2. 获取处理器适配器:调用 getHandler() 找到所有 HandlerAdapter 实现,每个实现通过 handle() 处理请求
  3. ⚠️ 可选绑定
    • LocaleResolver:绑定到请求,支持国际化解析
    • ThemeResolver:绑定到请求,支持主题解析(如视图样式)
  4. 文件上传处理:若配置了 MultipartResolver,检测 MultipartFile 并包装为 MultipartHttpServletRequest
  5. 异常处理HandlerExceptionResolver 实现捕获处理过程中的异常

💡 关于 DispatcherServlet 的注册与配置方式,可参考官方文档

3. HandlerAdapter 核心接口

HandlerAdapter 接口是 DispatcherServlet 流程的核心枢纽,它协调控制器、Servlet、HTTP 请求和路径的映射。每个 HandlerAdapter 实现会被放入 HandlerExecutionChain,并通过 handle() 处理 HttpServletRequest

3.1. 请求映射(Mappings)

控制器注解是 HandlerMapping 的基础。Spring 提供两种主要适配器:

  • SimpleControllerHandlerAdapter:支持无 @Controller 注解的传统控制器
  • RequestMappingHandlerAdapter:支持 @RequestMapping 注解方法

📌 本文聚焦 @Controller 注解,传统实现可参考示例文档

@RequestMapping 注解定义处理器的接口路径:

@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
 
    @GetMapping("/example")
    public User fetchUserExample() {
        // ...
    }
}

路径映射规则

  • 路径相对于 DispatcherServlet 的映射配置
  • DispatcherServlet 映射为 /,则所有路径均有效
  • 若映射为 /dispatcher,则 @RequestMapping 路径需基于该根路径

⚠️ 踩坑警告//* 的区别

  • /:默认映射,覆盖所有路径
  • /*:会覆盖其他映射,导致 /example 返回 404!

    🚫 除非特殊场景(如配置过滤器),否则避免使用 /*

3.2. HTTP 请求处理

DispatcherServlet 的核心职责:将请求分发给 @Controller/@RestController 注解的处理器。

💡 @RestController = @Controller + @ResponseBody(自动序列化响应体)

深入控制器原理可参考Spring 控制器详解

3.3. ViewResolver 接口

ViewResolver 决定视图的类型和位置,通过 ApplicationContext 配置绑定到 DispatcherServlet

JSP 视图解析器配置示例

@Configuration
@EnableWebMvc
@ComponentScan("com.baeldung.springdispatcherservlet")
public class AppConfig implements WebMvcConfigurer {

    @Bean
    public UrlBasedViewResolver viewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

关键配置项

  1. setPrefix:视图文件的基础路径
  2. setSuffix:视图文件扩展名
  3. setViewClass:视图技术(如 JSTL、Tiles)

路径解析逻辑

  • 应用部署地址:http://localhost:8080/dispatcherexample-1.0.0/
  • 前缀 /jsp/ → 实际访问:http://localhost:8080/dispatcherexample-1.0.0/jsp/
  • 项目结构:
    src/main/
      ├── java
      ├── resources
      └── webapp/
          ├── jsp/          # 视图文件位置
          └── WEB-INF/
    

3.4. LocaleResolver 接口

LocaleResolver 用于定制会话、请求或 Cookie 的国际化信息

Cookie 方式配置

@Bean
public CookieLocaleResolver cookieLocaleResolverExample() {
    CookieLocaleResolver localeResolver = new CookieLocaleResolver();
    localeResolver.setDefaultLocale(Locale.ENGLISH);
    localeResolver.setCookieName("locale-cookie-resolver-example");
    localeResolver.setCookieMaxAge(3600); // 1小时
    return localeResolver;
}

会话方式配置

@Bean 
public LocaleResolver sessionLocaleResolver() { 
    SessionLocaleResolver localeResolver = new SessionLocaleResolver(); 
    localeResolver.setDefaultLocale(Locale.US); 
    localeResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
    return localeResolver; 
}

📌 setDefaultLocale() 设置地理/文化区域,setDefaultTimeZone() 设置时区

3.5. ThemeResolver 接口

Spring 支持视图主题切换(如 CSS 样式)。配置流程:

1. 配置静态资源和主题源

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
      .addResourceLocations("/", "/resources/")
      .setCachePeriod(3600)
      .resourceChain(true)
      .addResolver(new PathResourceResolver());
}

@Bean
public ResourceBundleThemeSource themeSource() {
    ResourceBundleThemeSource themeSource = new ResourceBundleThemeSource();
    themeSource.setDefaultEncoding("UTF-8");
    themeSource.setBasenamePrefix("themes."); // 主题文件前缀
    return themeSource;
}

2. 配置主题解析器和拦截器

@Bean
public CookieThemeResolver themeResolver() {
    CookieThemeResolver resolver = new CookieThemeResolver();
    resolver.setDefaultThemeName("example");
    resolver.setCookieName("example-theme-cookie");
    return resolver;
}

@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
   ThemeChangeInterceptor interceptor = new ThemeChangeInterceptor();
   interceptor.setParamName("theme"); // URL参数名
   return interceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(themeChangeInterceptor());
}

3. 视图中使用主题

<link rel="stylesheet" href="${ctx}/<spring:theme code='styleSheet'/>" type="text/css"/>

切换主题:访问 http://localhost:8080/dispatcherexample-1.0.0/?theme=example

3.6. MultipartResolver 接口

处理文件上传需配置 MultipartConfigElement,限制请求大小:

@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setMaxFileSize(DataSize.ofBytes(10000000L)); // 10MB
    factory.setMaxRequestSize(DataSize.ofBytes(10000000L));
    return factory.createMultipartConfig();
}

文件上传控制器示例

@Controller
public class MultipartController {

    @Autowired
    ServletContext context;

    @PostMapping("/upload")
    public ModelAndView FileuploadController(
      @RequestParam("file") MultipartFile file) 
      throws IOException {
        ModelAndView modelAndView = new ModelAndView("index");
        InputStream in = file.getInputStream();
        String path = new File(".").getAbsolutePath();
        FileOutputStream f = new FileOutputStream(
          path.substring(0, path.length()-1)
          + "/uploads/" + file.getOriginalFilename());
        int ch;
        while ((ch = in.read()) != -1) {
            f.write(ch);
        }
        f.flush();
        f.close();
        in.close();
        modelAndView.getModel()
          .put("message", "文件上传成功!");
        return modelAndView;
    }
}

📌 文件保存位置:CATALINA_HOME/bin/uploads/

3.7. HandlerExceptionResolver 接口

提供全局异常处理能力。两种实现方式:

1. 全局异常处理(推荐)

@ControllerAdvice
public class ExampleGlobalExceptionHandler {

    @ExceptionHandler
    @ResponseBody 
    public String handleExampleException(Exception e) {
        // 处理所有控制器抛出的异常
    }
}

2. 控制器内异常处理

@Controller
public class FooController{

    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        // 仅处理 FooController 内的指定异常
    }
}

💡 深入异常处理可参考Spring 异常处理机制

4. 总结

本文系统剖析了 Spring DispatcherServlet 的核心原理与配置方法,涵盖:

  • 请求处理全流程
  • HandlerAdapter 系列接口实战
  • 文件上传、国际化、主题等高级特性

完整源码可在 GitHub 获取。


原始标题:An Intro to the Spring DispatcherServlet | Baeldung

« 上一篇: JAX-WS 入门指南
» 下一篇: Serenity BDD 实战指南