Spring = Spring拦截器之实现原理?

(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=""/>,分别是作用路径和不作用路径。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章