简介

SpringWebMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于处理器进行预处理和后处理。

应用场景

  1、日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等等。
  2、权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。
  3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

  4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

  5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。

  本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。

  HandlerInterceptor:

public interface HandlerInterceptor {

   /**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);
   * false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
*/
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
* 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finall
   * 但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion。
*/
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
  拦截器适配器HandlerInterceptorAdapter
  有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,不管你需不需要,此时spring提供了一个HandlerInterceptorAdapter适配器(种适配器设计模式的实现),允许我们只实现需要的回调方法。

public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
//省略代码 此处所以三个回调方法都是空实现,preHandle返回true。
}

  HandlerInterceptorAdapter类:

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {

    //默认是true
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { return true;
}
//This implementation is empty.
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
//This implementation is empty.
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
//不是HandlerInterceptor的接口实现,是AsyncHandlerInterceptor的,AsyncHandlerInterceptor实现了HandlerInterceptor
@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
} }

运行流程图

      

      

  运行流程总结如下:

  1、拦截器执行顺序是按照Spring配置文件中定义的顺序而定的。

  2、会先按照顺序执行所有拦截器的preHandle方法,一直遇到return false为止,比如第二个preHandle方法是return false,则第三个以及以后所有拦截器都不会执行。若都是return true,则按顺序加载完preHandle方法。

  3、然后执行主方法(自己的controller接口),若中间抛出异常,则跟return false效果一致,不会继续执行postHandle,只会倒序执行afterCompletion方法。

  4、在主方法执行完业务逻辑(页面还未渲染数据)时,按倒序执行postHandle方法。若第三个拦截器的preHandle方法return false,则会执行第二个和第一个的postHandle方法和afterCompletion(postHandle都执行完才会执行这个,也就是页面渲染完数据后,执行after进行清理工作)方法。(postHandle和afterCompletion都是倒序执行)

DispatcherServlet内部工作机制

//doDispatch方法
//1、处理器拦截器的预处理(正序执行)
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
//1.1、失败时触发afterCompletion的调用
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return
;
}
interceptorIndex = i;//1.2、记录当前预处理成功的索引
}
}
//2、处理器适配器调用我们的处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//当我们返回null或没有返回逻辑视图名时的默认视图名翻译(详解4.15.5 RequestToViewNameTranslator)
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
//3、处理器拦截器的后处理(逆序)
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
//4、视图的渲染
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
//5、触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
// triggerAfterCompletion方法
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler, int interceptorIndex,
HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
// 5、触发整个请求处理完毕回调方法afterCompletion (逆序从1.2中的预处理成功的索引处的拦截器执行)
if (mappedHandler != null) {
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}

应用示例

  Intercepter:

public class SyslogInterceptor implements HandlerInterceptor {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("==========================================pre1================================================");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("==========================================post1================================================");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("==========================================after1================================================");
}
}

public class SyslogInterceptor implements HandlerInterceptor {
    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("==========================================pre2================================================");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("==========================================post2================================================");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("==========================================after2================================================");
}
}

  ... ...,然后是配置文件

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bshf.recipe.demo.syslog.SyslogInterceptor" />
</mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bshf.recipe.demo.syslog.SyslogInterceptor2" />
</mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bshf.recipe.demo.syslog.SyslogInterceptor3" />
</mvc:interceptor>
</mvc:interceptors>

  <mvc:mapping path="/**"/>需要拦截的路径,这里匹配所有。这里的配置顺序将是拦截器执行顺序(post和after是倒序的)。

==========================================pre1================================================
==========================================pre2================================================
==========================================pre3================================================
==========================================post3================================================
==========================================post2================================================
==========================================post1================================================
==========================================after3================================================
==========================================after2================================================
==========================================after1================================================

  将SyslogInterceptor3的preHandle方法返回false。其他不变,再看输出结果:

==========================================pre1================================================
==========================================pre2================================================
==========================================pre3================================================
==========================================after2================================================
==========================================after1================================================

  发现没有了post,post方法一个都没有输出。不仅如此,你的主程序代码(xxx.controller的一个接口)都不会被执行,所以只要遇到return false,则直接进行after清理工作,post和自己主程序代码都将被略过。

应用

  1、性能监测

  如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。

  实现分析:

  1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;

  2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。

  问题:

  我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?

  解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。

  代码实现:

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {
private NamedThreadLocal<Long> startTimeThreadLocal =
new NamedThreadLocal<Long>("StopWatch-StartTime"
);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
long beginTime = System.currentTimeMillis();//1、开始时间
startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
return true;//继续流程
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();//2、结束时间
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;//3、消耗的时间
if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
//TODO 记录到日志文件
System.out.println(
String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}

  NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。在测试时需要把stopWatchHandlerInterceptor放在拦截器链的第一个,这样得到的时间才是比较准确的。

  2、登陆检测

  在访问某些资源时(如订单页面),需要用户登录后才能查看,因此需要进行登录检测。流程:

  1、访问需要登录的资源时,由拦截器重定向到登录页面;

  2、如果访问的是登录页面,拦截器不应该拦截;

  3、用户登录成功后,往cookie/session添加登录成功的标识(如用户编号);

  4、下次请求时,拦截器通过判断cookie/session中是否有该标识来决定继续流程还是到登录页面;

  5、在此拦截器还应该允许游客访问的资源。

  代码实现:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//1、请求到登录页面 放行
if(request.getServletPath().startsWith(loginUrl)) {
return true;
} //2、TODO 比如退出、首页等页面无需登录,即此处要放行 允许游客的请求 //3、如果用户已经登录 放行
if(request.getSession().getAttribute("username") != null) {
//更好的实现方式的使用cookie
return true;
} //4、非法请求 即这些请求需要登录后才能访问
//重定向到登录页面
response.sendRedirect(request.getContextPath() + loginUrl);
return false;
}

  提示:推荐能使用servlet规范中的过滤器Filter实现的功能就用Filter实现,因为HandlerInteceptor只有在Spring Web MVC环境下才能使用,因此Filter是最通用的、最先应该使用的。如登录这种拦截器最好使用Filter来实现。

处理器拦截器(HandlerInterceptor)详解(转)的更多相关文章

  1. springboot拦截器HandlerInterceptor详解

    Web开发中,我们除了使用 Filter 来过滤请web求外,还可以使用Spring提供的HandlerInterceptor(拦截器). HandlerInterceptor 的功能跟过滤器类似,但 ...

  2. 拦截器 应用详解--SpringMVC

    在实际项目中,拦截器的使用是非常普遍的,例如在购物网站中通过拦截器可以拦截未登录的用户,禁止其购买商品,或者使用它来验证已登录用户是否有相应的操作权限等,Spring MVC提供了拦截器功能,通过配置 ...

  3. mybatis Interceptor拦截器代码详解

    mybatis官方定义:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...

  4. Flume 拦截器(interceptor)详解

    flume 拦截器(interceptor)1.flume拦截器介绍拦截器是简单的插件式组件,设置在source和channel之间.source接收到的事件event,在写入channel之前,拦截 ...

  5. Spring 注解拦截器使用详解

    Spring mvc拦截器 平时用到的拦截器通常都是xml的配置方式.今天就特地研究了一下注解方式的拦截器. 配置Spring环境这里就不做详细介绍.本文主要介绍在Spring下,基于注解方式的拦截器 ...

  6. axios 源码解析(下) 拦截器的详解

    axios的除了初始化配置外,其它有用的应该就是拦截器了,拦截器分为请求拦截器和响应拦截器两种: 请求拦截器    ;在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要 ...

  7. 处理器拦截器(HandlerInterceptor)详解

    处理器拦截器(HandlerInterceptor)详解 编程界的小学生 关注 2017.04.06 15:19* 字数 881 阅读 657评论 0喜欢 4 简介SpringWebMVC的处理器拦截 ...

  8. [转载] Spring MVC - 处理器拦截器

    5.1.处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器)类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理.   ...

  9. SpringMVC 学习-拦截器 HandlerInterceptor 类

    一.拦截器 HandlerInterceptor 类的作用 SpringMVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理. 二.怎么使用呢? 1. ...

随机推荐

  1. 18 Flutter仿京东商城项目 商品详情顶部tab切换 顶部下拉菜单 底部浮动导航

    ProductContent.dart import 'package:flutter/material.dart'; import '../services/ScreenAdaper.dart'; ...

  2. 15 Flutter仿京东商城项目 搜索页面布局

    Search.dart import 'package:flutter/material.dart'; import 'package:flutter_jdshop/services/ScreenAd ...

  3. Qt编写安防视频监控系统5-视频回放

    一.前言 一般视频回放都会采用GB28181国标来处理,这样可以保证兼容国内各大厂家的NVR,毕竟在同一的国家标准下,大家都会统一支持国标的,就不需要根据各个厂家的SDK来做兼容处理,烦得很,厂家越来 ...

  4. cached占比过高

    Linux手动释放缓存的方法Linux释放内存的命令:syncecho 1 > /proc/sys/vm/drop_caches drop_caches的值可以是0-3之间的数字,代表不同的含义 ...

  5. Scrapy框架(1)

    参考博客: 10.scrapy框架简介和基础应用 Scrapy框架(1)

  6. web框架之socket

    概述 套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信.这就是所谓的进程间通信 ( Inter Process Communication, ...

  7. scalaTest的初步使用

    1. 概述 ScalaTest是scala生态系统中最流行和灵活的测试工具,可以测试scala.js.java代码. 2. ScalaTest的特性 a. ScalaTest的核心是套件(suite) ...

  8. Public thanks to Shao Qirui for his contribution to open source software

    Public thanks to Shao Qirui for his contribution to open source softwareShao Qirui is a student, but ...

  9. POJ 1734:Sightseeing trip

    Sightseeing trip Time Limit: 1000MS Memory Limit: 65536K Total Submissions: Accepted: Special Judge ...

  10. [转帖].NET导出Excel的四种方法及评测

    .NET导出Excel的四种方法及评测 https://www.cnblogs.com/sdflysha/p/20190824-dotnet-excel-compare.html 导出Excel是.N ...