springMVC筆記系列(23)——攔截器及其在亂碼問題和登陸問題的應用實現

攔截器的概念css

A:「什麼是攔截器?」html

B:「攔截器是經過統一攔截從客戶端發往服務器的請求來完成功能的加強。」前端

A:(一臉懵逼)java

B:「說得簡單點,攔截器就是在客戶端向服務器端發出請求的期間,在請求交友服務器處理以前或以後對請求數據作一些修改或者其餘相關的操做。」web

A:「能說說具體實現什麼功能嗎?」spring

B:「攔截器的使用場景是解決一些共性問題。好比亂碼問題、權限驗證問題。你能夠將這些共性的操做從各個不一樣業務功能的控制器方法中抽離出來,放在統一的攔截器中執行,這樣你的解決亂碼問題的代碼,或者權限驗證的代碼不會重複地出如今各個不一樣業務功能對應的控制器方法執行體中。」express

A:「我能夠理解爲攔截器是看門狗嗎?這隻狗負責看門,監管進門和出門的人;這隻狗也能不一樣人家的門前作相同的事情——攔截出門或進門的人們。」json

B:「能文雅點嗎。。。」spring-mvc

A:「springMVC攔截器的出現卻是幫咱們在MVC實現時,營造了一種AOP的效果」tomcat

B:「spring大法萬歲!」

A:「。。。」

好,小段子結束,仍是用博客的正常寫法來寫吧。

 

這裏,也許你還要問——攔截器和過濾器有什麼區別?

過濾器Filter依賴於Servlet容器,Filter被Servlet容器所管理;基於回調函數;過濾範圍大(請求、資源等)

攔截器Interceptor依賴於springMVC框架容器;基於反射機制;只過濾請求

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

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. 以」/’開頭和以」/*」結尾的是用來作路徑映射的。 

2. 之前綴」*.」開頭的是用來作擴展映射的。 

3. 「/」 是用來定義default servlet映射的。 

4. 剩下的都是用來定義詳細映射的。好比: /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

看到了吧,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.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方法

看到了吧,攔截器方法能夠對請求的數據作不少設置和修改,一樣,也能夠對響應的數據的編碼等作修改。

 

2. 解決權限驗證問題

好比,咱們如今須要一個攔截器,專門用來作權限驗證,攔截器會在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

相關文章
相關標籤/搜索