1. 程式人生 > >SpringMVC學習(十二)——SpringMVC中的攔截器

SpringMVC學習(十二)——SpringMVC中的攔截器

bsp 分析 打印 TP 過濾 列表 登錄驗證 ping pub

SpringMVC學習(十二)——SpringMVC中的攔截器

SpringMVC的處理器攔截器類似於Servlet開發中的過濾器Filter,用於對處理器進行預處理和後處理。本文主要總結一下SpringMVC中攔截器是如何定義的,以及測試攔截器的執行情況和使用方法。

SpringMVC中攔截器的定義和配置

SpringMVC中攔截器的定義

在SpringMVC中,定義攔截器要實現HandlerInterceptor接口,並實現該接口中提供的三個方法,如下:

public class Interceptor1 implements HandlerInterceptor {

    @Override
    
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor1 preHandle........"); // 執行的時機是在Handler執行之前執行此方法 // 返回值:如果返回true,就放行,不攔截,正常執行Handler進行處理 // 返回值:如果返回false,那就攔截,Handler就不能正常處理了
return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // Handler執行之後,在返回ModelAndView之前,對modelAndView做些手腳 System.out.println("Interceptor1 postHandle........"); } @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 返回ModelAndView之後 // Handler執行過程中出現異常,可以在此處理異常 System.out.println("Interceptor1 afterCompletion........"); } }

針對這三個方法,我做一下簡單的分析:

  1. preHandle方法:該方法執行的時機是在Handler執行之前執行。可以用於身份認證、身份授權等。比如如果認證沒有通過表示用戶沒有登錄,需要此方法攔截不再往下執行(return false),否則就放行(return true)。
  2. postHandle方法:該方法執行的時機是在Handler執行之後,在返回ModelAndView之前執行,可以看到該方法中有個modelAndView的形參。應用場景:從modelAndView出發,將公用的模型數據(比如菜單導航之類的)在這裏傳到視圖,也可以在這裏統一指定視圖。
  3. afterCompletion方法:返回ModelAndView之後執行。應用場景:統一異常處理(即Handler執行過程中出現異常,可以在此處理異常),統一日誌處理等。

SpringMVC中攔截器的配置

針對某種HandlerMapping配置攔截器

在SpringMVC中,攔截器是針對具體的HandlerMapping進行配置的,也就是說如果在某個HandlerMapping中配置攔截,經過該HandlerMapping映射成功的Handler最終會使用該攔截器。比如,假設我們在springmvc.xml配置文件中配置的映射器是org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,那麽我們可以這樣來配置攔截器:

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="handlerInterceptor1"/>
            <ref bean="handlerInterceptor2"/>
        </list>
    </property>
</bean>
<bean id="handlerInterceptor1" class="com.itheima.springmvc.interceptor.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="com.itheima.springmvc.interceptor.HandlerInterceptor2"/>

針對所有HandlerMapping配置全局攔截器

那麽在SpringMVC中,如何配置類似於全局的攔截器呢?上面也說了,SpringMVC中的攔截器是針對具體的映射器而言的,為了解決這個問題,SpringMVC框架將配置的類似全局的攔截器註入到每個HandlerMapping中,這樣就可以成為全局的攔截器了。配置如下:

<!-- 配置攔截器 -->
<mvc:interceptors>
    <!-- 按照配置的順序執行攔截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.springmvc.interceptor.Interceptor1"></bean>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.springmvc.interceptor.Interceptor2"></bean>
    </mvc:interceptor>
</mvc:interceptors>

註意:path=”/**”表示攔截所有的url包括子url路徑。在實際開發中,一般我們都用這種配置,<mvc:mapping>中指定要攔截的url即可。

SpringMVC攔截器的執行測試

仿照上面的HandlerInterceptor1再寫兩個攔截器,HandlerInterceptor2和HandlerInterceptor3,配置是按照上面這個配置,然後我們來測試一下三個攔截器的執行情況,並做相關總結。

三個攔截器都放行

也就是說,我們將三個攔截器的preHandle方法中的返回值都改成true,來測試一下攔截器的執行順序,訪問任何一個url,測試結果都將如下:

Interceptor1 preHandle........
Interceptor2 preHandle........
Interceptor3 preHandle........

Interceptor3 postHandle........
Interceptor2 postHandle........
Interceptor1 postHandle........

Interceptor3 afterCompletion........
Interceptor2 afterCompletion........
Interceptor1 afterCompletion........

根據打印的結果做一個總結:當所有攔截器都放行的時候,preHandle方法是按照配置的順序執行的;而另外兩個方法按照配置的順序逆向執行。

有一個攔截器不放行

我們將第三個攔截器的preHandle方法中的返回值改成false,前兩個還是true,來測試一下攔截器的執行順序,訪問任何一個url,測試結果都將如下:

Interceptor1 preHandle........
Interceptor2 preHandle........
Interceptor3 preHandle........
Interceptor2 afterCompletion........
Interceptor1 afterCompletion........

根據打印的結果做一個總結:

  1. 由於攔截器1和2放行,所以攔截器3的preHandle才能執行。也就是說前面的攔截器放行了,後面的攔截器才能執行preHandle方法。

  2. 攔截器3不放行,所以其另外兩個方法沒有被執行。即如果某個攔截器不放行,那麽它的另外兩個方法就不會被執行。

  3. 只要有一個攔截器不放行,所有攔截器的postHandle方法都不會執行,但是只要執行過preHandle並且放行的,就會執行afterCompletion方法。

三個攔截器都不放行

這種情況其實可以參考上面的情況了,是一個特例,也看一下運行結果:

Interceptor1 preHandle........

很明顯,就只執行了第一個攔截器的preHandle方法,因為都不放行,所以沒有一個執行postHandle方法和afterCompletion方法。

總結

  1. preHandle按攔截器定義順序調用
  2. postHandler按攔截器定義逆序調用
  3. afterCompletion按攔截器定義逆序調用
  4. postHandler在攔截器鏈內所有攔截器返回true才調用
  5. afterCompletion只有preHandle返回true才調用

攔截器的應用

從第二種情況來看,比如現在要寫一個統一異常處理的邏輯,那麽要將該攔截器放在攔截器鏈的第一個位置,且一定要放行,因為只有放行了,才會去執行afterCompletion,而且放在攔截器鏈的第一個的話,afterCompletion方法會最後執行,才能在裏面執行統一異常處理的邏輯。
再比如,登錄認證攔截器,要放在攔截器鏈接中的第一個位置(如果有統一異常處理,那麽應該放在統一異常處理的後面);權限校驗攔截器,放在登錄認證攔截器之後(因為登錄通過後才校驗權限)。但我在這裏只寫一個登錄驗證的攔截器來以示說明如何使用SpringMVC的攔截器。

處理流程

  1. 先得有一個登錄頁面,然後需要寫一個Controller類訪問該頁面
  2. 登錄頁面有一個提交表單的動作,也即需要在Controller類中進行處理
    • a) 判斷用戶名密碼是否正確
    • b) 如果正確,向Session中寫入用戶信息
    • c) 返回登錄成功頁面,或者跳轉到商品列表頁面
  3. 攔截器的處理
    • a) 攔截用戶請求,判斷用戶是否登錄
    • b) 如果用戶已經登錄,則放行
    • c) 如果用戶未登錄,則跳轉到登錄頁面

提供一個登錄頁面

在/WEB-INF/jsp目錄下編寫一個login.jsp登錄頁面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/user/login" method="post">
        <label>用戶名</label>
        <input type="text" name="username" />
        <br/>
        <label>密碼</label>
        <input type="password" name="password" />
        <br/>
        <input type="submit" value="提交" />
    </form>
</body>
</html>

編寫實現用戶登錄的Controller

在com.itheima.springmvc.controller包下編寫實現用戶登錄的UserController,如下:

public class UserController {

    @RequestMapping("/user/showlogin")
    public String showLogin() {
        return "login";
    }

    @RequestMapping("/user/login")
    public String userLogin(String username, String password, HttpSession session) {
        // a)   判斷用戶名密碼是否正確
        System.out.println(username);
        System.out.println(password);
        // b)   如果正確,向session中寫入用戶信息
        session.setAttribute("username", username);
        // c)   返回登錄成功頁面,或者跳轉到商品列表頁面
        return "redirect:/item/itemList.action";
    }

}

登錄驗證攔截器的實現

在com.itheima.springmvc.interceptor包下編寫一個登錄驗證的攔截器——LoginInterceptor.java,如下:

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String uri = request.getRequestURL().toString();
        // 如果用戶未登錄,則跳轉到登錄頁面,跳轉的過程中有可能被攔截,所以得做一個判斷
        if (uri.contains("login")) {
            return true;
        }
        System.out.println("Interceptor1 preHandle........");
        // a)   攔截用戶請求,判斷用戶是否登錄
        HttpSession session = request.getSession();
        Object username = session.getAttribute("username");
        if (username != null) {
            // b)   如果用戶已經登錄。放行
            return true;
        }
        // c)   如果用戶未登錄,跳轉到登錄頁面,跳轉的過程中有可能被攔截,所以得做一個判斷
        response.sendRedirect(request.getContextPath() + "/user/showlogin");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // Handler執行之後,在返回ModelAndView之前,對modelAndView做些手腳
        System.out.println("Interceptor1 postHandle........");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 返回ModelAndView之後
        // Handler執行過程中出現異常,可以在此處理異常
        System.out.println("Interceptor1 afterCompletion........");
    }

}

然後配置該攔截器:

<!-- 配置攔截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.springmvc.interceptor.LoginInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

這樣當我們任意請求一個url的時候,就會被剛剛我們定義的攔截器給捕獲到,然後會判斷session中是否有用戶信息,沒有的話就會跳到登陸頁面讓我們登陸。

SpringMVC學習(十二)——SpringMVC中的攔截器