本文以前端控制器模式为切入点,逐层剖析 DispatcherServlet 的 9 大特殊 Bean、
doDispatch核心流程、视图解析链与拦截器机制,结合 Spring Boot 自动配置和常见坑点,给出生产实践建议。
目录
| 章节 | 说明 |
|---|---|
| 整体架构:前端控制器模式 | DispatcherServlet 的定位与设计思想 |
| 9 大特殊 Bean | DispatcherServlet 识别的核心扩展点 |
| 请求处理完整流程 | doDispatch 源码级分析 |
| HandlerMapping 详解 | RequestMappingHandlerMapping vs RouterFunctionMapping |
| HandlerAdapter 详解 | 适配不同 Handler 类型的机制 |
| 视图解析流程 | ViewResolver 链的工作原理 |
| 拦截器机制 | HandlerInterceptor 三个回调的执行顺序 |
| 与 Spring Boot 的关系 | DispatcherServletAutoConfiguration 原理 |
| 常见坑 | 字符编码、静态资源、异常处理等 |
整体架构:前端控制器模式
Front Controller Pattern(前端控制器模式)是 GoF 之外的企业应用模式,由 Martin Fowler 在《Patterns of Enterprise Application Architecture》中定义:
用一个统一的入口点处理所有请求,集中处理安全、路由、视图渲染等横切关注点,再将控制权委托给具体的处理器。
Spring MVC 的 DispatcherServlet 正是该模式的实现:
HTTP 请求
│
▼
DispatcherServlet(前端控制器)
│ 统一入口,协调所有组件
├─► HandlerMapping → 找到 Handler(Controller 方法)
├─► HandlerAdapter → 调用 Handler,返回 ModelAndView
├─► ViewResolver → 逻辑视图名 → 具体 View 对象
└─► View.render() → 生成响应
DispatcherServlet 继承链:
HttpServlet
└── HttpServletBean (Spring 托管的 Servlet 基类)
└── FrameworkServlet (持有 WebApplicationContext)
└── DispatcherServlet (核心前端控制器)
FrameworkServlet 覆写了 doGet / doPost 等方法,统一转发给 processRequest(),最终调用 DispatcherServlet.doService() → doDispatch()。
9 大特殊 Bean
DispatcherServlet 在初始化时(initStrategies())从 WebApplicationContext 中查找以下 9 种 Bean,若找不到则加载 DispatcherServlet.properties 中配置的默认实现:
| Bean 类型 | 默认实现 | 职责 |
|---|---|---|
HandlerMapping |
RequestMappingHandlerMappingBeanNameUrlHandlerMapping |
将请求 URL 映射到 Handler(含拦截器链) |
HandlerAdapter |
RequestMappingHandlerAdapterHttpRequestHandlerAdapterSimpleControllerHandlerAdapter |
适配并调用具体 Handler |
HandlerExceptionResolver |
ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver |
统一处理 Handler 执行期间抛出的异常 |
ViewResolver |
InternalResourceViewResolver |
逻辑视图名 → View 对象 |
LocaleResolver |
AcceptHeaderLocaleResolver |
解析客户端 Locale,支持国际化 |
ThemeResolver |
FixedThemeResolver |
解析主题(现代项目基本不用) |
MultipartResolver |
无默认(需手动配置) | 解析文件上传请求(multipart/form-data) |
FlashMapManager |
SessionFlashMapManager |
管理 PRG(Post/Redirect/Get)模式中的 Flash 属性 |
RequestToViewNameTranslator |
DefaultRequestToViewNameTranslator |
当 Handler 未返回视图名时,从 URL 自动推导视图名 |
关键细节:
MultipartResolver没有默认实现,若不配置则DispatcherServlet不会解析 multipart 请求,文件上传会失败。Spring Boot 通过MultipartAutoConfiguration自动注册StandardServletMultipartResolver。
请求处理完整流程
doDispatch 源码分析
DispatcherServlet#doDispatch 是请求处理的核心方法,简化后的源码逻辑如下:
// org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1. 检查是否是 multipart 请求,若是则封装为 MultipartHttpServletRequest
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 2. 遍历所有 HandlerMapping,找到第一个匹配的 HandlerExecutionChain
// HandlerExecutionChain = Handler + 拦截器列表
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 找不到 Handler → 返回 404 或 noHandlerFound()
noHandlerFound(processedRequest, response);
return;
}
// 3. 找到能处理该 Handler 的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 处理 Last-Modified(GET/HEAD 请求的缓存优化)
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return; // 304 Not Modified,直接返回
}
}
// 5. 依次执行所有拦截器的 preHandle()
// 若某个 preHandle() 返回 false,逆序执行已通过拦截器的 afterCompletion(),终止流程
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 6. 调用 HandlerAdapter 执行实际的 Handler(Controller 方法)
// 返回 ModelAndView;若 Handler 直接写响应(@ResponseBody),mv 可能为 null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 7. 异步处理判断
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 8. 若 mv 不为 null 且没有视图名,使用 RequestToViewNameTranslator 推导视图名
applyDefaultViewName(processedRequest, mv);
// 9. 逆序执行所有拦截器的 postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 10. 处理结果:渲染视图 或 处理异常(HandlerExceptionResolver)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
// 11. 无论如何,逆序执行 afterCompletion()
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} finally {
// 12. 若是 multipart 请求,清理临时文件
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
processDispatchResult 中的视图渲染
// 简化逻辑
private void processDispatchResult(...) throws Exception {
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
// 交给 HandlerExceptionResolver 链处理
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
}
}
if (mv != null && !mv.wasCleared()) {
// 渲染视图
render(mv, request, response);
}
// 无论成功还是异常,最终都执行 afterCompletion
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
流程时序图
sequenceDiagram
participant C as 客户端
participant DS as DispatcherServlet
participant HM as HandlerMapping
participant I as Interceptor
participant HA as HandlerAdapter
participant H as Handler(Controller)
participant VR as ViewResolver
participant V as View
C->>DS: HTTP 请求
DS->>DS: checkMultipart()
DS->>HM: getHandler(request)
HM-->>DS: HandlerExecutionChain
DS->>DS: getHandlerAdapter(handler)
DS->>I: preHandle() × N(顺序)
I-->>DS: true/false
DS->>HA: handle(request, response, handler)
HA->>H: 调用方法(参数解析、返回值处理)
H-->>HA: 返回值
HA-->>DS: ModelAndView(或 null)
DS->>I: postHandle() × N(逆序)
DS->>VR: resolveViewName(viewName, locale)
VR-->>DS: View 对象
DS->>V: render(model, request, response)
V-->>C: HTTP 响应
DS->>I: afterCompletion() × N(逆序)
HandlerMapping 详解
HandlerMapping 接口只有一个核心方法:
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
getHandler() 返回的 HandlerExecutionChain 包含:
- Handler 对象:通常是
HandlerMethod(封装了 Controller 实例 + Method) - 拦截器列表:
List<HandlerInterceptor>
RequestMappingHandlerMapping
最常用的实现,处理 @RequestMapping(及其派生注解 @GetMapping、@PostMapping 等)。
初始化阶段(afterPropertiesSet()):
- 扫描所有 Bean,找到标注
@Controller或@RequestMapping的类 - 提取每个方法上的
@RequestMapping元信息,封装为RequestMappingInfo - 以
RequestMappingInfo → HandlerMethod的形式注册到内部Map
匹配阶段:
- 根据 URL、HTTP Method、Content-Type、Accept、Header、Param 等条件匹配
- 若有多个匹配,按特异性排序,取最优匹配
// 注册示例:@GetMapping("/user/{id}")
// 内部存储:RequestMappingInfo{"/user/{id}", GET} → HandlerMethod{UserController#getUser}
RouterFunctionMapping
函数式端点的 HandlerMapping,配合 Spring 5.2+ 引入的函数式 Web 框架使用:
@Bean
public RouterFunction<ServerResponse> routes(UserHandler handler) {
return RouterFunctions.route()
.GET("/user/{id}", handler::getUser)
.POST("/user", handler::createUser)
.build();
}
RouterFunctionMapping 从 ApplicationContext 收集所有 RouterFunction<?> Bean,合并为一条路由链,按顺序匹配。
对比:
| 维度 | RequestMappingHandlerMapping | RouterFunctionMapping |
|---|---|---|
| 编程风格 | 注解驱动(声明式) | 函数式(命令式) |
| 路由定义 | @GetMapping 等注解 |
RouterFunctions.route() |
| Handler 类型 | HandlerMethod |
HandlerFunction<ServerResponse> |
| 适配器 | RequestMappingHandlerAdapter |
HandlerFunctionAdapter |
| 适合场景 | 传统 MVC、CRUD | 轻量路由、函数式风格 |
HandlerAdapter 详解
HandlerAdapter 解决的核心问题:DispatcherServlet 不直接调用 Handler,因为 Handler 可以是任意类型(HandlerMethod、HttpRequestHandler、Controller 接口实现等),需要一个适配层。
public interface HandlerAdapter {
// 判断是否能处理该 Handler
boolean supports(Object handler);
// 调用 Handler,返回 ModelAndView(@ResponseBody 场景返回 null)
@Nullable
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
}
三种主要实现
| 实现类 | 支持的 Handler 类型 | 典型场景 |
|---|---|---|
RequestMappingHandlerAdapter |
HandlerMethod(@RequestMapping 方法) |
现代 Spring MVC 控制器 |
HttpRequestHandlerAdapter |
HttpRequestHandler 接口实现 |
静态资源处理(ResourceHttpRequestHandler) |
SimpleControllerHandlerAdapter |
Controller 接口实现 |
遗留 Spring 2.x 风格控制器 |
RequestMappingHandlerAdapter 内部流程
// 简化的 handle() 逻辑
ModelAndView handle(...) {
// 1. 参数解析:HandlerMethodArgumentResolver 链
// 解析 @RequestParam、@PathVariable、@RequestBody、@ModelAttribute 等
Object[] args = resolveHandlerArguments(request, response, handlerMethod);
// 2. 调用目标方法(反射)
Object returnValue = handlerMethod.invokeForRequest(request, mavContainer, args);
// 3. 返回值处理:HandlerMethodReturnValueHandler 链
// @ResponseBody → 直接用 HttpMessageConverter 写响应,不产生视图
// String → 视图名
// ModelAndView → 直接使用
handleReturnValue(returnValue, returnType, mavContainer, request, response);
}
视图解析流程
ViewResolver 链
DispatcherServlet 维护一个 List<ViewResolver>,按 Order 排序,依次调用每个 ViewResolver.resolveViewName(),返回第一个非 null 的 View 对象:
// DispatcherServlet#resolveViewName
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view; // 取第一个非 null 结果
}
}
return null;
}
常用 ViewResolver 对比
| ViewResolver | 适用场景 | 关键配置 |
|---|---|---|
InternalResourceViewResolver |
JSP 模板 | prefix=/WEB-INF/views/,suffix=.jsp |
ThymeleafViewResolver |
Thymeleaf 模板 | 配合 SpringTemplateEngine |
FreeMarkerViewResolver |
FreeMarker 模板 | 配合 FreeMarkerConfigurer |
ContentNegotiatingViewResolver |
根据 Accept 头选择视图格式 | 包装其他 ViewResolver,优先级最高 |
BeanNameViewResolver |
按 Bean 名称查找 View Bean | 用于自定义 View 实现 |
ContentNegotiatingViewResolver 的特殊性
它本身不解析视图,而是委托给其他 ViewResolver,根据客户端的 Accept 请求头选择最合适的视图格式:
Accept: application/json → MappingJackson2JsonView
Accept: text/html → InternalResourceViewResolver → JSP
Accept: application/xml → MarshallingView
拦截器机制
HandlerInterceptor 接口
public interface HandlerInterceptor {
// 在 HandlerAdapter 调用 Handler 之前执行
// 返回 false 时,终止后续流程(不调用 Handler,不调用 postHandle)
default boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
return true;
}
// 在 HandlerAdapter 调用 Handler 之后、视图渲染之前执行
// 可以修改 ModelAndView;若 Handler 抛出异常则不执行
default void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
// 整个请求完成后执行(视图渲染完毕后)
// 无论是否发生异常都会执行(前提:对应的 preHandle 返回过 true)
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception {
}
}
执行顺序
假设注册了 3 个拦截器 I1、I2、I3(按注册顺序):
请求进来:
I1.preHandle() → true
I2.preHandle() → true
I3.preHandle() → true
Handler 执行
I3.postHandle() ← 逆序
I2.postHandle()
I1.postHandle()
视图渲染
I3.afterCompletion() ← 逆序
I2.afterCompletion()
I1.afterCompletion()
关键规则:若 I2.preHandle() 返回 false,则只执行 I1.afterCompletion()(已通过的拦截器逆序执行 afterCompletion),I2 和 I3 的 afterCompletion 不执行。
注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 全局拦截器
registry.addInterceptor(new AuthInterceptor())
.order(1);
// 指定路径的拦截器
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**")
.order(2);
}
}
AsyncHandlerInterceptor
继承自 HandlerInterceptor,增加 afterConcurrentHandlingStarted() 回调,用于异步请求场景(Callable/DeferredResult)。异步请求中,postHandle 和 afterCompletion 不在原始线程执行,需要用 afterConcurrentHandlingStarted 做资源清理。
Filter vs HandlerInterceptor vs AOP MethodInterceptor
Spring MVC 为何不用 Servlet Filter 或 Spring AOP 拦截器,而要自己实现一套 HandlerInterceptor?
| 能力 | Servlet Filter | HandlerInterceptor | AOP MethodInterceptor |
|---|---|---|---|
| 工作层次 | Servlet 容器(DispatcherServlet 外) | DispatcherServlet 内部 | Spring Bean 方法调用层 |
| 拿到 HttpRequest/Response | ✅ | ✅ 直接传参 | ❌ 需绕路 RequestContextHolder |
| 知道由哪个 Controller 方法处理 | ❌ | ✅ HandlerMethod |
❌ |
| 直接终止请求(返回 false) | ✅(不调用 chain.doFilter) | ✅(preHandle 返回 false) | ❌ 需抛异常 |
| 修改 ModelAndView | ❌ | ✅ postHandle | ❌ |
| 视图渲染后执行(afterCompletion) | ❌ | ✅ | ❌ |
| 注入 Spring Bean | ❌(需 DelegatingFilterProxy) | ✅ | ✅ |
| 代理开销 | 无 | 无(DispatcherServlet 直调) | CGLIB/JDK 动态代理 |
| 典型用途 | 跨域、编码、安全框架(Shiro/JWT) | 认证鉴权、操作日志、耗时统计 | 事务、缓存、方法级审计 |
三者不是替代关系,是不同层次的切面:Filter 做通用 HTTP 处理,Interceptor 做 MVC 层控制,AOP 做业务逻辑横切。实际项目中通常混合使用。
与 Spring Boot 的关系
DispatcherServletAutoConfiguration
Spring Boot 通过 spring-boot-autoconfigure 模块自动完成 DispatcherServlet 的注册,核心类:
// org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// 从 spring.mvc.* 配置中读取参数
dispatcherServlet.setDispatchOptionsRequest(
webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(
webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class,
name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfig) {
// 注册到嵌入式 Servlet 容器,默认映射 "/"
DispatcherServletRegistrationBean registration =
new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
}
Spring Boot 与传统 Spring MVC 对比
| 维度 | 传统 Spring MVC(war) | Spring Boot |
|---|---|---|
| Servlet 容器 | 外置(Tomcat/Jetty/WildFly) | 内嵌(默认 Tomcat) |
| DispatcherServlet 注册 | web.xml 或 WebApplicationInitializer |
DispatcherServletAutoConfiguration 自动注册 |
| 配置方式 | @EnableWebMvc + 手动配置 |
spring.mvc.* 属性 + WebMvcConfigurer |
| 部署产物 | .war 文件 |
可执行 .jar 文件 |
| 上下文 | Root + Servlet 两级上下文 | 单一 ApplicationContext(默认) |
注意:Spring Boot 中若使用
@EnableWebMvc,会完全接管 MVC 自动配置,WebMvcAutoConfiguration的默认配置(静态资源处理、消息转换器等)将全部失效。扩展 MVC 配置应实现WebMvcConfigurer而非加@EnableWebMvc。
常见坑
坑 1:@RequestBody 字符编码问题
现象:POST 请求体中的中文乱码。
原因:CharacterEncodingFilter 必须在所有其他 Filter 之前执行,且需要设置 forceRequestEncoding=true。若 request.getCharacterEncoding() 非空(某些容器默认设置了编码),则 CharacterEncodingFilter 默认不会覆盖。
// Spring Boot 中通过配置开启(默认已配置 UTF-8)
// application.properties
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true
server.servlet.encoding.enabled=true
// 手动配置(传统 Spring MVC)
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>();
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceRequestEncoding(true); // 关键:强制覆盖
filter.setForceResponseEncoding(true);
bean.setFilter(filter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 必须最先执行
return bean;
}
@RequestBody使用HttpMessageConverter(如MappingJackson2HttpMessageConverter)读取请求体,编码由Content-Type: application/json;charset=UTF-8头决定,通常不受CharacterEncodingFilter影响。真正受影响的是@RequestParam/ 表单参数。
坑 2:静态资源被 DispatcherServlet 拦截
现象:/static/app.js 返回 404,或被 DispatcherServlet 处理后找不到 Handler。
原因:DispatcherServlet 映射到 / 时,会拦截所有请求,包括静态资源。
解决方案:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 映射 /static/** 到 classpath:/static/
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
Spring Boot 的 WebMvcAutoConfiguration 默认已配置:/webjars/**、/** 到 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/。
坑 3:拦截器中使用 @Transactional 无效
原因:HandlerInterceptor 的 preHandle 在 RequestMappingHandlerAdapter 调用 Handler 之前执行,此时事务上下文尚未建立(事务由 AOP 在 Controller 方法调用时开启)。在拦截器中操作数据库应使用 TransactionTemplate 编程式事务。
坑 4:postHandle 在 @ResponseBody 场景不渲染视图
原因:@ResponseBody 方法返回值由 HttpMessageConverter 直接写入响应流,ModelAndView 为 null,视图渲染步骤被跳过,postHandle 仍会执行但 modelAndView 参数为 null,无法修改视图。
坑 5:多个 ViewResolver 顺序问题
现象:配置了 Thymeleaf 和 JSP,但总是走 JSP 解析。
原因:InternalResourceViewResolver 的 Order 值较小(优先级高),或未设置 Order(默认 Integer.MAX_VALUE,优先级最低,但 InternalResourceViewResolver 在没有找到模板时也会返回 View 对象而非 null,从而"吞掉"后续解析机会)。
解决:始终给 InternalResourceViewResolver 设置最低优先级(最大 Order 值):
@Bean
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(Ordered.LOWEST_PRECEDENCE); // 最后才检查 JSP
return resolver;
}
坑 6:@ExceptionHandler 的作用域
@ExceptionHandler 方法在 @Controller 类中只对该类的 Handler 异常生效。要全局处理,需使用 @ControllerAdvice:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ApiResponse<Void> handleBusinessException(BusinessException ex) {
return ApiResponse.error(ex.getCode(), ex.getMessage());
}
}
参考资料
- Spring Framework Reference — Web on Servlet Stack(请自行验证可访问性)
- Spring Framework Reference — DispatcherServlet Special Bean Types(请自行验证可访问性)
- Spring Framework Reference — Interception(请自行验证可访问性)
- 《Spring in Action, 6th Edition》— Craig Walls,Manning Publications
- 《Expert One-on-One J2EE Design and Development》— Rod Johnson,Wrox(Front Controller Pattern 原始参考)
评论 (0)
发表评论