基于最新Spring 5.x,详细介绍了Spring MVC的HandlerInterceptor处理器拦截器机制,以及它的一系列拦截方法。
本次我们来学习Sring MVC的HandlerInterceptor处理器拦截器机制,HandlerInterceptor和Filter类似,可用于拦截请求,实现比如登陆、鉴权、过滤等自定义扩展的逻辑。
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解决跨域问题
Spring MVC
提供的特性,依赖于Spring MVC框架,而不依赖Servlet容器,Filter则是Servlet的特性,属于Servlet
的规范,并且依赖Servlet容器。filterChain.doFilter
进行分隔,而HandlerInterceptor将预处理和后处理逻辑拆分成两个方法,即preHandle、postHandle
方法。init和destroy
,而HandlerInterceptor则没有,但是HandlerInterceptor拥有afterCompletion
处理方法,无论有没有抛出异常,在DispatcherServlet请求处理的最后都会执行!所以说,很多功能其实Filter和HandlerInterceptor都能做,具体看你怎么选了,如果使用Spring MVC框架,那么建议使用HandlerInterceptor吧,它可以类似于普通bean直接注册到Spring容器中被管理。
关于Filter过滤器,我们在此前讲Java Web的时候就讲过了,在此不再赘述,我们主要讲Spring MVC的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
中配置。
我们需要编写一个实现了拦截器接口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");
}
}
我们可以为某个拦截器设置拦截或者不拦截的路径,拦截器的路径也可以使用通配符,如下:
路径 | 解析 |
---|---|
/** | 表示拦截全部路径的请求 |
/**/*.jsp | 表示拦截以.jsp结尾的全部请求 |
/a/*.jsp | 表示拦截a路径下的以.jsp结尾的全部请求 |
/b/jp. | 表示拦截b路径下的以j开头和p结尾的具有任何后缀的请求 |
/?/?.xx | 表示拦截单个字符路径下一单个字符名的xx后缀请求 |
基于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/**");
}
}
基于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>
有一个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则不会!
我们可以配置多个拦截并应用到一个请求中!
多个拦截器默认情况下是按照在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方法则会倒序执行。
如果我们在第二个拦截器中preHandle返回false,也就是不放行,对请求进行拦截,那么访问/a/bbb/c结果如下:
可以看到,Controller方法并没有被执行,拦截器链的postHandle方法也不会被执行,并且只执行了MyInterceptor1的afterCompletion方法。
实际上,如果某个拦截器不放行,那么后续的拦截器的preHandle预处理方法、Handler处理器、所有的postHandle后处理方法都不会被执行,并且在最后只会对此前放行成功的拦截器倒序执行afterCompletion方法。
拦截器的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中
相关文章:
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!