SpringMVC學習(十二)——SpringMVC中的攔截器
SpringMVC學習(十二)——SpringMVC中的攔截器
SpringMVC的處理器攔截器類似於Servlet開發中的過濾器Filter,用於對處理器進行預處理和後處理。本文主要總結一下SpringMVC中攔截器是如何定義的,以及測試攔截器的執行情況和使用方法。
SpringMVC中攔截器的定義和配置
SpringMVC中攔截器的定義
在SpringMVC中,定義攔截器要實現HandlerInterceptor接口,並實現該接口中提供的三個方法,如下:
public class Interceptor1 implements HandlerInterceptor { @Overridepublic 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........"); } @Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 返回ModelAndView之後 // Handler執行過程中出現異常,可以在此處理異常 System.out.println("Interceptor1 afterCompletion........"); } }
針對這三個方法,我做一下簡單的分析:
- preHandle方法:該方法執行的時機是在Handler執行之前執行。可以用於身份認證、身份授權等。比如如果認證沒有通過表示用戶沒有登錄,需要此方法攔截不再往下執行(return false),否則就放行(return true)。
- postHandle方法:該方法執行的時機是在Handler執行之後,在返回ModelAndView之前執行,可以看到該方法中有個modelAndView的形參。應用場景:從modelAndView出發,將公用的模型數據(比如菜單導航之類的)在這裏傳到視圖,也可以在這裏統一指定視圖。
- 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和2放行,所以攔截器3的preHandle才能執行。也就是說前面的攔截器放行了,後面的攔截器才能執行preHandle方法。
-
攔截器3不放行,所以其另外兩個方法沒有被執行。即如果某個攔截器不放行,那麽它的另外兩個方法就不會被執行。
-
只要有一個攔截器不放行,所有攔截器的postHandle方法都不會執行,但是只要執行過preHandle並且放行的,就會執行afterCompletion方法。
三個攔截器都不放行
這種情況其實可以參考上面的情況了,是一個特例,也看一下運行結果:
Interceptor1 preHandle........
很明顯,就只執行了第一個攔截器的preHandle方法,因為都不放行,所以沒有一個執行postHandle方法和afterCompletion方法。
總結
- preHandle按攔截器定義順序調用
- postHandler按攔截器定義逆序調用
- afterCompletion按攔截器定義逆序調用
- postHandler在攔截器鏈內所有攔截器返回true才調用
- afterCompletion只有preHandle返回true才調用
攔截器的應用
從第二種情況來看,比如現在要寫一個統一異常處理的邏輯,那麽要將該攔截器放在攔截器鏈的第一個位置,且一定要放行,因為只有放行了,才會去執行afterCompletion,而且放在攔截器鏈的第一個的話,afterCompletion方法會最後執行,才能在裏面執行統一異常處理的邏輯。
再比如,登錄認證攔截器,要放在攔截器鏈接中的第一個位置(如果有統一異常處理,那麽應該放在統一異常處理的後面);權限校驗攔截器,放在登錄認證攔截器之後(因為登錄通過後才校驗權限)。但我在這裏只寫一個登錄驗證的攔截器來以示說明如何使用SpringMVC的攔截器。
處理流程
- 先得有一個登錄頁面,然後需要寫一個Controller類訪問該頁面
- 登錄頁面有一個提交表單的動作,也即需要在Controller類中進行處理
- a) 判斷用戶名密碼是否正確
- b) 如果正確,向Session中寫入用戶信息
- c) 返回登錄成功頁面,或者跳轉到商品列表頁面
- 攔截器的處理
- 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中的攔截器