Spring MVC学习—HandlerInterceptor处理器拦截器机制全解
Posted L-Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC学习—HandlerInterceptor处理器拦截器机制全解相关的知识,希望对你有一定的参考价值。
基于最新Spring 5.x,详细介绍了Spring MVC的HandlerInterceptor处理器拦截器机制,以及它的一系列拦截方法。
本次我们来学习Sring MVC的HandlerInterceptor处理器拦截器机制,HandlerInterceptor和Filter类似,可用于拦截请求,实现比如登陆、鉴权、过滤等自定义扩展的逻辑。
Spring MVC学习 系列文章
Spring MVC学习(1)—MVC的介绍以及Spring MVC的入门案例
Spring MVC学习(2)—Spring MVC中容器的层次结构以及父子容器的概念
Spring MVC学习(3)—Spring MVC中的核心组件以及请求的执行流程
Spring MVC学习(4)—ViewSolvsolver视图解析器的详细介绍与使用案例
Spring MVC学习(5)—基于注解的Controller控制器的配置全解【一万字】
Spring MVC学习(6)—Spring数据类型转换机制全解【一万字】
Spring MVC学习(7)—Validation基于注解的声明式数据校验机制全解【一万字】
Spring MVC学习(8)—HandlerInterceptor处理器拦截器机制全解
Spring MVC学习(9)—项目统一异常处理机制详解与使用案例
Spring MVC学习(10)—文件上传配置、DispatcherServlet的路径配置、请求和响应内容编码
Spring MVC学习(11)—跨域的介绍以及使用CORS解决跨域问题
文章目录
1 拦截器与过滤器
- 处理器拦截器HandlerInterceptor是
Spring MVC
提供的特性,依赖于Spring MVC框架,而不依赖Servlet容器,Filter则是Servlet的特性,属于Servlet
的规范,并且依赖Servlet容器。 - 应用中可以存在多个拦截器形成拦截器链,也可以存在多个过滤器形成过滤器链!
- 拦截器链和过滤器链的预处理和后处理的调用顺序都是相反的,即预处理调用时按照链从前向后调用,而后处理调用时则按照链从后向前调用。
- 过滤器可用于对所有到达该应用的请求进行拦截,而拦截器则只能对通过DispatcherServlet进行处理的请求进行拦截。
- 在Request请求到达Servlet之前执行过滤器的预处理逻辑,在请求到达DispatcherServlet之后、执行Handler之前执行拦截器的预处理逻辑,并在成功执行Handler之后执行拦截器的后处理逻辑,在DispatcherServlet返回之后最后执行过滤器的后处理逻辑。
- Filter在doFilter一个方法中定义预处理和后处理逻辑,在方法中通过
filterChain.doFilter
进行分隔,而HandlerInterceptor将预处理和后处理逻辑拆分成两个方法,即preHandle、postHandle
方法。 - Filter有该类本身的初始化和销毁的回调方法,即
init和destroy
,而HandlerInterceptor则没有,但是HandlerInterceptor拥有afterCompletion
处理方法,无论有没有抛出异常,在DispatcherServlet请求处理的最后都会执行!
所以说,很多功能其实Filter和HandlerInterceptor都能做,具体看你怎么选了,如果使用Spring MVC框架,那么建议使用HandlerInterceptor吧,它可以类似于普通bean直接注册到Spring容器中被管理。
关于Filter过滤器,我们在此前讲Java Web的时候就讲过了,在此不再赘述,我们主要讲Spring MVC的HandlerInterceptor。
2 HandlerInterceptor拦截器的原理
HandlerMapping在根据request查找Handler时,最终会返回一个HandlerExecutionChain
对象,字面翻译就是处理器执行链对象,其内部包含了一个Handler和可以应用于该Handler的HandlerInterceptor执行链。
所有的HandlerMapping实现都支持查找HandlerInterceptor链,想要自定义Handler的拦截器,必须实现org.springframework.web.servlet.HandlerInterceptor
接口,此接口中有三个抽象方法,用于灵活的实现拦截器的功能:
preHandle
:在执行Handler之前(执行业务逻辑之前),根据拦截器链顺序执行;postHandle
:在执行Handler成功(执行业务逻辑成功)之后,根据拦截器链倒序执行,如果前面的流程中抛出异常或者请求被拦截则不会执行!afterCompletion
:在请求处理完毕之后执行,无论是否有响应视图,无论有没有通过preHandle,无论有没有抛出异常。只会对此前放行成功(preHandle返回true)的拦截器进行倒序调用。
preHandle
方法返回boolean类型的值,可以使用此方法中断或继续处理执行链。当当前此方法返回true时,拦截器链将继续先后执行,即继续执行后续拦截器的preHandle
方法。当当前某个拦截器的preHandle方法返回false 时,DispatcherServlet 会假定拦截器本身已处理完毕请求(例如,已经渲染了合适的视图),此时将尝试直接倒序执行此前已放行的拦截器链的afterCompletion
方法,随后retrun结束处理,不会继续执行执行链中的后续其他拦截器和Handler实际处理程序(业务逻辑)以及后续其他流程。
对于采用了@ResponseBody
注解或者返回ResponseEntity
的方法,postHandle
后处理不太有效,因为在Handler执行成功时响应的数据(比如JSON数据)已经被写入response并且已被提交,并且是在postHandle方法被执行之前进行的!此时对于response的任何修改(比如添加额外的头部信息)都为时已晚。对于这种情况,我们可以实现ResponseBodyAdvice
接口并且声明为ControllerAdvice
,或者直接在RequestMappingHandlerAdapter
中配置。
3 配置拦截器
3.1 编写实现类
我们需要编写一个实现了拦截器接口HandlerInterceptor的类
/**
1. 自定义拦截器
2. 3. @author lx
*/
@Component
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
*
* @return true表示, 执行下一个拦截器, 没有拦截器了就执行controller中的方法;false表示不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1 preHandle invoke ,true");
return true;
}
/**
* 后处理
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1 postHandle invoke");
}
/**
* 请求处理完毕调用
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor1 afterCompletion invoke");
}
}
3.2 配置拦截器
3.2.1 配置拦截规则
我们可以为某个拦截器设置拦截或者不拦截的路径,拦截器的路径也可以使用通配符,如下:
- ? 表示匹配任何单个字符。
- * 表示匹配0或者任意数量的字符。
- ** 表示匹配后续的任何0或者更多级的目录。
路径 | 解析 |
---|---|
/** | 表示拦截全部路径的请求 |
/**/*.jsp | 表示拦截以.jsp结尾的全部请求 |
/a/*.jsp | 表示拦截a路径下的以.jsp结尾的全部请求 |
/b/jp. | 表示拦截b路径下的以j开头和p结尾的具有任何后缀的请求 |
/?/?.xx | 表示拦截单个字符路径下一单个字符名的xx后缀请求 |
3.2.2 基于JavaCOnfig配置拦截器
基于Java的配置很简单。编写配置类并继承WebMvcConfigurer
类,重写其中的方法 addInterceptors
,并且将这个配置类交给Spring管理!
@Configuration
@EnableWebMvc //支持MVC配置
public class WebConfig implements WebMvcConfigurer {
/**
* 添加拦截器,默认执行顺序为添加顺序
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
/*
* 添加拦截器
* 通过addPathPatterns配置拦截器的拦截路径,可以多次调用该方法
* 通过excludePathPatterns配置拦截器的不拦截路径,可以多次调用该方法
*/
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/a/**");
}
}
3.2.3 基于XML配置拦截器
基于XML的配置如下!
<!--配置拦截器-->
<mvc:interceptors>
<!--配置拦截器 默认拦截所有-->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<!--配置拦截器 -->
<mvc:interceptor>
<!--拦截-->
<mvc:mapping path="/**"/>
<!--不拦截-->
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
<!--配置自定义拦截器-->
<mvc:interceptor>
<!--拦截-->
<mvc:mapping path="/a/**"/>
<bean class="com.spring.mvc.controller.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
3.3 测试
有一个Controller:
/**
* @author lx
*/
@Controller
public class InterceptorController {
public InterceptorController() {
System.out.println("InterceptorController create");
}
@RequestMapping(path = "/a/b/c")
public String interceptor1() {
System.out.println("---interceptor1 Controller invoke---");
return "/index.jsp";
}
@RequestMapping(path = "/a/bbb/c")
public String interceptor2() {
System.out.println("---interceptor2 Controller invoke---");
return "/index.jsp";
}
@RequestMapping(path = "/b")
public String interceptor3() {
System.out.println("---interceptor3 Controller invoke---");
return "/index.jsp";
}
}
我们测试访问/a/bbb/c和/a/b/c,发现请求都会被拦截:
而访问/b则不会!
4 拦截器的细节
4.1 多个拦截器
我们可以配置多个拦截并应用到一个请求中!
多个拦截器默认情况下是按照在XML或者JavaConfig中的定义的顺序执行的,如果想要指定Order排序,那么需要自己创建InterceptorRegistration。
我们将MyInterceptor1类复制一份并改为MyInterceptor2配置到拦截器链中:
@Configuration //配置类
@EnableWebMvc //支持Spring MVC注解配置
public class WebConfig implements WebMvcConfigurer {
/**
* 添加拦截器,默认执行顺序为添加顺序
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
/*
* 添加拦截器
* 通过addPathPatterns配置拦截器的拦截路径,可以多次调用该方法
* 通过excludePathPatterns配置拦截器的不拦截路径,可以多次调用该方法
*/
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/a/**");
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/a/**");
}
}
我们测试访问/a/bbb/c,结果如下:
正常情况下,preHandle方法在拦截器链中会顺序执行,而postHandle和afterCompletion方法则会倒序执行。
4.2 拦截测试
如果我们在第二个拦截器中preHandle返回false,也就是不放行,对请求进行拦截,那么访问/a/bbb/c结果如下:
可以看到,Controller方法并没有被执行,拦截器链的postHandle方法也不会被执行,并且只执行了MyInterceptor1的afterCompletion方法。
实际上,如果某个拦截器不放行,那么后续的拦截器的preHandle预处理方法、Handler处理器、所有的postHandle后处理方法都不会被执行,并且在最后只会对此前放行成功的拦截器倒序执行afterCompletion方法。
4.3 拦截器跳转
拦截器的preHandle、postHandle、afterCompletion
方法均支持forward、include、redirect
转发到其他路径或者动态资源!但是要注意在跳转前如果响应已提交,那么可能会造成异常(比如response写了数据并且执行了PrintWriter或者OutputStream的flush或者close方法,比如调用了sendError、sendRedirect方法,那么将会抛出一个IllegalStateException,并且操作不会完成)!
基于这个特性,我们可以校验用户是否登陆,如果没有登陆,那么直接跳转到登录页面即可!
/**
* 预处理,controller方法执行前
*
* @return true表示, 执行下一个拦截器, 没有拦截器了就执行controller中的方法;false表示不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor2 preHandle invoke ,true");
//支持转发
// RequestDispatcher requestDispatcher = request.getRequestDispatcher("/index.jsp");
// requestDispatcher.forward(request, response);
//支持包含
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/index.jsp");
requestDispatcher.include(request, response);
//支持重定向
// response.sendRedirect("/mvc/index.jsp");
return false;
}
再次访问/a/bbb/c,结果如下,确实转发到了index.jsp中
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!
以上是关于Spring MVC学习—HandlerInterceptor处理器拦截器机制全解的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC学习笔记---Spring MVC 的HelloWorld
Spring MVC学习笔记---Spring MVC 的HelloWorld
Spring MVC学习—Spring MVC中容器的层次结构以及父子容器的概念