springMVC笔记系列(23)——拦截器及其在乱码问题和登录问题的应用实现

(本文出自happyBKs的博客:http://my.oschina.net/happyBKs/blog/710833)

拦截器的概念

A:“什么是拦截器?”

B:“拦截器是通过统一拦截从客户端发往服务器的请求来完成功能的增强。”

A:(一脸懵逼)

B:“说得简单点,拦截器就是在客户端向服务器端发出请求的期间,在请求交友服务器处理之前或之后对请求数据做一些修改或者其他相关的操作。”

A:“能说说具体实现什么功能吗?”

B:“拦截器的使用场景是解决一些共性问题。比如乱码问题、权限验证问题。你可以将这些共性的操作从各个不同业务功能的控制器方法中抽离出来,放在统一的拦截器中执行,这样你的解决乱码问题的代码,或者权限验证的代码不会重复地出现在各个不同业务功能对应的控制器方法执行体中。”

A:“我可以理解为拦截器是看门狗吗?这只狗负责看门,监管进门和出门的人;这只狗也能不同人家的门前做相同的事情——拦截出门或进门的人们。”

B:“能文雅点吗。。。”

A:“springMVC拦截器的出现倒是帮我们在MVC实现时,营造了一种AOP的效果”

B:“spring大法万岁!”

A:“。。。”

好,小段子结束,还是用博客的正常写法来写吧。

这里,也许你还要问——拦截器和过滤器有什么区别?

过滤器Filter依赖于Servlet容器,Filter被Servlet容器所管理;基于回调函数;过滤范围大(请求、资源等)

拦截器Interceptor依赖于springMVC框架容器;基于反射机制;只过滤请求

springMVC拦截器的原理和使用

我们还是先来看一个应用场景:解决乱码问题。

利用SpringMVC的拦截器可以解决乱码问题。但是,你应该会说,我使用Servlet容器中的过滤器也可以完成。是的,现在我们现在一个springMVC项目中通过配置一个过滤器来解决乱码问题。(实际上springMVC拦截器与Servlet容器中的过滤器在实现解决乱码问题时的原理十分相似)

我们在前几篇博客文章的例子的基础上改代码吧,顺便说明一下一个springMVC可以有两个互不干扰的前端控制器配置。在web.xml中,我们在最后追加一个前端控制器viewSpace-dispatcher。可以看到,这个web.xml配置文件中有两个org.springframework.web.servlet.DispatcherServlet。但是拦截的请求是不同的,一个是/,一个是/test2/*。这里请求会自动匹配更具体的一个,关于各种url通配符优先级,改天我专门弄一篇博客来说。

关于web.xml的servlet的url-pattern,我只想插入一个注意事项:

在web.xml文件中,以下语法用于定义映射:

l. 以”/’开头和以”/*”结尾的是用来做路径映射的。

  1. 以前缀”*.”开头的是用来做扩展映射的。

  2. “/” 是用来定义default servlet映射的。

  3. 剩下的都是用来定义详细映射的。比如: /aa/bb/cc.action

所以,为什么定义”/*.action”这样一个看起来很正常的匹配会错?因为这个匹配即属于路径映射,也属于扩展映射,导致容器无法判断。

言归正传,下面是web.xml,重点看最后的那个viewSpace-dispatcher:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd " version="2.5">

  <display-name>Archetype Created Web Application</display-name>

  <!-- Spring应用上下文, 理解层次化的ApplicationContext -->
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value>
    </context-param>

    <listener>
      <listener-class>
        org.springframework.web.context.ContextLoaderListener
      </listener-class>
    </listener>

  <!-- DispatcherServlet, Spring MVC的核心 -->
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml
     -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <!-- mvc-dispatcher拦截所有的请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>


  <servlet>
    <servlet-name>viewSpace-dispatcher</servlet-name>
    <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml
     -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/configs/spring/viewSpace-dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>viewSpace-dispatcher</servlet-name>
    <!-- mvc-dispatcher拦截所有的请求-->
    <url-pattern>/test2/*</url-pattern>
  </servlet-mapping>



</web-app>

然后我们编写viewSpace-dispatcher的配置文件:
这里写图片描述

\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.xml如下:(具体含义可以参加前面的文章)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 -->

    <!-- 启用Spring基于annotation的DI, 使用户可以在Spring MVC中使用Spring的强大功能。 激活 @Required
        @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 -->
    <context:annotation-config />

    <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其他型的bean, 如@Service -->
    <context:component-scan base-package="com.happyBKs.controller">
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- HandlerMapping, 无需配置, Spring MVC可以默认启动。 DefaultAnnotationHandlerMapping
        annotation-driven HandlerMapping -->

    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven />

    <!-- 静态资源处理, css, js, imgs -->
    <mvc:resources mapping="/resources/**" location="/resources/" />


    <!-- 配置ViewResolver。 可以用多个ViewResolver。 使用order属性排序。 InternalResourceViewResolver放在最后。 -->
    <bean
            class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="order" value="1"/>
        <!--<property name="mediaTypes">-->
        <!--<map>-->
        <!--<entry key="json" value="application/json"/>-->
        <!--<entry key="xml" value="application/xml"/>-->
        <!--<entry key="htm" value="text/html"/>-->
        <!--</map>-->
        <!--</property>-->

        <property name="defaultViews">
            <list>
                <!-- JSON View -->
                <bean
                        class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
                </bean>
            </list>
        </property>
        <!--<property name="ignoreAcceptHeader" value="true"/>-->
    </bean>

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsps/" />
        <property name="suffix" value=".jsp" />
    </bean>


    <!--200*1024*1024即200M resolveLazily属性启用是为了推迟文件解析,以便捕获文件大小异常 -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="209715200"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>
    </bean>

</beans>

之后我们变量一个控制器TestController2 :

package com.happyBKs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * Created by sunsun on 2016/7/9.
 */

@Controller
@RequestMapping("/test2")
public class TestController2 {

    @RequestMapping("/login")
    public String login(){
        System.out.println("进入控制器的login方法");
        return "login";
    }

    @RequestMapping("/viewAll")
    public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){
        ModelAndView mv = new ModelAndView();
        System.out.println("进入控制器的viewAll方法");
        System.out.println("name="+name);
        System.out.println("pwd="+pwd);
        mv.setViewName("/hello");
        return mv;
    }


}

这个控制器的两个方法,一个是分发访问登录页面的请求,一个是接收登录请求并将表单数据在控制台输出最终转到hello页面。

登录页面\WEB-INF\jsps\login.jsp:(本项目是之前博客中的项目例子基础上追加的功能,原项目定义了webapp的url名称为mvc,所以表单action的请求路径请注意!)

<%--
  Created by IntelliJ IDEA.
  User: happyBKs
  Date: 2016/7/9
  Time: 13:22
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<form action="/mvc/test2/viewAll" method="post">
    用户名<input type="text" name="name" id="name"><br/>
    密 码<input type="password" name="pwd" id="pwd"><br/>
    <input type="submit" name="登录">
</form>
</body>
</html>

登录后的hello页面:

<%--
  Created by IntelliJ IDEA.
  User: happyBKs
  Date: 2016/7/9
  Time: 14:42
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功登录</title>
</head>
<body>
成功登录
</body>
</html>

之后我们发布项目到tomcat,但是请求http://localhost:8080/mvc/test2/login,却提示404错误。

这里写图片描述

控制台输出:

19680 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'viewSpace-dispatcher' processing GET request for [/mvc/test2/login]
19680 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'viewSpace-dispatcher' processing GET request for [/mvc/test2/login]
19686 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /login
19686 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /login
19689 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Did not find handler method for [/login]
19689 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Did not find handler method for [/login]
19690 [http-apr-8080-exec-10] WARN  org.springframework.web.servlet.PageNotFound  - No mapping found for HTTP request with URI [/mvc/test2/login] in DispatcherServlet with name 'viewSpace-dispatcher'
19690 [http-apr-8080-exec-10] WARN  org.springframework.web.servlet.PageNotFound  - No mapping found for HTTP request with URI [/mvc/test2/login] in DispatcherServlet with name 'viewSpace-dispatcher'
19690 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Successfully completed request
19690 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Successfully completed request

这是为什么呢?

原因是,当一个请求的全路径通过servlet映射找到所服务的DispatcherServlet后,DispatcherServlet按照url-pattern路径映射的匹配后剩余的路径进一步交给DispatcherServlet,由DispatcherServlet进一步实现剩下路径在其搜索包空间内的控制器类的请求映射的匹配。

回到这个例子中就是:http://localhost:8080/mvc/test2/login中,首先去除了域名和端口/mvc/test2/login,tomcat中配置了应用上下文Application Context为mvc,所以springMVC项目处理的请求为/test2/login。在web.xml中由viewSpace-dispatcher因url-pattern为/test2/**接收请求/test2/login,然后将匹配剩余部分路径/login进一步交给viewSpace-dispatcher所对应的springMVC容器配置文件中定义的控制器类包搜索空间中搜索能够匹配这个请求/login的控制器及其方法。但是这里我们原本希望其能够映射到的控制器类TestController2的@RequestMappping的注解值又配置了一个/test2,原本我们希望的目标方法public String login()的@RequestMappping注解值为/login。这时候实际springMVC是将请求/login尝试与/test2/login进行匹配,当然不可能匹配上,所以才会在控制台输出请求匹配不了,整个请求也只能返回404。

你若不信,当我们这时候请求http://localhost:8080/mvc/test2/test2/login,你会发现尽然正常显示了!!

这里写图片描述

所以我们就将控制器类TestController2的映射路径改为“/”,这样就可以了。

package com.happyBKs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * Created by sunsun on 2016/7/9.
 */

@Controller
@RequestMapping("/")
public class TestController2 {

    @RequestMapping("/login")
    public String login(){
        System.out.println("进入控制器的login方法...");
        return "login";
    }

    @RequestMapping("/viewAll")
    public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){
        ModelAndView mv = new ModelAndView();
        System.out.println("进入控制器的viewAll方法...");
        System.out.println("name="+name);
        System.out.println("pwd="+pwd);
        mv.setViewName("/hello");
        return mv;
    }


}

我们来看看运行结果:

请求http://localhost:8080/mvc/test2/login
这里写图片描述
控制台输出:
这里写图片描述

提交表单之后,转到http://localhost:8080/mvc/test2/viewAll
这里写图片描述
控制台输出:
这里写图片描述
可以看到中文数据出现了乱码。。。。。。。

springMVC在框架中已经为web的过滤器提供了一个CharacterEncodingFilter类

这里写图片描述

在web.xml中,增加一个过滤器:

  <filter>
    <filter-name>viewSpace-filter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>viewSpace-filter</filter-name>
    <url-pattern>/test2/*</url-pattern>
  </filter-mapping>

这个过滤器指定/test2/*符合该通配符的url请求都会被这个过滤器过滤到,然后交由这个CharacterEncodingFilter来处理,将请求参数中的数据编码转换成utf8。

注意:这个不同用/test2,而要使用完整的通配符,否则过滤器会以为你仅仅是要过滤http://localhost:8080/mvc/test2一个url请求,这与前端控制器的url-pattern不同,需要特别注意。

我们按照刚才的方法运行请求:
这里写图片描述
控制台输出发现参数已经不会再出现中文乱码了。
这里写图片描述

好,过滤器就像一个检票口,符合特定条件的请求进入者可以经过相应一些手续后进入。过滤器和拦截器在功能上可以说是十分相似的,只是在一些细节上有所不同,下面,我们来看看拦截器如何实现。

拦截器的实现步骤如下:

  1. 首先编写一个拦截器类,须要实现HandlerInterceptor接口:
package com.happyBKs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * Created by happyBKs on 2016/7/9.
 */

@Controller
@RequestMapping("/")
public class TestController2 {

    @RequestMapping("/login")
    public String login(){
        System.out.println("进入控制器的login方法...");
        return "login";
    }

    @RequestMapping("/viewAll")
    public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){
        ModelAndView mv = new ModelAndView();
        System.out.println("进入控制器的viewAll方法...");
        System.out.println("name="+name);
        System.out.println("pwd="+pwd);
        mv.setViewName("/hello");
        return mv;
    }


}

2.将拦截器注册到springMVC框架中:

注意,在这个前端控制器器配置文件中,必须声明

xmlns:mvc=”http://www.springframework.org/schema/mvc”

http://www.springframework.org/schema/mvc/spring-mvc.xsd

应为拦截器须要用到mvc的名称空间:

我们在\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.xml中追加如下内容:

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.happyBKs.interceptor.TestInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

注意:这里的,mvc:mapping的path属性配置的路径必须是“/*”,而不能是“/”或者“/”。

/**的意思是所有文件夹及里面的子文件夹
/*是所有文件夹,不含子文件夹
/是web项目的根目录

然后可以看到请求http://localhost:8080/mvc/test2 结果控制台输出:拦截器的三个方法与控制器方法的执行顺序可以看见了吧。

执行进入preHandle方法
进入控制器的login方法...
执行进入postHandle方法
11705 [http-apr-8080-exec-10] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Invoking afterPropertiesSet() on bean with name 'login'
11705 [http-apr-8080-exec-10] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Invoking afterPropertiesSet() on bean with name 'login'
11705 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Rendering view [org.springframework.web.servlet.view.JstlView: name 'login'; URL [/WEB-INF/jsps/login.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher'
11705 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Rendering view [org.springframework.web.servlet.view.JstlView: name 'login'; URL [/WEB-INF/jsps/login.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher'
11714 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.view.JstlView  - Forwarding to resource [/WEB-INF/jsps/login.jsp] in InternalResourceView 'login'
11714 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.view.JstlView  - Forwarding to resource [/WEB-INF/jsps/login.jsp] in InternalResourceView 'login'
执行进入afterCompletion方法

拦截器的方法

下面我们来详细说说拦截器的三个方法:

preHandle:在请求备注里之前进行调用

postHandle:在请求被处理之后进行调用

afterComletion:在请求结束之后才进行调用

preHandle方法与其他两个方法相比比较特殊,它是具有返回值的。这个boolean类型的返回值表示这个请求是否能“活命”。

 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入preHandle方法");
        return true;
    }

返回值为true表示,在preHandle执行完之后请求继续传递下去;为false,请求被拦截后就终止请求了。

如果我们把这个方法的返回值改成false,控制台输出结果和页面显示如下:
这里写图片描述

这里写图片描述

看到了请求没有被执行下去,连控制器方法都没有被执行,所以这个请求被拦截下懒之后被拦截器给扼杀在摇篮里了。

这三个方法的形参都具有HttpServletRequest request和HttpServletResponse response,前者包含了所有的请求内容,后者包含了所有的响应内容。

preHandle方法里的Object handler表示的是被拦截的请求目标对象。比如,这个例子中请求拦截的目标就是这个TestController2。

postHandle方法里的独有的参数ModelAndView modelAndView:我们可以通过该参数改变显示的视图,或者修改发往视图的方法。看到了吧,即使你在控制器方法中已经对映射的视图资源做了设定,这里依然可以更改。这个更改既包括这请求映射到那个视图资源,还包括了传递给视图资源的数据等。

这里我们举个例子,加入我们在控制器TestController2中增加一个方法:

  @RequestMapping("/msg")
    public ModelAndView msg(){
        System.out.println("进入控制器的msg方法...");
        ModelAndView mv=new ModelAndView();
        mv.setViewName("/InterTest");
        mv.addObject("msg","从控制器的方法返回的视图数据");
        return mv;
    }

增加一个jsp视图页面\WEB-INF\jsps\InterTest.jsp:
这里写图片描述

注意:这里msg数据用的EL表达式的形式${msg}。

<%--
  Created by IntelliJ IDEA.
  User: sunsun
  Date: 2016/7/12
  Time: 20:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>拦截器测试</title>
</head>
<body>
msg=${msg}
</body>
</html>

然后我们运行请求http://localhost:8080/mvc/test2/msg
这里写图片描述
好,这时候,我们在原先的拦截器类TestInterceptor类的postHandle方法上做个修改:

   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入postHandle方法");
        //通过modelAndView参数改变显示的视图,或者修改发往视图的方法
        modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据");

    }

我们再次运行方才的项目,请求:http://localhost:8080/mvc/test2/msg

http://static.oschina.net/uploads/space/2016/0712/213923_fDsF_1156339.png

看到了吧,msg的数据已经是被拦截器的postHandle方法修改后的数据了。

并且,控制台输出也表明,执行的各个环节依然还在。
这里写图片描述

当然,这里的拦截器还可以对请求转发的视图资源做更改。例如,我们继续更改控制器类的postHandle方法:

 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入postHandle方法");
        //通过modelAndView参数改变显示的视图,或者修改发往视图的方法
        modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据");
        modelAndView.setViewName("/hello");

    }

将modelAndView的视图资源设置为hello页面,即成功登录页面\WEB-INF\jsps\hello.jsp

运行结果变为:
这里写图片描述

看吧,视图资源也能换。

afterCompletion方法有点像C++中的析构函数,afterCompletion方法在请求被响应之后最后执行,用于对一些资源的释放,对我们来说不是很常用。

下面我们来说明另外一个问题:如果存在多个拦截器,执行机制是怎么样的?

我们将定义两个拦截器TestController和TestController2
这里写图片描述

package com.happyBKs.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by happyBKs on 2016/7/10.
 */
public class TestInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入TestInterceptor的preHandle方法");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入TestInterceptor的postHandle方法");
        //通过modelAndView参数改变显示的视图,或者修改发往视图的方法
//        modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据");
//        modelAndView.setViewName("/hello");

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行进入TestInterceptor的afterCompletion方法");
    }
}
package com.hap
package com.happyBKs.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by happyBKs on 2016/7/10.
 */
public class TestInterceptor2 implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入TestInterceptor2的preHandle方法");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入TestInterceptor2的postHandle方法");
        //通过modelAndView参数改变显示的视图,或者修改发往视图的方法
//        modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据");
//        modelAndView.setViewName("/hello");

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行进入TestInterceptor2的afterCompletion方法");
    }
}

之后在springMVC前端控制器配置文件\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 -->

    <!-- 启用Spring基于annotation的DI, 使用户可以在Spring MVC中使用Spring的强大功能。 激活 @Required
        @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 -->
    <context:annotation-config />

    <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其他型的bean, 如@Service -->
    <context:component-scan base-package="com.happyBKs.controller">
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- HandlerMapping, 无需配置, Spring MVC可以默认启动。 DefaultAnnotationHandlerMapping
        annotation-driven HandlerMapping -->

    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven />


    <!-- 静态资源处理, css, js, imgs -->
    <mvc:resources mapping="/resources/**" location="/resources/" />


    <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">-->
        <!--<property name="alwaysUseFullPath" value="true"></property>-->
    <!--</bean>-->

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsps/" />
        <property name="suffix" value=".jsp" />
    </bean>


    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.happyBKs.interceptor.TestInterceptor"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.happyBKs.interceptor.TestInterceptor2"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

然后我们运行,请求http://localhost:8080/mvc/test2/login

控制台输出结果:

执行进入TestInterceptor的preHandle方法
执行进入TestInterceptor2的preHandle方法
进入控制器的login方法…
执行进入TestInterceptor2的postHandle方法
执行进入TestInterceptor的postHandle方法
执行进入TestInterceptor2的afterCompletion方法
执行进入TestInterceptor的afterCompletion方法

是不是有点糊涂,不要紧,看看这个执行机制的示意图:

这里写图片描述

打个比方说:比如你从上海去北京出差,有两个收费口,我们先经过,收费口1,然后经过2,达到北京,然后回来时先经过2,在经过1,并且,回来的过程中我告诉收费口把发票寄到上海家里,于是收费口2的发票先寄出,然后收费口1的再寄出。收费口就是拦截器,去北京的路上的收费口执行preHandle方法,到北京执行控制器方法,返程中收费口执行postHandle方法,开发票就是afterCompletion方法。
这里写图片描述

最后,在拦截器的实现方法上再补充一点,拦截器除了可以通过实现HandleInterceptor接口来完成,还有一种接口也可以——接口WebRequestInterCeptor。但是这种方法的preHandle方法没有返回值,因此不具有终止请求的功能。所以我还是推荐通过实现HandleInterceptor接口来完成。

SpringMVC拦截器的使用场景

使用原则:处理所有请求中的共性问题

  1. 解决乱码问题

我们将控制器TestInterceptor重新整理一下,对preHandle方法中的request参数设置一下编码。

package com.happyBKs.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by happyBKs on 2016/7/10.
 */
public class TestInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入TestInterceptor的preHandle方法");
        request.setCharacterEncoding("utf-8");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入TestInterceptor的postHandle方法");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行进入TestInterceptor的afterCompletion方法");
    }
}

原先项目web.xml中的过滤器我们注释掉。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd " version="2.5">

  <display-name>Archetype Created Web Application</display-name>

  <!-- Spring应用上下文, 理解层次化的ApplicationContext -->
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value>
    </context-param>

    <listener>
      <listener-class>
        org.springframework.web.context.ContextLoaderListener
      </listener-class>
    </listener>

   <!--DispatcherServlet, Spring MVC的核心-->
  <!--<servlet>-->
    <!--<servlet-name>mvc-dispatcher</servlet-name>-->
    <!--<servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>-->
    <!--&lt;!&ndash; DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml-->
     <!--&ndash;&gt;-->
    <!--<init-param>-->
      <!--<param-name>contextConfigLocation</param-name>-->
      <!--<param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>-->
    <!--</init-param>-->
    <!--<load-on-startup>1</load-on-startup>-->
  <!--</servlet>-->
  <!--<servlet-mapping>-->
    <!--<servlet-name>mvc-dispatcher</servlet-name>-->
    <!--&lt;!&ndash; mvc-dispatcher拦截所有的请求&ndash;&gt;-->
    <!--<url-pattern>/</url-pattern>-->
  <!--</servlet-mapping>-->


  <servlet>
    <servlet-name>viewSpace-dispatcher</servlet-name>
    <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml
     -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/configs/spring/viewSpace-dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>viewSpace-dispatcher</servlet-name>
    <!-- mvc-dispatcher拦截所有的请求-->
    <url-pattern>/test2/*</url-pattern>
  </servlet-mapping>


<!--  <filter>
    <filter-name>viewSpace-filter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>viewSpace-filter</filter-name>
    <url-pattern>/test2/*</url-pattern>
  </filter-mapping>-->

</web-app>

springMVC前端控制器配置文件\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.xml也只保留一个拦截器注册:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 -->

    <!-- 启用Spring基于annotation的DI, 使用户可以在Spring MVC中使用Spring的强大功能。 激活 @Required
        @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 -->
    <context:annotation-config />

    <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其他型的bean, 如@Service -->
    <context:component-scan base-package="com.happyBKs.controller">
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- HandlerMapping, 无需配置, Spring MVC可以默认启动。 DefaultAnnotationHandlerMapping
        annotation-driven HandlerMapping -->

    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven />


    <!-- 静态资源处理, css, js, imgs -->
    <mvc:resources mapping="/resources/**" location="/resources/" />


    <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">-->
        <!--<property name="alwaysUseFullPath" value="true"></property>-->
    <!--</bean>-->

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsps/" />
        <property name="suffix" value=".jsp" />
    </bean>





    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.happyBKs.interceptor.TestInterceptor"></bean>
        </mvc:interceptor>
<!--        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.happyBKs.interceptor.TestInterceptor2"></bean>
        </mvc:interceptor>-->
    </mvc:interceptors>

</beans>

运行后请求http://localhost:8080/mvc/test2/login
这里写图片描述

提交后控制输出:无乱码



执行进入TestInterceptor的afterCompletion方法
197415 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Successfully completed request
197415 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet  - Successfully completed request
206359 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'viewSpace-dispatcher' processing POST request for [/mvc/test2/viewAll]
206359 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'viewSpace-dispatcher' processing POST request for [/mvc/test2/viewAll]
206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /viewAll
206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /viewAll
206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Returning handler method [public org.springframework.web.servlet.ModelAndView com.happyBKs.controller.TestController2.viewAll(java.lang.String,java.lang.String)]
206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Returning handler method [public org.springframework.web.servlet.ModelAndView com.happyBKs.controller.TestController2.viewAll(java.lang.String,java.lang.String)]
206360 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'testController2'
206360 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'testController2'
执行进入TestInterceptor的preHandle方法
206381 [http-apr-8080-exec-1] DEBUG org.springframework.web.cors.DefaultCorsProcessor  - Skip CORS processing: request is from same origin
206381 [http-apr-8080-exec-1] DEBUG org.springframework.web.cors.DefaultCorsProcessor  - Skip CORS processing: request is from same origin
进入控制器的viewAll方法...
name=马云
pwd=123
执行进入TestInterceptor的postHandle方法
206404 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Invoking afterPropertiesSet() on bean with name '/hello'
206404 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Invoking afterPropertiesSet() on bean with name '/hello'
206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet  - Rendering view [org.springframework.web.servlet.view.JstlView: name '/hello'; URL [/WEB-INF/jsps//hello.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher'
206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet  - Rendering view [org.springframework.web.servlet.view.JstlView: name '/hello'; URL [/WEB-INF/jsps//hello.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher'
206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.view.JstlView  - Forwarding to resource [/WEB-INF/jsps//hello.jsp] in InternalResourceView '/hello'
206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.view.JstlView  - Forwarding to resource [/WEB-INF/jsps//hello.jsp] in InternalResourceView '/hello'
执行进入TestInterceptor的afterCompletion方法

看到了吧,拦截器方法可以对请求的数据做很多设置和修改,同样,也可以对响应的数据的编码等做修改。

  1. 解决权限验证问题

比如,我们现在需要一个拦截器,专门用来做权限验证,拦截器会在preHandle方法检查服务服务器session是否有该用户的会话,如果有则继续执行,如果没有则将响应重定向到登录页面。

这个部分因为在大部分业务模块中都需要先行完成,如果把这样一个共性的东西添加到各个业务模块,整个系统的代码质量和可维护性可想而知有多糟,这正是拦截器的用武之地和使用原则。

好,我们就试着写个代码示例,在preHandle方法中:

package com.happyBKs.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by happyBKs on 2016/7/10.
 */
public class TestInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入TestInterceptor的preHandle方法");
        request.setCharacterEncoding("utf-8");
        //对用户是否登录进行判断
        if(request.getSession().getAttribute("user")==null){
            //如果用户没有回话,即没有登录,就终止请求,并发送到登录页面
            request.getRequestDispatcher("/test2/login").forward(request,response);//发送到登录页面
            return false;//终止请求
        }
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行进入TestInterceptor的postHandle方法");
        //通过modelAndView参数改变显示的视图,或者修改发往视图的方法
//        modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据");
//        modelAndView.setViewName("/hello");

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行进入TestInterceptor的afterCompletion方法");
    }
}

好,我们尝试运行,并随便请求一个能给该拦截器拦截的url,如http://localhost:8080/mvc/test2/msg。这时候我们是没有登录过的,看看会发生什么。我们的预想是preHandle方法将验证到我们sessioin会话为空,然后跳转到登录页面。然而,恐怖的一幕发生了,页面死住,控制台开始疯狂套异常和死循环。。。

这是为什么呢?原来,我们的登录页面url /test2/login也在拦截器的拦截返回内,当我们请求http://localhost:8080/mvc/test2/msg,拦截器preHandle方法检查到了我们会话为空没有登录,然后请求被终止的同时转而请求同样在拦截器作用返回内的/test2/login,这时候死循环的故事就开始了,懂了吧。因此,拦截器的使用需要十分留心,拦截器的方法中在对请求进行转发时尤为要注意,请求转发不能是该拦截器,否则就会出现死循环。当然,还有一种情况,就是多个拦截器作用下的多个url相互转发请求,造成多个拦截器之间的死循环。

好,这里我们增加一个公共jsp页面\loginPub.jsp:页面内容不再详述

这里写图片描述

拦截器代码改为:

public class TestInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行进入TestInterceptor的preHandle方法");
        request.setCharacterEncoding("utf-8");
        //对用户是否登录进行判断
        if(request.getSession().getAttribute("user")==null){
            //如果用户没有回话,即没有登录,就终止请求,并发送到登录页面
            //request.getRequestDispatcher("/test2/login").forward(request,response);//发送到登录页面
            request.getRequestDispatcher("/loginPub.jsp").forward(request,response);//发送到登录页面
            return false;//终止请求
        }
        return true;
    }

运行结果:

请求http://localhost:8080/mvc/test2/msg

控制台输出显示,执行到了拦截器的preHandle方法之后就没有了,因为拦截器检测到没有登录,所以讲请求转发到了登录页面\loginPub.jsp

这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值