09.SpringMVC 拦截器 - HandlerInterceptor

基本概念

Interceptor(即处理器拦截器、拦截器),类似于 Servlet 开发中的过滤器 Filter,用于对处理器(Controller)进行预处理和后处理。

常见的应用场景:

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

  • 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面。

  • 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录)。

  • 通用行为:读取 cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用。


拦截器类型

1.HandlerInterceptor

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;

    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}

preHandle

  • 预处理回调方法,实现处理器的预处理(即在处理方法被调用之前生效)。

  • 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断,不会继续调用其他的拦截器或处理器,此时需要通过response来产生响应;

postHandle

  • 后处理回调方法,实现处理器的后处理(但在渲染视图之前)。

  • 此时可以通过 modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView 也可能为 null。

afterCompletion

  • 整个请求处理完毕回调方法,即在视图渲染完毕时回调。

  • 类似于try-catch-finally中的finally,但仅调用处理器执行链中 preHandle 返回 true 的拦截器的 afterCompletion。


2.WebRequestInterceptor

public interface WebRequestInterceptor {

    void preHandle(WebRequest request) throws Exception;

    void postHandle(WebRequest request, ModelMap model) throws Exception;

    void afterCompletion(WebRequest request, Exception ex) throws Exception;
}
  • 与HandlerInterceptor接口类似,区别是 WebRequestInterceptor 的 preHandle 没有返回值。

  • WebRequestInterceptor 是针对请求的,接口方法参数中没有response。


3.ConversionServiceExposingInterceptor

// <mvc:annotation-driven /> 该标签初始化的时候会初始化这个拦截器
// 作用是暴露 conversionService 到请求中以便如<spring:eval>标签使用

4.ResourceUrlProviderExposingInterceptor

// 该拦截器用于静态资源访问,定义了以下的标签,在该标签初始化的时候会初始化这个拦截器
"/js/**" location="/js/" />

实例探究

1.权限控制

public class LoginInterceptor extends HandlerInterceptorAdapter {

    // 重写父类的方法(父类默认返回 true)
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handelr) throws Exception {
        return loginValidate(request, response);
    }

    // 登录验证
    private boolean loginValidate(HttpServletRequest request, HttpServletResponse response) throws IOException {

        // 存在 session 信息返回 true
        if (request.getSession().getAttribute("username") != null) {
            return true;
        }

        // 不存在重定向到错误页面,并返回 false
        response.sendRedirect(request.getContextPath()+"/login/error");
        return false;
    }

}

2.性能监控

public class HelloInterceptor extends HandlerInterceptorAdapter {

    private Logger log = Logger.getLogger(HelloInterceptor.class); 
    private NamedThreadLocal startTimeThreadLocal = new NamedThreadLocal("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handelr) throws Exception {
        // 开始时间
        long beginTime = System.currentTimeMillis();

        // 线程绑定变量(该数据只有当前请求的线程可见)
        startTimeThreadLocal.set(beginTime);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
     throws Exception {
        // 计算页面加载时间
        long endTime = System.currentTimeMillis();
        long beginTime = startTimeThreadLocal.get();
        long consumeTime = endTime - beginTime;

        // 大于 500 毫秒表示请求为慢请求
        if(consumeTime > 500){
            // 记录日志
            log.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
    }
}

源码分析

1.拦截器解析过程

首先来拦截器在 xml 中的配置。


    
        "/login/**"/>
        "/index/**"/>
        "com.interceptor.LoginInterceptor" />
    

拦截器会在 springmvc 初始化过程中被解析,然后被装载进 springmvc 的 Ioc 容器。具体的实现发生在 InterceptorsBeanDefinitionParser 类的 parse 方法。

ublic BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compDefinition = 

    parserContext.pushContainingComponent(compDefinition);

    RuntimeBeanReference pathMatcherRef = null;
    if (element.hasAttribute("path-matcher")) {
        pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
    }

    // 取得  的子元素 ,即 
    List interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");

    // 遍历所有的 ,即拦截器
    for (Element interceptor : interceptors) {

        // 将拦截器统一注册成 MappedInterceptor 类型
        RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
        mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
        mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

        ManagedList includePatterns = null;
        ManagedList excludePatterns = null;
        Object interceptorBean;
        if ("interceptor".equals(interceptor.getLocalName())) {
            // 对应 
            includePatterns = getIncludePatterns(interceptor, "mapping");
            // 对应 
            excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
            // 取得  的子元素  的信息
            Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
            interceptorBean = parserContext.getDelegate().parsePropertySubElement(beanElem, null);
        }else {
            interceptorBean = parserContext.getDelegate().parsePropertySubElement(interceptor, null);
        }
        mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns);
        mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
        mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);

        if (pathMatcherRef != null) {
            mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
        }

        // 注册到 IOC 容器
        String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
        parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
    }

    parserContext.popAndRegisterContainingComponent();
    return null;
}

2.拦截器执行过程

拦截器的执行过程属于 springmvc 请求转发的一部分。

首先找到 DispatcherServlet 的 doDispatch 方法,该方法负责实现 springmvc 的请求转发,同时也包含了 interceptor 的执行过程。

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 {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 取得拦截器
                mappedHandler = getHandler(processedRequest);

                // 省略部分代码...

                // ① PreHandle ③ AfterCompletion
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 调用处理器(controller )匹配请求路径的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // 省略部分代码...

                // 取得默认的视图名(即返回的页面路径)
                applyDefaultViewName(processedRequest, mv);

                // ② PostHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }

            // ③渲染视图(即生成 html 页面)
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            // ③ AfterCompletion
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            // ③ AfterCompletion
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

观察代码以及注释,分析如下:

springmvc 的请求转发过程大概可分为:匹配请求路径 -> 执行处理器的对应方法 -> 获取视图名称 -> 渲染视图。

interceptor 的执行过程为:PreHandle,AfterCompletion (处理器方法执行之前)-> PostHandle(获取视图名称之后)-> AfterCompletion(渲染视图之后)。

值得注意的是 AfterCompletion 这个方法,它在多个地方(即不同的条件下)都会调用到,下面会详细讲到。


下面来看 mappedHandler.applyPreHandle ,在该方法中拦截器会调用它的 preHandle 方法 。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();

    if (!ObjectUtils.isEmpty(interceptors)) {
        // 按照拦截器定义的顺序依次执行
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            // 执行 preHandle 方法
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 一旦存在返回 false,触发该方法【逆序】执行【前面所有拦截器】的 afterCompletion 方法
                triggerAfterCompletion(request, response, null);
                return false;
            }

            // 关键 --> 只有在 preHandle 方法返回 true 时才执行
            // 在 triggerAfterCompletion 会用到
            this.interceptorIndex = i;
        }
    }
    return true;
}

再来看 triggerAfterCompletion 这个方法,会调用到该方法的情况如下:

  • preHandle 返回 false 时

  • applyDefaultViewName(渲染视图,上面提过了)

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    // 省略部分源码...

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
  • 抛出异常

  • 抛出错误

该方法具体的定义如下:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();

    if (!ObjectUtils.isEmpty(interceptors)) {
        // 关键 -> 逆序执行
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

最后来看 mappedHandler.applyPostHandle,该方法会调用拦截器的 postHandle 方法。

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 关键 --> 同样是逆序执行
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

你可能感兴趣的:(SpringMVC,SpringMVC)