[SSM]详解 SpringMVC 中的拦截器异常处理器与执行流程及两种配置方式
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SSM]详解 SpringMVC 中的拦截器异常处理器与执行流程及两种配置方式相关的知识,希望对你有一定的参考价值。
SpringMVC
一、拦截器
1. 拦截器的配置与使用
- SpringMVC 中的拦截器用于拦截控制器方法的执行,自定义拦截器需要实现
HandlerInterceptor
接口,拦截器必须在 SpringMVC 的配置文件中进行配置preHandle
:控制器方法执行之前执行preHandle()
,其boolean
类型的返回值表示是否调用相应的控制器方法postHandle
:控制器方法执行之后执行postHandle()
afterComplation
:处理完视图和模型数据,视图渲染完毕之后执行afterComplation()
- 自定义 Java 类实现拦截器
/**
* @author Spring-_-Bear
* @datetime 2022/4/12 15:47
*/
@Component
public class FirstInterceptor implements HandlerInterceptor
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
System.out.println("FirstInterceptor preHandle()");
return true;
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
System.out.println("FirstInterceptor postHandle()");
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
System.out.println("FirstInterceptor afterCompletion()");
- mvc.xml 中配置拦截器
<!-- 配置拦截器方式一:以下两种配置方式等价,对所有经 DispatcherServlet 处理后的请求进行拦截 -->
<mvc:interceptors>
<bean class="com.bear.mvc.interceptor.FirstInterceptor"/>
<!-- 需给对应的拦截器类加上 @Component 注解 -->
<ref bean="fileController"/>
</mvc:interceptors>
<!-- 配置拦截器方式二:拦截除首页的所有请求 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截所有请求,两个 ** -->
<mvc:mapping path="/**"/>
<!-- 排除 / 请求 -->
<mvc:exclude-mapping path="/"/>
<ref bean="fileController"/>
</mvc:interceptor>
</mvc:interceptors>
2. 多个拦截器的执行顺序
- 若每个拦截器的
preHandle()
都返回true
,则多个拦截器的执行顺序与拦截器在 SpringMVC 的配置文件的配置顺序有关,preHandle()
会按照配置的顺序执行,而postHandle()
和afterComplation()
会按照配置的反序执行
源码查看,剖析原因
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception
for (int i = 0; i < this.interceptorList.size(); i++)
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler))
triggerAfterCompletion(request, response, null);
return false;
this.interceptorIndex = i;
return true;
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception
for (int i = this.interceptorList.size() - 1; i >= 0; i--)
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
for (int i = this.interceptorIndex; i >= 0; i--)
HandlerInterceptor interceptor = this.interceptorList.get(i);
try
interceptor.afterCompletion(request, response, this.handler, ex);
catch (Throwable ex2)
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
- 若某个拦截器的
preHandle()
返回了false
,preHandle()
返回false
和它之前的拦截器的preHandle()
都会执行,postHandle()
都不执行,返回false
的拦截器之前的拦截器的afterComplation()
会执行(例如以下示例:第四个拦截器返回 false)
二、异常处理器
SpringMVC 提供了一个处理控制器方法执行过程中所出现的异常的接口:
HandlerExceptionResolver
,其常用实现类有:DefaultHandlerExceptionResolver
(SpringMVC 使用) 和SimpleMappingExceptionResolver
(工程师自定义使用)
3. 基于配置文件处理控制器方法异常
<!-- 配置异常处理器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- key 匹配遇到的异常类型 标签值表示对应的视图页面 -->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!-- 为异常信息设置一个属性名,值为 ex,默认在 request 域中进行共享 -->
<property name="exceptionAttribute" value="ex"/>
</bean>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Error</title>
</head>
<body>
服务器开小差了<br/>
异常信息:<span th:text="$ex"></span>
</body>
</html>
4. 基于注解方式处理控制器方法异常
/**
* @author Spring-_-Bear
* @datetime 2022/4/12 17:15
*/
@ControllerAdvice
public class ExceptionHandler
@org.springframework.web.bind.annotation.ExceptionHandler(value = ArithmeticException.class, NullPointerException.class)
public ModelAndView handleException(Exception ex)
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("ex", ex);
return modelAndView;
三、SpringMVC 执行流程详解
5. SpringMVC 常用组件
组件名 | 描述 | 作用 |
---|---|---|
DispatcherServlet | 前端控制器,不需要工程师开发,由框架提供 | 统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求 |
HandlerMapping | 处理器映射器,不需要工程师开发,由框架提供 | 根据请求的 url 、method 等信息查找 Handler ,即控制器方法 |
Handler | 处理器(控制器),需要工程师开发 | 在 DispatcherServlet 的控制下通过 Handler 对具体的用户请求进行处理 |
HandlerAdapter | 处理器适配器,不需要工程师开发,由框架提供 | 通过 HandlerAdapter 对处理器(控制器方法)进行执行 |
ViewResolver | 视图解析器,不需要工程师开发,由框架提供 | 进行视图解析,得到相应的视图,例如:ThymeleafView 、InternalResourceView 、RedirectView |
View | 视图即页面 | 将模型数据通过页面展示给用户 |
6. WebApplicationContext 上下文对象初始化流程
DispatcherServlet
的继承体系和执行流程
- 首先初始化上下文对象
context
:调用FrameworkServlet
类中的initWebApplicationContext
方法实现context
对象的初始化- 在初始化方法中先调用其同类的方法
createWebApplicationContext
创建context
对象- 接着调用刷新上下文对象的方法
onRefresh(wac)
,该方法在其子类DispatcherServlet
中进行了重写并调用了initStrategies(context)
方法初始化策略,即初始化各个组件
org.springframework.web.servlet.FrameworkServlet
/**
* 初始化 web 应用程序上下文对象
*/
protected WebApplicationContext initWebApplicationContext()
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null)
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext)
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive())
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null)
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
configureAndRefreshWebApplicationContext(cwac);
if (wac == null)
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
if (wac == null)
// No context instance is defined for this servlet -> create a local one
// 创建 WebApplicationContext
wac = createWebApplicationContext(rootContext);
if (!this.refreshEventReceived)
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor)
// 刷新 WebApplicationContext
onRefresh(wac);
if (this.publishContext)
// Publish the context as a servlet context attribute.
// 将 IOC 容器在应用域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
return wac;
/**
* 初始化 web 应用程序上下文对象
*/
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent)
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
// 通过反射创建 IOC 容器对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null)
wac.setConfigLocation(configLocation);
configureAndRefreshWebApplicationContext(wac);
return wac;
org.springframework.web.servlet.DispatcherServlet
@Override
protected void onRefresh(ApplicationContext context)
initStrategies(context);
/**
* 初始化各个组件
*/
protected void initStrategies(ApplicationContext context)
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
调用流程图
7. DispatcherServlet 调用组件处理请求流程
FrameworkServlet
重写HttpServlet
中的service()
和doXxx()
,这些方法中调用了FrameServlet
类中的processRequest(request, response)
,在此方法中又调用了doService(request, response)
方法处理请求(子类DispatcherServlet
进行了重写)
org.springframework.web.servlet.FrameworkServlet
// org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try
// 执行服务,doService() 是一个抽象方法,在 DispatcherServlet 中进行了重写
doService(request, response);
catch (ServletException | IOException ex)
failureCause = ex;
throw ex;
catch (Throwable ex)
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
finally
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null)
requestAttributes.requestCompleted();
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
DispatcherServlet
中的doService()
方法调用同类的doDispatch()
方法分发处理各请求
org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request))
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements())
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX))
attributesSnapshot.put(attrName, request.getAttribute(attrName));
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null)
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null)
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, 以上是关于[SSM]详解 SpringMVC 中的拦截器异常处理器与执行流程及两种配置方式的主要内容,如果未能解决你的问题,请参考以下文章
学习笔记——SpringMVC拦截器的两种装配方式;SpringMVC拦截器工作原理;SpringMVC中的异常处理器;SpringMVC工作原理