(1)SpringMVC运行流程
在介绍SpringMVC拦截器之前,我们先介绍一下SpringMVC的运行流程:
(1)用户发送请求,经过 前端控制器Dispacherservlet(Controller的核心)将url交给处理器映射器HandlerMapping处理
(2)处理器映射器HandlerMapping处理url,返回HandlerExecutionChain(可能包含拦截器,一定包含自定义的Controller(Handler))
(3)前端控制器Controller交给处理器适配器HandlerAdapter 处理,处理完成后,返回MV对象(ModelAndView)
(4)前端控制器将MV 交给视图控制器ViewResolver ,处理过程:将MV 拆分成Model和View 两个对象,并且将model渲染到View视图上,并且将View返回给前端控制器
(5)最后,前端控制器将视图响应给用户。
(2)SpringMVC拦截器:
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
作用:用户可以自己定义一些拦截器来实现特定的功能。例:访问特定页面前验证用户是否登陆等
拦截器链:
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
拦截器与过滤器的区别:
它和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别:
1)、过滤器是 servlet 规范中的一部分, 任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
2)、过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp, html,css,image 或者 js 是不会进行拦截的。
拦截器链执行流程图:
SpringMVC自定义拦截器使用步骤:
(1)自定义拦截器:
SpringMVC为我们提供了拦截器规范的接口,创建一个类实现HandlerInterceptor ,重写接口中的抽象方法;
抽象方法介绍:
1)、preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,处理器也不会调用;
2)、postHandle方法:在调用完处理器后调用该方法;
3)、afterCompletion方法:只要该拦截器中的preHandle方法返回true,该方法就会被调用;
(2)在SpringMVC核心配置文件中注册自定义拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的 url -->
<mvc:exclude-mapping path=""/><!-- 用于排除指定的 url-->
<!-- 使用指定的拦截器进行对指定的url进行拦截-->
<bean id="handlerInterceptorDemo1" class="拦截器所对应的全限定类名"></bean>
</mvc:interceptor>
</mvc:interceptors>
三.拦截器的实现方式:
SpringMVC拦截器的实现一般有两种实现方式:
第一种是:要定义的 Interceptor 类要实现了Spring的HandlerInterceptor 接口
第二种是:继承实现了HandlerInterceptor 接口的类,比如Spring已经提供的实现了 HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter
注意上图的步骤三,HandlerMapping接口,返回一个HandlerExecutionChain类、
HandlerExecutionChain是通过HandlerMapping的getHandler方法返回的。
继承该接口的类是来实现请求和handler对象的映射关系的。
这个接口中只有这样一个方法
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
根据函数名,参数及返回值我们不难猜出这个接口的作用,就是根据request返回HandlerExecutionChain。至于HandlerMapping在springMVC中有多种实现,我们此处就不深究了。
对于getHandler最后的调度部分便是springMVC的最外层DispatcherServlet类(Controller的核心)了。
HandlerExecutionChain类由HandlerInterceptor构成
HandlerExecutionChain类:由一个handler和若干的HandlerInterceptor构成。那么这个类的作用就显而易见了,就是将拦截器和handle组合*起来执行。就是对handle进行了包装。
这个类中有几个主要的方法:
1.applyPreHandle()看起,我们发现这个方法就是做的这样一个工作,按照列表中interceptor的顺序来执行它们的preHandle方法,直到有一个返回false。再看一下返回false后这个方法所做的工作,这时会调用triggerAfterCompletion方法,此时this.interceptorIndex指向上一个返回true的interceptor的位置,所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。
2.applyPostHandle(),这个方法较为简单,就是按照逆序执行所有interceptor的postHandle方法。
3.triggerAfterCompletion()也是一样,就是从最后一次preHandle成功的interceptor处逆序执行afterCompletion方法。
举例:
那么整个拦截器的处理过程我们便可以很清晰的分为两种情况:
一种是所有拦截器preHandle都返回true情况,另一种是有拦截器preHandle 返回false 的情况:
对于第一种情况:
那么在DispatcherServlet中分别依次调用HandlerExecutionChain类中applyPreHandle、applyPostHandle和triggerAfterCompletion方法,
那么所有方法的执行顺序为
A.pre -> B.pre -> C.pre -> D.pre
-> D.post -> C.post -> B.post -> A.post
-> D.after -> C.after -> B.after -> A.after
对于第二种情况:
我们不妨设C拦截器的preHandle返回为false。
这时DispatcherServlet类调用HandlerExecutionChain类中applyPreHandle方法,然后由applyPreHandle调用triggerAfterCompletion方法
那么执行情况如下:
A.pre -> B.pre -> C.pre ->
B.after -> A.after
也就是,指向上一个返回true的interceptor的位置(B位置),所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。(也就是跳过了他们的postHandle方法)
四.案例:
1.继承自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");
return false;
}
}
登录Controller:
@Controller
@RequestMapping(value = "/login")
public class LoginController {
@RequestMapping(value = {"/", ""})
public String index() {
return "login";
}
@RequestMapping("/auth")
public String auth(@RequestParam String username, HttpServletRequest req) {
req.getSession().setAttribute("loginUser", username);
return "redirect:/index";
}
@RequestMapping("/out")
public String out(HttpServletRequest req) {
req.getSession().removeAttribute("loginUser");
return "redirect:/login";
}
}
配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="org.format.demo.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
我们看到LoginInterceptor里的preHandle方法对于地址“/login/auth”和"/login/out"不处理。
因此,可以写点配置,少写带java代码。在拦截器配置中添加2个exclude-mapping,并且去掉LoginInterceptor里的
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
return true;
}
配置新增:
<mvc:exclude-mapping path="/login/out"/>
<mvc:exclude-mapping path="/login/auth"/>
2.实现接口HandlerInterceptor
它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆。
Spring MVC 的拦截器类似于Servlet中的拦截器!需要先定义一个类实现HandlerInterceptor接口。添加未实现的方法,在springmvc配置中配置,具体实现步骤如下:乱码过滤
package com.lx.controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用
* 若返回值为true,则继续调用后去的拦截器和目标方法
* 若返回值为false,则不会再调用后续的拦截器和方法
* 可以考虑做权限.日志,事务,等.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("text/htm;charset=utf-8");
System.out.println("ddddd");
return true;
}
/**
* 调用目标方法之后,但渲染视图之前.
* 有modelAndView对象,可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
* 渲染视图之后调用,释放资源.
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
-boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在请求到达Handler之前,先执行这个前置处理方法.当该方法返回false时,请求直接返回,不会传递到链中的下一个拦截器,更不会传递到链尾的Handler,只有返回true时,请求才会向链中的下一个处理节点传递!
-
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView); 在相应已经被渲染后,执行该方法.
-
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); 在响应已经被渲染后,执行该方法!
配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.lx.controller.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
注意其中的mvc:interceptor标签,子标签下有<mvc:mapping path="" />,<mvc:exclude-mapping path=""/>,分别是作用路径和不作用路径。