猿蜕变系列8——一文搞懂Interceptor操纵姿势

时间:2022-07-22
本文章向大家介绍猿蜕变系列8——一文搞懂Interceptor操纵姿势,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

看过之前的蜕变系列文章,相信你对springMVC有了一定的认识。对springMVC的文件上传,也有了一定的认识。今天我们来开启新讨论,讲一讲springMVC的Interceptor拦截器怎么去处理web层面通用逻辑。

在web开发中,我们有很多需要统一去处理的事情,我们先回忆下之前解决过的一个问题——请求的中文乱码问题。为了处理中文的乱码问题,我们编写并配置了过滤器(Filter),统一对字符做了UTF-8编码。

其实在程序中,还有很多类似需要全局统一处理的事情要做,比如登录验证、权限验证、请求访问时长统计,web应用层关键日志输出 ……为了解决这些需要去统一处理的问题,Spring MVC提供了拦截器(Interceptor)来处理。Spring MVC的拦截器有框架提供的,也提供了接口让用户自己去定义。用户自定义拦截器要求实现接口org.springframework.web.servlet.HandlerInterceptor。我们看下接口的定义(去掉注释的):

package org.springframework.web.servlet;
 
import  javax.servlet.http.HttpServletRequest;
import  javax.servlet.http.HttpServletResponse;
 
import  org.springframework.web.method.HandlerMethod;
 
public  interface  HandlerInterceptor {
 
      
       boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)
          throws Exception;
 
      
       void postHandle(
                     HttpServletRequest request,HttpServletResponse response, Object handler, ModelAndView modelAndView)
                     throws Exception;
 
      
       void afterCompletion(
                     HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex)
                     throws Exception;
 
}

接口有三个方法需要实现:

    boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)
          throws Exception;

返回值为 boolean类型,如果返回为 true,就会执行controller方法,并且会将afterCompletion()方法放到一个方法栈中等待执行。大家注意方法入参,request和response都存在,可以做的事情很多了。

void postHandle(
                     HttpServletRequest request,HttpServletResponse response, Object handler, ModelAndView modelAndView)
              throws Exception;

无返回值类型,这个方法会在Controller方法执行完成之后执行。如果Controller的方法没有被执行到,那么这个方法也不会执行。大家注意一下,方法有个入参ModelAndView 类型的,这意味着我们可以修改返回视图。

void afterCompletion(
                     HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex)
                     throws Exception;

当preHandle返回为true时,这个方法会被放到一个方法栈等待执行,什么时候执行呢?等待视图渲染完成之后再执行。大家注意方法参数有Exception类型的,在这里可以处理异常。

看看DispatcherServlet的源码的doDispatch方法就知道了,接下来我们自己实现一个拦截器:

package com.pz.web.study.springmvc.interceptor;
 
import  javax.servlet.http.HttpServletRequest;
import  javax.servlet.http.HttpServletResponse;
 
import  org.springframework.web.servlet.HandlerInterceptor;
import  org.springframework.web.servlet.ModelAndView;
 
public  class  FirstInterceptor implements HandlerInterceptor{
 
       @Override
       public  boolean  preHandle(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler) throws Exception{
             
              System.out.println("==preHandle==被执行了");
              return  true;
       }
 
       @Override
       publicvoidpostHandle(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler,
                     ModelAndView modelAndView) throws Exception {
              System.out.println("==postHandle==被执行了");
             
       }
 
       @Override
       publicvoidafterCompletion(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler, Exception ex)
                     throws Exception {
              System.out.println("==afterCompletion==被执行了");
             
       }
 
}
 

为了更好的查看效果我们编写个新的Controller

package com.pz.web.study.springmvc.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
public class InterceptorControllerDemo {
      
      
       @RequestMapping("/InterceptorControllerDemo.do")
       public String InterceptorControllerDemo() throws Exception {
              System.out.println("Interceptor方法==InterceptorControllerDemo===被执行了");
           return"../index";
        }
 
}
 

增加拦截器配置,修改spring-servlet.xml增加内容

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.FirstInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

启动应用,访问

http://127.0.0.1/InterceptorControllerDemo.do

注意控制台输出:

=FirstInterceptor=preHandle==被执行了

=Controller方法==InterceptorControllerDemo===被执行了

=FirstInterceptor=postHandle==被执行了

=FirstInterceptor=afterCompletion==被执行了

我们观察配置文件,mvc:interceptors发现拦截器有多个,那么有多个拦截器时怎么执行呢?哪个拦截器先执行?

为了搞明白这个我们再定义个拦截器吧:

package com.pz.web.study.springmvc.interceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
public class SecondInterceptor implements HandlerInterceptor{
 
       @Override
       public booleanpreHandle(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler) throws Exception {
             
              System.out.println("=SecondInterceptor=preHandle==被执行了");
              return true;
       }
 
       @Override
       public void postHandle(HttpServletRequestrequest,
                     HttpServletResponseresponse, Object handler,
                     ModelAndView modelAndView)throws Exception {
              System.out.println("=SecondInterceptor=postHandle==被执行了");
             
       }
 
       @Override
       public voidafterCompletion(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler, Exception ex)
                     throws Exception {
              System.out.println("=SecondInterceptor=afterCompletion==被执行了");
             
       }
 
}

修改拦截器配置

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.FirstInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.SecondInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

启动应用继续访问:

http://127.0.0.1/InterceptorControllerDemo.do

控制台输出

=FirstInterceptor=preHandle==被执行了

=SecondInterceptor=preHandle==被执行了

=Controller方法==InterceptorControllerDemo===被执行了

=SecondInterceptor=postHandle==被执行了

=FirstInterceptor=postHandle==被执行了

=SecondInterceptor=afterCompletion==被执行了

=FirstInterceptor=afterCompletion==被执行了

preHandle方法是按照配置顺序执行的,而postHandle和afterCompletion方法是按照配置逆序执行的!

我们在生活中经常遇到下面的场景,我们访问某些站点,一些功能需要在登录后才能使用。下面我们就来实现这样一个小功能:让url为/user/下访问的 Contoller方法,只能使用用户名为pangzi的用户才能访问。我们看下面这个例子:

先编写用于登录的页面login.jsp:

<%@ page language="java" contentType="text/html;charset=utf-8"
    pageEncoding="utf-8"isELIgnored="false"%>
<html>
<head>
<title>spring form表单样例使用对象接收参数</title>
</head>
<body>
   <form action="./login.do"method=post>
    <lable>用户名:</lable>
      <input type="text"name="userName" id="userName"/><br />
    <lable>密码:</lable>
     <input type="password"name="passWord" id="phone"/><br />
      <input type="submit"value="登录"id="submit" /><br />
   </form>
</body>
</html>

编写用于登录的Controller:

package com.pz.web.study.springmvc.controller;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.pz.web.study.springmvc.domain.User;
 
@Controller
public class LoginControllerDemo {
 
       @RequestMapping("/login.do")
       public String userAction(String userName,StringpassWord,HttpServletRequest request) throws Exception {
             
              if(userName.equals("pangzi")&&passWord.equals("123456")){
                     User user= new User();
                     user.setUserName(userName);
                     request.getSession().setAttribute("user", user);
                     return"../index";
              }
             
           return"../login";
        }
}

编写权限访问控制拦截器

package  com.pz.web.study.springmvc.interceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import com.pz.web.study.springmvc.domain.User;
 
public class UserLoginInterceptor implements HandlerInterceptor{
 
       @Override
       public booleanpreHandle(HttpServletRequest request,
                     HttpServletResponseresponse, Object handler) throws Exception {
             
              String uri=request.getRequestURI();
             
              if(uri.contains("/user/")){
                     User user = (User)request.getSession().getAttribute("user");
                     if(null==user){
                            response.sendRedirect("/login.jsp");
                     }
                    
              }
             
              System.out.println("=UserLoginInterceptor=preHandle==被执行了");
              return true;
       }
 
       @Override
       public void postHandle(HttpServletRequestrequest,
                     HttpServletResponseresponse, Object handler,
                     ModelAndView modelAndView)throws Exception {
              System.out.println("=UserLoginInterceptor=postHandle==被执行了");
             
       }
 
       @Override
       public void afterCompletion(HttpServletRequestrequest,
                     HttpServletResponseresponse, Object handler, Exception ex)
                     throws Exception {
              System.out.println("=UserLoginInterceptor=afterCompletion==被执行了");
             
       }
 
}

修改spring-servlet.xml增加拦截器配置:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.FirstInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.SecondInterceptor"/>
    </mvc:interceptor>
   
     <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.pz.web.study.springmvc.interceptor.UserLoginInterceptor"/>
    </mvc:interceptor>
   
</mvc:interceptors>

编写一个用于验证的Controller:

package com.pz.web.study.springmvc.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
@RequestMapping("/user")
publicclass UserLoginControllerDemo {
      
      
       @RequestMapping("/userAction.do")
       public String userAction() throws Exception {
              System.out.println("userAction成功执行了");
           return"../index";
        }
 
}

启动应用,访问:

http://127.0.0.1/user/userAction.do

页面跳转到登录页面,输入

用户名 pangzi

密码 123456

登录后

然后再访问

http://127.0.0.1/user/userAction.do

看看效果