1.前言

SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC拦截器的工作原理并自定义拦截器

2.源码分析

进入SpringMVC核心类DispatcherServlet的doDispatch方法,在SpringMVC源码阅读:核心分发器DispatcherServlet曾经分析过,这里再分析一遍

936行获得HandlerExecutionChain,含有HandlerMethod和interceptorList

943行根据HandlerExecutionChain获取RequestMappingHandlerAdapter

958行如果HandlerExecutionChain需要执行下一个拦截器,则返回True

963HandlerAdapter调用Handler处理请求,可以看到,请求方法夹在applyPreHandle和applyPostHandle之间

980行processDispatchResult会调用triggerAfterCompletion,不抛出异常

983和986行调用triggerAfterCompletion会抛出异常

重点看下936行getHandler方法,点进去

1156行HandlerMapping调用getHandler方法获取HandlerExecutionChain

对着getHandler ctrl+alt+b跳转到HandlerMapping接口方法实现处,在AbstractHandlerMapping类中

352行获取HandlerMethod

365行获取HandlerExecutionChain

366行对跨域请求进行拦截处理

点进去365行的getHandlerExecutionChain方法

417行获取requestmapping请求路径

419行判断HandlerInterceptor是不是MappedInterceptor类型,不是则直接向HandlerExecutionChain加入HandlerInterceptor

HandlerInterceptor是MappedInterceptor类型,需要检验是否匹配,最后向HandlerExecutionChain加入HandlerInterceptor

getCorsHandlerExecutionChain方法获取跨域的HandlerExecutionChain和getHandlerExecutionChain同理,园友可自行分析

现在看看HandlerExecutionChain

主要看applyPostHandle、applyPreHandle和triggerAfterCompletion方法

打开applyPostHandle方法

130行接收HandlerInterceptor数组

134行HandlerInterceptor的preHandle方法执行失败依然会执行HandlerExecutionChain的triggerAfterCompletion方法

triggerAfterCompletion方法在所有拦截器preHandle方法成功执行返回True后才会执行(触发afterCompletion)

3.实例

设置自定义拦截器,继承HandlerInterceptorAdapter

public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 获得请求路径的uri
String uri = request.getRequestURI(); // 判断路径是登出还是登录验证,是这两者之一的话执行Controller中定义的方法
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
return true;
} // 进入登录页面,判断session中是否有key,有的话重定向到首页,否则进入登录界面
if(uri.endsWith("/login/") || uri.endsWith("/login")) {
if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
response.sendRedirect(request.getContextPath() + "/index");
} else {
return true;
}
} // 其他情况判断session中是否有key,有的话继续用户的操作
if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
return true;
} // 最后的情况就是进入登录页面
response.sendRedirect(request.getContextPath() + "/login/login");
return false;
} }

测试Controller

@Controller
@RequestMapping(value = "/login")
public class LoginController { //@RequestMapping(value = {"/", ""})
@RequestMapping(value = {"login"})
@ResponseBody
public String login() {
return "login";
} @RequestMapping("/auth")
public String auth(@RequestParam String username, HttpServletRequest req) {
req.getSession().setAttribute("loginUser", username);
return "redirect:/";
} @RequestMapping("/out")
public String out(HttpServletRequest req) {
req.getSession().removeAttribute("loginUser");
return "redirect:/login/login";
} }

在dispatcher-servlet.xml配置拦截器

因为我们使用了<mvc:annotation-driven/>注解,SpringMVC源码阅读:Json,Xml自动转换提到过

<mvc:annotation-driven/>自动帮我们注册了

  1. RequestMappingHandlerMapping
  2. RequestMappingHandlerAdapter
  3. ExceptionHandlerExceptionResolver

所以只要在RequestMappingHandlerMapping中配置interceptors属性

interceptors属性来自于RequestMappingHandlerMapping的父类AbstractHandlerMapping

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<bean class="org.format.demo.interceptor.LoginInterceptor"/>
</property>
</bean>

现在浏览器输入任何路径,都会跳转到http://localhost:8080/springmvcdemo/login/login,说明拦截器已经生效

浏览器输入http://localhost:8080/springmvcdemo/login/auth?username=ss,给HttpSession设置Attribute,返回主界面

浏览器输入http://localhost:8080/springmvcdemo/login/out,将HttpSession的Attribute移除,重定向到http://host:port/contextPath/login/login

我们还可以通过<mvc:interceptors>标签来配置拦截器,此时不需要再配置RequestMappingHandlerMapping的interceptors属性

    <mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login/out"/>
<mvc:exclude-mapping path="/login/auth"/>
<bean class="org.format.demo.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

LoginInterceptor这段代码和mvc:exclude-mapping功能一致,是指不拦截的请求路径,可以注释掉

运行效果和刚才一致

讲一下为什么可以这么配置拦截器,mvc:interceptors由InterceptorsBeanDefinitionParser解析,该类实现了BeanDefinitionParser,我在SpringMVC源码阅读:Json,Xml自动转换分析过mvc:annotation-driven如何由AnnotationDrivenBeanDefinitionParser解析,道理是类似的,

核心方法是parse,在InterceptorsBeanDefinitionParser的parse方法打断点验证一下

和我们用mvc:interceptors标签配置的内容一致

4.总结

HandlerExecutionChain由Handler对象和Handler拦截器组成,由HandlerMapping的getHandler方法返回,RequestMappingHandlerMapping将adaptedInterceptors传递给HandlerExecutionChain的interceptorList

HandlerInterceptor接口允许自定义Handler执行链,并为Handler注册已存在或者自定义的拦截器

AbstractHandlerMapping是HandlerMapping的抽象类,支持优先级排序、默认的Handler和handler拦截器

HandlerMapping根据请求信息调用getHandler方法获取HandlerExecutionChain

DispatcherServlet的doDispatch方法处理HandlerExecutionChain,该类含有HandlerMethod和interceptorList,在applyPreHandle和applyPostHandle方法之间调用Handler,triggerAfterCompletion最后运行

5.参考

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

https://docs.spring.io/spring/docs/current/javadoc-api/

https://github.com/spring-projects/spring-framework

文中难免有不足,还望指出

SpringMVC源码阅读:拦截器的更多相关文章

  1. springMVC源码分析--拦截器HandlerExecutionChain(三)

    上一篇博客springMVC源码分析--HandlerInterceptor拦截器调用过程(二)中我们介绍了HandlerInterceptor的执行调用地方,最终HandlerInterceptor ...

  2. SpringMVC源码阅读:核心分发器DispatcherServlet

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...

  3. SpringMVC源码阅读:视图解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  4. SpringMVC源码阅读:异常解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  5. SpringMVC源码阅读:过滤器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  6. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  7. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  8. SpringMVC源码阅读:定位Controller

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...

  9. SpringMVC源码阅读:Controller中参数解析

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

随机推荐

  1. CSS 基础 例子 盒子模型及外边距塌陷

    我们通常设置的宽度和高度,是指盒子模型中内容(content)的宽度和高度.元素的高度,还要加上上下padding和上下border,元素整个盒子的高度还要加上上下margin:宽度类似计算. 注意: ...

  2. Android-多线程安全问题-synchronized

    先看一个售票案例Demo,多线程程序对共享数据操作引发的安全问题: package android.java.thread09; /** * 售票线程 */ class Booking impleme ...

  3. 关于getProperties的一点记录

    写了一很简单的获取配置文件的代码,结果怎么都在报空指针,经过上网查,直到要这样写才不会报: InputStream is = getClass().getClassLoader().getResour ...

  4. 使用System.Net.Mail中的SMTP发送邮件(带附件)

    System.Net.Mail 使用简单邮件传输协议SMTP异步发送邮件 想要实现SMTP发送邮件,你需要了解这些类 SmtpClient :使用配置文件设置来初始化 SmtpClient类的新实例. ...

  5. windform 重绘Treeview "+-"号图标

    模仿wind系统界面,重绘Treeview + - 号图标 一,首先需要图片 ,用于替换原有的 +-号 二.新建Tree扩展类 TreeViewEx继承TreeView using System; u ...

  6. 01_python_初始python

    一.初始python python是一门解释型语言,弱类型语言 / python解释器最为常用的是cpython(官方) 弱类型语言:   a = 1 a = 'alex'   #说明变量a既可以是整 ...

  7. Python 生成器的使用(yield)

    一. 生成器就是一个特殊的迭代器, 使用关键字yield就可以生成一个生成器 def func(): for i in range(10): yield i item = func() yield i ...

  8. centos7搭建kafka集群-第二篇

    好了,本篇开始部署kafka集群 Zookeeper集群搭建 注:Kafka集群是把状态保存在Zookeeper中的,首先要搭建Zookeeper集群(也可以用kafka自带的ZK,但不推荐) 1.软 ...

  9. Vue的声明周期

    以下简单介绍,以自己的理解进行分析.如有不好,请大牛勿喷!!!!!! new Vue() 创建 Vue 实例 beforeCreate(){}: 第一生命周期 表示实例完全创建出来,此函数执行是,da ...

  10. 在Ubuntu下编译安装nginx

    一.安装nginx 1.安装前提 a)epoll,linux内核版本为2.6或者以上 b)gcc编译器,g++编译器 c)pcre库,函数库,支持解析正则表达式 d)zlib库:压缩解压功能 e)op ...