SpringMVC學習記錄(七)--攔截器的使用
SpringMVC的請求如下面這種圖所示:
可以看出所有的請求都要通過Dispatherservlet來接收,然後通過Handlermapping來決定使用哪個控制器,再根據ViewResolver來決定返回哪個檢視.從流程來看,Handlermapping就是我們可以實現攔截器的第一種方法.另外還有一種是實現WebRequestInterceptor介面,或者繼承其子類.
一.實現HandlerInterceptor介面
實現HandlerInterceptor介面或者繼承HandlerInterceptor的子類,比如比如Spring 已經提供的實現了HandlerInterceptor 介面的抽象類HandlerInterceptorAdapter ,下面講實現其介面的寫法,先看一下這個介面的三個方法.
- 方法preHandle
- 方法postHandle
- 方法afterCompletion
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>
這樣就實現了訪問攔截功能