1. 程式人生 > >SpringMVC學習記錄(七)--攔截器的使用

SpringMVC學習記錄(七)--攔截器的使用

SpringMVC的請求如下面這種圖所示:
此處輸入圖片的描述

可以看出所有的請求都要通過Dispatherservlet來接收,然後通過Handlermapping來決定使用哪個控制器,再根據ViewResolver來決定返回哪個檢視.從流程來看,Handlermapping就是我們可以實現攔截器的第一種方法.另外還有一種是實現WebRequestInterceptor介面,或者繼承其子類.

一.實現HandlerInterceptor介面

實現HandlerInterceptor介面或者繼承HandlerInterceptor的子類,比如比如Spring 已經提供的實現了HandlerInterceptor 介面的抽象類HandlerInterceptorAdapter ,下面講實現其介面的寫法,先看一下這個介面的三個方法.
- 方法preHandle

: 顧名思義,該方法將在請求處理之前進行呼叫。SpringMVC 中的Interceptor 是鏈式的呼叫的,在一個應用中或者說是在一個請求中可以同時存在多個Interceptor 。每個Interceptor 的呼叫會依據它的宣告順序依次執行,而且最先執行的都是Interceptor 中的preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布林值Boolean 型別的,當它返回為false 時,表示請求結束,後續的Interceptor 和Controller 都不會再執行;當返回值為true 時就會繼續呼叫下一個Interceptor 的preHandle 方法,如果已經是最後一個Interceptor 的時候就會是呼叫當前請求的Controller 方法。
- 方法postHandle
:由preHandle 方法的解釋我們知道這個方法包括後面要說到的afterCompletion 方法都只能是在當前所屬的Interceptor 的preHandle 方法的返回值為true 時才能被呼叫。postHandle 方法,顧名思義就是在當前請求進行處理之後,也就是Controller 方法呼叫之後執行,但是它會在DispatcherServlet 進行檢視返回渲染之前被呼叫,所以我們可以在這個方法中對Controller 處理之後的ModelAndView 物件進行操作。postHandle 方法被呼叫的方向跟preHandle 是相反的,也就是說先宣告的Interceptor 的postHandle 方法反而會後執行,這和Struts2 裡面的Interceptor 的執行過程有點型別。Struts2 裡面的Interceptor 的執行過程也是鏈式的,只是在Struts2 裡面需要手動呼叫ActionInvocation 的invoke 方法來觸發對下一個Interceptor 或者是Action 的呼叫,然後每一個Interceptor 中在invoke 方法呼叫之前的內容都是按照宣告順序執行的,而invoke 方法之後的內容就是反向的。
- 方法afterCompletion
:該方法也是需要當前對應的Interceptor 的preHandle 方法的返回值為true 時才會執行。顧名思義,該方法將在整個請求結束之後,也就是在DispatcherServlet 渲染了對應的檢視之後執行。這個方法的主要作用是用於進行資源清理工作的。

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

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

public class SpringMVCInterceptor implements HandlerInterceptor {


    /**
     * preHandle方法是進行處理器攔截用的,顧名思義,該方法將在Controller處理之前進行呼叫,SpringMVC中的Interceptor攔截器是鏈式的,可以同時存在
     * 多個Interceptor,然後SpringMVC會根據宣告的前後順序一個接一個的執行,而且所有的Interceptor中的preHandle方法都會在
     * Controller方法呼叫之前呼叫。SpringMVC的這種Interceptor鏈式結構也是可以進行中斷的,這種中斷方式是令preHandle的返
     * 回值為false,當preHandle的返回值為false的時候整個請求就結束了。
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        // TODO Auto-generated method stub
        return false;
    }

    /**
     * 這個方法只會在當前這個Interceptor的preHandle方法返回值為true的時候才會執行。postHandle是進行處理器攔截用的,它的執行時間是在處理器進行處理之
     * 後,也就是在Controller的方法呼叫之後執行,但是它會在DispatcherServlet進行檢視的渲染之前執行,也就是說在這個方法中你可以對ModelAndView進行操
     * 作。這個方法的鏈式結構跟正常訪問的方向是相反的,也就是說先宣告的Interceptor攔截器該方法反而會後呼叫,這跟Struts2裡面的攔截器的執行過程有點像,
     * 只是Struts2裡面的intercept方法中要手動的呼叫ActionInvocation的invoke方法,Struts2中呼叫ActionInvocation的invoke方法就是呼叫下一個Interceptor
     * 或者是呼叫action,然後要在Interceptor之前呼叫的內容都寫在呼叫invoke之前,要在Interceptor之後呼叫的內容都寫在呼叫invoke方法之後。
     */
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub

    }

    /**
     * 該方法也是需要當前對應的Interceptor的preHandle方法的返回值為true時才會執行。該方法將在整個請求完成之後,也就是DispatcherServlet渲染了檢視執行,
     * 這個方法的主要作用是用於清理資源的,當然這個方法也只能在當前這個Interceptor的preHandle方法的返回值為true時才會執行。
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
    throws Exception {
        // TODO Auto-generated method stub

    }

}

二.實現WebRequestInterceptor介面

WebRequestInterceptor 中也定義了三個方法,我們也是通過這三個方法來實現攔截的。這三個方法都傳遞了同一個引數WebRequest ,那麼這個WebRequest 是什麼呢?這個WebRequest 是Spring 定義的一個介面,它裡面的方法定義都基本跟HttpServletRequest 一樣,在WebRequestInterceptor 中對WebRequest 進行的所有操作都將同步到HttpServletRequest 中,然後在當前請求中一直傳遞。

  • 方法preHandle:方法將在請求處理之前進行呼叫,也就是說會在Controller 方法呼叫之前被呼叫。這個方法跟HandlerInterceptor 中的preHandle 是不同的,主要區別在於該方法的返回值是void ,也就是沒有返回值,所以我們一般主要用它來進行資源的準備工作,比如我們在使用Hibernate 的時候可以在這個方法中準備一個Hibernate 的Session 物件,然後利用WebRequest 的setAttribute(name, value, scope) 把它放到WebRequest 的屬性中。這裡可以說說這個setAttribute 方法的第三個引數scope ,該引數是一個Integer 型別的。在WebRequest 的父層介面RequestAttributes 中對它定義了三個常量:
    SCOPE_REQUEST :它的值是0 ,代表只有在request 中可以訪問。
    SCOPE_SESSION :它的值是1 ,如果環境允許的話它代表的是一個區域性的隔離的session,否則就代表普通的session,並且在該session範圍內可以訪問。
    SCOPE_GLOBAL_SESSION :它的值是2 ,如果環境允許的話,它代表的是一個全域性共享的session,否則就代表普通的session,並且在該session 範圍內可以訪問。

  • 方法postHandle:該方法將在請求處理之後,也就是在Controller 方法呼叫之後被呼叫,但是會在檢視返回被渲染之前被呼叫,所以可以在這個方法裡面通過改變資料模型ModelMap 來改變資料的展示。該方法有兩個引數,WebRequest 物件是用於傳遞整個請求資料的,比如在preHandle 中準備的資料都可以通過WebRequest 來傳遞和訪問;ModelMap 就是Controller 處理之後返回的Model 物件,我們可以通過改變它的屬性來改變返回的Model 模型。

  • 方法afterCompletion:該方法會在整個請求處理完成,也就是在檢視返回並被渲染之後執行。所以在該方法中可以進行資源的釋放操作。而WebRequest 引數就可以把我們在preHandle 中準備的資源傳遞到這裡進行釋放。Exception 引數表示的是當前請求的異常物件,如果在Controller 中丟擲的異常已經被Spring 的異常處理器給處理了的話,那麼這個異常物件就是是null 。
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;

public class AllInterceptor implements WebRequestInterceptor {

    /**
     * 在請求處理之前執行,該方法主要是用於準備資源資料的,然後可以把它們當做請求屬性放到WebRequest中
     */
    @Override
    public void preHandle(WebRequest request) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("AllInterceptor...............................");
        request.setAttribute("request", "request", WebRequest.SCOPE_REQUEST);//這個是放到request範圍內的,所以只能在當前請求中的request中獲取到
        request.setAttribute("session", "session", WebRequest.SCOPE_SESSION);//這個是放到session範圍內的,如果環境允許的話它只能在區域性的隔離的會話中訪問,否則就是在普通的當前會話中可以訪問
        request.setAttribute("globalSession", "globalSession", WebRequest.SCOPE_GLOBAL_SESSION);//如果環境允許的話,它能在全域性共享的會話中訪問,否則就是在普通的當前會話中訪問
    }

    /**
     * 該方法將在Controller執行之後,返回檢視之前執行,ModelMap表示請求Controller處理之後返回的Model物件,所以可以在
     * 這個方法中修改ModelMap的屬性,從而達到改變返回的模型的效果。
     */
    @Override
    public void postHandle(WebRequest request, ModelMap map) throws Exception {
        // TODO Auto-generated method stub
        for (String key:map.keySet())
            System.out.println(key + "-------------------------");;
        map.put("name3", "value3");
        map.put("name1", "name1");
    }

    /**
     * 該方法將在整個請求完成之後,也就是說在檢視渲染之後進行呼叫,主要用於進行一些資源的釋放
     */
    @Override
    public void afterCompletion(WebRequest request, Exception exception)
    throws Exception {
        // TODO Auto-generated method stub
        System.out.println(exception + "-=-=--=--=-=-=-=-=-=-=-=-==-=--=-=-=-=");
    }

}

三.攔截器的配置

1.mvc:interceptors標籤來宣告需要加入到SpringMVC攔截器鏈中的攔截器

    <mvc:interceptors>
        <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 -->
        <bean class="com.host.app.web.interceptor.AllInterceptor"/>
        <mvc:interceptor>
            <mvc:mapping path="/test/number.do"/>
            <!-- 定義在mvc:interceptor下面的表示是對特定的請求才進行攔截的 -->
            <bean class="com.host.app.web.interceptor.LoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

由上面的示例可以看出可以利用mvc:interceptors標籤宣告一系列的攔截器,然後它們就可以形成一個攔截器鏈,攔截器的執行順序是按宣告的先後順序執行的,先宣告的攔截器中的preHandle方法會先執行,然而它的postHandle方法和afterCompletion方法卻會後執行。
在mvc:interceptors標籤下宣告interceptor主要有兩種方式:
(1)直接定義一個Interceptor實現類的bean物件。使用這種方式宣告的Interceptor攔截器將會對所有的請求進行攔截。
(2)使用mvc:interceptor標籤進行宣告。使用這種方式進行宣告的Interceptor可以通過mvc:mapping子標籤來定義需要進行攔截的請求路徑。
經過上述兩步之後,定義的攔截器就會發生作用對特定的請求進行攔截了。

例子:

最近正好寫了一個簡單的登入驗證攔截器:

下面攔截器,判斷session裡面是否存在登入,存在且允許訪問,不存在則跳轉到登入頁面

package com.aust.interceptor;

import com.aust.model.system.SysUser;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class LoginUserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String url = httpServletRequest.getRequestURI();
        if (url.indexOf("system/login")>0){
            return true;
        }
        //判斷session是否已登入
        HttpSession session = httpServletRequest.getSession();
        SysUser user = (SysUser) session.getAttribute("loginsucess");
        if (user != null){
            return true;
        }
        //執行到這裡說明沒有session,需要攔截
        httpServletRequest.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(httpServletRequest,httpServletResponse);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

需要在springMVC.xml中配置:

    <!--訪問攔截-->
    <mvc:interceptors>
        <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 -->
        <!--<bean class="com.host.app.web.interceptor.AllInterceptor"/>-->
        <mvc:interceptor>
        <!-- 對system下的請求全部攔截 -->
            <mvc:mapping path="/system/*"/>
            <!-- 定義在mvc:interceptor下面的表示是對特定的請求才進行攔截的 -->
            <bean class="com.aust.interceptor.LoginUserInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

這樣就實現了訪問攔截功能