1. 概述
前端控制器模式(Front Controller)中,单个控制器负责将所有传入的 HttpRequest
分发到应用的其他控制器和处理器。Spring 的 DispatcherServlet
正是这一模式的实现,它负责协调请求与正确处理器的匹配。
本文将深入剖析 DispatcherServlet
的请求处理流程,并演示如何实现参与该流程的核心接口。
2. DispatcherServlet 请求处理流程
本质上,DispatcherServlet
的职责是:接收请求 → 委托处理 → 通过配置的 HandlerAdapter
接口执行处理逻辑。这些接口通常与注解(如 @Controller
、@RequestMapping
)配合使用,指定处理器、接口和响应对象。
DispatcherServlet
处理请求的核心步骤:
- ✅ 查找上下文:通过键
DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
查找关联的WebApplicationContext
,供流程中所有组件使用 - ✅ 获取处理器适配器:调用
getHandler()
找到所有HandlerAdapter
实现,每个实现通过handle()
处理请求 - ⚠️ 可选绑定:
LocaleResolver
:绑定到请求,支持国际化解析ThemeResolver
:绑定到请求,支持主题解析(如视图样式)
- ✅ 文件上传处理:若配置了
MultipartResolver
,检测MultipartFile
并包装为MultipartHttpServletRequest
- ❌ 异常处理:
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;
}
}
关键配置项:
setPrefix
:视图文件的基础路径setSuffix
:视图文件扩展名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 获取。