[SSM]详解 SpringMVC 中的拦截器异常处理器与执行流程及两种配置方式

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SSM]详解 SpringMVC 中的拦截器异常处理器与执行流程及两种配置方式相关的知识,希望对你有一定的参考价值。

SpringMVC

一、拦截器

1. 拦截器的配置与使用

  1. SpringMVC 中的拦截器用于拦截控制器方法的执行,自定义拦截器需要实现 HandlerInterceptor 接口,拦截器必须在 SpringMVC 的配置文件中进行配置
  2. preHandle:控制器方法执行之前执行 preHandle() ,其 boolean 类型的返回值表示是否调用相应的控制器方法
  3. postHandle:控制器方法执行之后执行 postHandle()
  4. afterComplation:处理完视图和模型数据,视图渲染完毕之后执行 afterComplation()
  1. 自定义 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()");
    

  1. 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. 多个拦截器的执行顺序

  1. 若每个拦截器的 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);
        
    

  1. 若某个拦截器的 preHandle() 返回了 falsepreHandle() 返回 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处理器映射器,不需要工程师开发,由框架提供根据请求的 urlmethod 等信息查找 Handler,即控制器方法
Handler处理器(控制器),需要工程师开发DispatcherServlet 的控制下通过 Handler 对具体的用户请求进行处理
HandlerAdapter处理器适配器,不需要工程师开发,由框架提供通过 HandlerAdapter 对处理器(控制器方法)进行执行
ViewResolver视图解析器,不需要工程师开发,由框架提供进行视图解析,得到相应的视图,例如:ThymeleafViewInternalResourceViewRedirectView
View视图即页面将模型数据通过页面展示给用户

6. WebApplicationContext 上下文对象初始化流程

DispatcherServlet 的继承体系和执行流程

  1. 首先初始化上下文对象 context:调用 FrameworkServlet 类中的 initWebApplicationContext 方法实现 context 对象的初始化
  2. 在初始化方法中先调用其同类的方法 createWebApplicationContext 创建 context 对象
  3. 接着调用刷新上下文对象的方法 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 调用组件处理请求流程

  1. 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);
    

  1. 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 中的拦截器异常处理器与执行流程及两种配置方式的主要内容,如果未能解决你的问题,请参考以下文章

基于 xml 配置文件的入门级 SSM 框架整合

SSM+Maven的JavaWeb项目中的异常的可能性

springmvc和ssh,ssm的区别

学习笔记——SpringMVC拦截器的两种装配方式;SpringMVC拦截器工作原理;SpringMVC中的异常处理器;SpringMVC工作原理

spring ioc的详解?

SpringMVC——拦截器异常处理机制