1. 程式人生 > 實用技巧 >寫的太細了!Spring MVC攔截器的應用,建議收藏再看!

寫的太細了!Spring MVC攔截器的應用,建議收藏再看!

Spring MVC攔截器

  • 攔截器是Spring MVC中強大的控制元件,它可以在進入處理器之前做一些操作,或者在處理器完成後進行操作,甚至是在渲染檢視後進行操作。

攔截器概述

  • 對於任何優秀的MVC框架,都會提供一些通用的操作,如請求資料的封裝、型別轉換、資料校驗、解析上傳的檔案、防止表單的多次提交等。早期的MVC框架將這些操作都寫死在核心控制器中,而這些常用的操作又不是所有的請求都需要實現的,這就導致了框架的靈活性不足,可擴充套件性降低
  • SpringMVC提供了Interceptor攔截器機制,類似於Servlet中的Filter過濾器,用於攔截使用者的請求並做出相應的處理。比如通過攔截器來進行使用者許可權驗證,或者用來判斷使用者是否已經登入。Spring MVC攔截器是可插拔式的設計,需要某一功能攔截器,只需在配置檔案中應用該攔截器即可;如果不需要這個功能攔截器,只需在配置檔案中取消應用該攔截器。
  • 在Spring MVC中定義一個攔截器有兩種方法:實現HandlerInterceptor介面,實現WebRequestInterceptor介面.

實現HandlerInterceptor介面

首先來看看HandlerInterceor介面的原始碼,該介面位於org.springframework.web.servlet的包中,定義了三個方法,若要實現該介面,就要實現其三個方法:

preHandle()方法:該方法在執行控制器方法之前執行。返回值為Boolean型別,如果返回false,表示攔截請求,不再向下執行,如果返回true,表示放行,程式繼續向下執行(如果後面沒有其他Interceptor,就會執行controller方法)。所以此方法可對請求進行判斷,決定程式是否繼續執行,或者進行一些初始化操作及對請求進行預處理。

postHandle()方法:該方法在執行控制器方法呼叫之後,且在返回ModelAndView之前執行。由於該方法會在DispatcherServlet進行返回檢視渲染之前被呼叫,所以此方法多被用於處理返回的檢視,可通過此方法對請求域中的模型和檢視做進一步的修改。

afterCompletion()方法:該方法在執行完控制器之後執行,由於是在Controller方法執行完畢後執行該方法,所以該方法適合進行一些資源清理,記錄日誌資訊等處理操作。

實現了HandlerInterceptor介面之後,需要在Spring的類載入配置檔案中配置攔截器實現類,才能使攔截器起到攔截的效果,載入配置有兩種方式:

針對HandlerMapping配置,樣例程式碼如下:

這裡為BeanNameUrlHandlerMapping處理器配置了一個interceptors攔截器鏈,該攔截器鏈包含了myInterceptor1和myInterceptor2兩個攔截器,具體實現分別對應下面id為myInterceptor1和myInterceptor2的bean配置。

優點:此種配置的優點是針對具體的處理器對映器進行攔截操作

缺點:缺點是如果使用多個處理器對映器,就要在多處新增攔截器的配置資訊,比較繁瑣

針對全域性配置,樣例程式碼如下:

在上面的配置中,可在mvc:interceptors標籤下配置多個攔截器其子元素 bean 定義的是全域性攔截器,它會攔截所有的請求;而mvc:interceptor元素中定義的是指定元素的攔截器,它會對指定路徑下的請求生效,其子元素必須按照mvc:mapping --> mvc:exclude-mapping --> bean的順序,否則檔案會報錯。

實現WebRequestInterceptor介面

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

(1) preHandle(WebRequest request) :WebRequestInterceptor的該方法返回值為void,不是boolean。所以該方法不能用於請求阻斷,一般用於資源準備。

(2) postHandle(WebRequest request, ModelMap model):preHandle 中準備的資料都可以通過引數WebRequest訪問。ModelMap 是Controller 處理之後返回的Model 物件,可以通過改變它的屬性來改變Model 物件模型,達到改變檢視渲染效果的目的。

(3) afterCompletion(WebRequest request, Exception ex) :。Exception 引數表示的是當前請求的異常物件,如果Controller 丟擲的異常已經被處理過,則Exception物件為null 。

單個攔截器的執行流程

執行程式時,攔截器的執行時有一定順序的,該順序與配置檔案中所定義的攔截的順序相關。如果程式中只定義了一個攔截器,則該單個攔截器在程式中的執行流程如圖所示。

程式首先執行攔截器類中的preHandle()方法,如果該方法返回值是true,則程式會繼續向下執行處理器中的方法,否則不再向下執行;在業務控制器類Controller處理完請求後,會執行postHandle()方法,而後會通過DispatcherServlet向客戶端返回相應;在DispatcherServlet處理完請求後,才會執行afterCompletion()方法。

單個攔截器的執行流程

下面在springmvc-6的專案中通過示例來演示單個攔截器的執行流程,步驟如下:

(1) 在src目錄下的com.springmvc.controller包中的UserController類中,新建一個hello()方法,並使用@RequestMapping註解進行對映。

package com.springmvc.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.support.RequestContext;
import com.springmvc.entity.User;

@Controller
public class UserController {

    @RequestMapping("/hello")
    public String hello() {
    	System.out.println("Hello!Controller控制器類執行hello方法");
    	return "hello";
    }

}

(2) 在src目錄下,新建一個com.springmvc.interceptor包,建立攔截器類MyInterceptor,實現HandlerInterceptor介面。

package com.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor implements HandlerInterceptor{

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
	 Object handler)
			throws Exception {
		System.out.println("MyInterceptor 攔截器執行preHandle()方法");
		return true;
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("MyInterceptor 攔截器執行afterCompletion方法");
	}

	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
		System.out.println("MyInterceptor 攔截器執行postHandle()方法");
	}

}

(3) 在springmvc.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:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.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
        ">
  <!-- 配置自動掃描的包 -->
  <context:component-scan base-package="com.springmvc"/>
  <!-- 自動註冊處理器對映器和處理器介面卡 -->
  <mvc:annotation-driven/>

  <!-- 配置檢視解析器,將控制器方法返回的邏輯檢視解析為物理檢視 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/ch11/"></property>
  <property name="suffix" value=".jsp"></property>
  </bean>  

  <!-- 如果不想經過控制器類的處理方法直接轉發到頁面,可以通過mvc:view-controller元素來實現 -->
  <mvc:view-controller path="/success" view-name="success"/>  
  <mvc:view-controller path="/index" view-name="index"/>

  <mvc:default-servlet-handler/>

  <mvc:interceptors>
  <!-- 使用bean直接定義在mvc:interceptors下面的攔截器將攔截所有請求 -->
  <bean class="com.springmvc.interceptor.MyInterceptor"/>
  </mvc:interceptors>

 </beans>        

(4) 在ch11資料夾中,建立一個hello.jsp頁面檔案,在主體部分編寫“攔截器執行過程完成!”提示資訊。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
攔截器執行過程完成!
</body>
</html>

(5) 重啟Tomcat,訪問http://localhost:8080/springmvc-6/hello,瀏覽器會跳轉到hello.jsp頁面,控制檯的輸出結果。

多個攔截器的執行流程

在一個Web工程中,甚至在一個HandlerMapping處理器介面卡中都可以配置多個攔截器,每個攔截器都按照提前配置好的順序執行。它們內部的執行規律並不像多個普通Java類一樣,它們的設計模式是基於“責任鏈”的模式。

下面通過圖例來描述多個攔截器的執行流程,假設有兩個攔截器MyInterceptor1和MyInterceptor2,將MyInterceptor1配置在前,如圖所示。

當多個攔截器同時工作時,它們的preHandle()方法會按照配置檔案中攔截器的配置順序執行,而它們的postHandle()方法和afterCompletion()方法則會按照配置順序的反序執行

多個攔截器的執行流程

修改單個攔截器執行流程的例項,來演示多個攔截器的執行,步驟如下:

(1) 在com.springmvc.interceptor包中,新建兩個攔截器類MyInterceptor1和MyInterceptor2,這兩個攔截器類均實現了HandlerInterceptor介面,其程式碼與MyInterceptor相似。

MyInterceptor1

package com.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor1 implements HandlerInterceptor{

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
	 Object handler)
			throws Exception {
		System.out.println("MyInterceptor1 攔截器執行preHandle()方法");
		return true;
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("MyInterceptor1 攔截器執行afterCompletion方法");

	}

	public void postHandle(HttpServletRequest request, HttpServletResponse response, 
	Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("MyInterceptor1 攔截器執行postHandle()方法");

	}
}

MyInterceptor2

package com.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor2 implements HandlerInterceptor{

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("MyInterceptor2 攔截器執行preHandle()方法");
		return true;
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("MyInterceptor2 攔截器執行afterCompletion方法");

	}
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("MyInterceptor2 攔截器執行postHandle()方法");

	}
}

(2) 在springmvc.xml的配置檔案中,首先註釋掉前面配置的MyInterceptor攔截器,而後在mvc:interceptors元素內配置上面所定義的的兩個攔截器。

<mvc:interceptors>
  <!-- 定義多個攔截器 -->
   <mvc:interceptor><!-- 攔截器1 -->
     <mvc:mapping path="/**"/><!-- 配置攔截器所作用的路徑 -->
     <!-- 定義在<mvc:interceptor>下面的攔截器表示對匹配路徑請求才進行攔截 -->
     <bean class="com.springmvc.interceptor.MyInterceptor1"></bean>
   </mvc:interceptor>

   <mvc:interceptor><!-- 攔截器2 -->
     <mvc:mapping path="/hello"/>
     <bean class="com.springmvc.interceptor.MyInterceptor2"></bean>
   </mvc:interceptor>
  </mvc:interceptors>

(3) 重啟Tomcat,訪問 http://localhost:8080/springmvc-6/hello ,程式正確執行後,瀏覽器會跳轉到hello.jsp頁面,控制檯輸出內容如圖所示。

2.3 使用攔截器實現使用者登入許可權驗證

在springmvc-6專案中完成使用攔截器實現使用者登入許可權驗證,步驟如下:

(1) 在com.springmvc.controller包中,在控制器UserController類中,註釋以前的方法,並在該類中定義向主頁跳轉、向登入頁跳轉、執行使用者登入等操作的方法。

//向用戶登入頁面的跳轉方法
    @RequestMapping(value="/login",method=RequestMethod.GET)
    public String loginPage() {
    	System.out.println("使用者從login的請求到登入跳轉login.jsp");
    	return "login";
    }
    //使用者實現登入的方法
    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(User user,Model model,HttpSession session) {
    	String loginName=user.getLoginName();
    	String password=user.getPassword();
    	if(loginName.equals("mary") && password.equals("123456")) {
    		System.out.println("使用者登入成功");
    		//將使用者新增至session中儲存
    		session.setAttribute("current_user", user);
    		//重新定向到主頁的index跳轉方法
    		return "redirect:/index";
    	}
    	model.addAttribute("message", "賬號或者密碼錯誤,請重新登入");
    	//跳轉到登入頁面
		return "login";
    }
    //向主頁跳轉的方法
    @RequestMapping(value="/index",method=RequestMethod.GET)
    public String indexPage() {
    	System.out.println("使用者從index請求到主頁跳轉index.jsp頁面");
    	//跳轉到主頁面
    	return "index";
    }
    //使用者退出登入的方法
    @RequestMapping(value="/logout",method=RequestMethod.GET)
    public String logout(HttpSession session) {
    	//清除session
    	session.invalidate();
    	System.out.println("退出功能實現, 清除session,重定向到login請求");
    	return "redirect:/login";//重定向到登入頁面的跳轉方法
    }

(2) 在com.springmvc.interceptor包中,新建LoginInterceptor的攔截器類。

package com.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

//登入的攔截器類
public class loginInterceptor implements HandlerInterceptor {

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		//獲取請求的URI
		String url=request.getRequestURI();
		if(!url.toLowerCase().contains("login")) {
			//非登入請求,獲取session,判斷是否有使用者資料
			if(request.getSession().getAttribute("current_user")!=null) {
				//已經登入,放行
				return true;
			}else {
				//沒有登入則跳轉到登入頁面
				request.setAttribute("message", "您還沒有登入,請先登入");
				request.getRequestDispatcher("/ch11/login.jsp").forward(request, response);
			}
			}else {
				return true;//登入請求,放行
			}
            return false;//預設攔截
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, 
	Object handler,ModelAndView modelAndView) throws Exception {
		HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
	 Object handler, Exception ex)throws Exception {
		HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
	}

}

(3) 在springmvc.xml的配置檔案中,首先註釋前面配置過的攔截器,而後在mvc:interceptors元素內配置上面所定義的的LoginInterceptor攔截器。

<!-- 登入攔截器 -->
  <mvc:interceptors>
    <mvc:interceptor>
     <mvc:mapping path="/**"/><!--配置攔截器所作用的路徑  -->
     <bean class="com.springmvc.interceptor.loginInterceptor"></bean>
    </mvc:interceptor>
  </mvc:interceptors>

(4) 在ch11資料夾中,新建登入頁login.jsp和主頁index.jsp。

login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入頁面</title>
</head>
<body>
<font color="red">${requestScope.message }</font><br/><br/>
<h3>登入頁面</h3>
<form action="${pageContext.request.contextPath }/login" method="post">
賬號:<input type="text" name="loginName"/><br/><br/>
密碼:<input type="password" name="password"/><br/><br/>
<input type="submit" value="登入"/>
</form>
</body>
</html>

主頁index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主頁面</title>
</head>
<body>
歡迎: ${sessionScope.current_user.loginName }
<a href="${pageContext.request.contextPath }/logout">退出</a>
</body>
</html>

(5) 重啟Tomcat,訪問 http://localhost:8080/springmvc-6/index,執行介面如圖所示。

小結

Spring MVC攔截器介紹瞭如何在Spring MVC專案中定義和配置攔截器,講解了單個攔截器和多個攔截器的執行流程,最後通過一個使用者登入許可權驗證的示例講解了攔截器的實際應用,通過應用攔截器機制,Spring MVC框架可以使用可插拔方式管理各種功能。

最後

感謝你看到這裡,文章有什麼不足還請指正,覺得文章對你有幫助的話記得給我點個贊,每天都會分享java相關技術文章或行業資訊,歡迎大家關注和轉發文章!