天天看點

基于SpringMVC的攔截器(Interceptor)和過濾器(Filter)的差別與聯系

一 簡介

(1)過濾器:

依賴于servlet容器。在實作上基于函數回調,可以對幾乎所有請求進行過濾,但是缺點是一個過濾器執行個體隻能在容器初始化時調用一次。使用過濾器的目的是用來做一些過濾操作,擷取我們想要擷取的資料,比如:在過濾器中修改字元編碼;在過濾器中修改HttpServletRequest的一些參數,包括:過濾低俗文字、危險字元等

關于過濾器的一些用法可以參考我寫過的這些文章:

  • 繼承HttpServletRequestWrapper以實作在Filter中修改HttpServletRequest的參數:https://www.zifangsky.cn/677.html
  • 在SpringMVC中使用過濾器(Filter)過濾容易引發XSS的危險字元:https://www.zifangsky.cn/683.html

(2)攔截器:

依賴于web架構,在SpringMVC中就是依賴于SpringMVC架構。在實作上基于Java的反射機制,屬于面向切面程式設計(AOP)的一種運用。由于攔截器是基于web架構的調用,是以可以使用Spring的依賴注入(DI)進行一些業務操作,同時一個攔截器執行個體在一個controller生命周期之内可以多次調用。但是缺點是隻能對controller請求進行攔截,對其他的一些比如直接通路靜态資源的請求則沒辦法進行攔截處理

  • 在SpringMVC中使用攔截器(interceptor)攔截CSRF攻擊(修):https://www.zifangsky.cn/671.html
  • SpringMVC中使用Interceptor+Cookie實作在一定天數之内自動登入:https://www.zifangsky.cn/700.html

二 多個過濾器與攔截器的代碼執行順序

如果在一個項目中僅僅隻有一個攔截器或者過濾器,那麼我相信相對來說了解起來是比較容易的。但是我們是否思考過:如果一個項目中有多個攔截器或者過濾器,那麼它們的執行順序應該是什麼樣的?或者再複雜點,一個項目中既有多個攔截器,又有多個過濾器,這時它們的執行順序又是什麼樣的呢?

下面我将用簡單的代碼來測試說明:

(1)先定義兩個過濾器:

i)過濾器1:

package cn.zifangsky.filter;

import java.io.IOException;

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

import org.springframework.web.filter.OncePerRequestFilter;

public class TestFilter1 extends OncePerRequestFilter {

	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//在DispatcherServlet之前執行
		System.out.println("############TestFilter1 doFilterInternal executed############");
		filterChain.doFilter(request, response);
		//在視圖頁面傳回給用戶端之前執行,但是執行順序在Interceptor之後
		System.out.println("############TestFilter1 doFilter after############");
//		try {
//			Thread.sleep(10000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
	}

}      

ii)過濾器2:

package cn.zifangsky.filter;

import java.io.IOException;

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

import org.springframework.web.filter.OncePerRequestFilter;

public class TestFilter2 extends OncePerRequestFilter {

	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		System.out.println("############TestFilter2 doFilterInternal executed############");
		filterChain.doFilter(request, response);
		System.out.println("############TestFilter2 doFilter after############");

	}

}      

iii)在web.xml中注冊這兩個過濾器:

<!-- 自定義過濾器:testFilter1 -->	
 	<filter>
		<filter-name>testFilter1</filter-name>
		<filter-class>cn.zifangsky.filter.TestFilter1</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>testFilter1</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<!-- 自定義過濾器:testFilter2 -->	
 	<filter>
		<filter-name>testFilter2</filter-name>
		<filter-class>cn.zifangsky.filter.TestFilter2</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>testFilter2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>      

(2)再定義兩個攔截器:

i)攔截器1,基本攔截器:

package cn.zifangsky.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 BaseInterceptor implements HandlerInterceptor{
	
	/**
	 * 在DispatcherServlet之前執行
	 * */
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		System.out.println("************BaseInterceptor preHandle executed**********");
		return true;
	}

	/**
	 * 在controller執行之後的DispatcherServlet之後執行
	 * */
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		System.out.println("************BaseInterceptor postHandle executed**********");
	}
	
	/**
	 * 在頁面渲染完成傳回給用戶端之前執行
	 * */
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		System.out.println("************BaseInterceptor afterCompletion executed**********");
//		Thread.sleep(10000);
	}

}      

ii)指定controller請求的攔截器:

package cn.zifangsky.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 TestInterceptor implements HandlerInterceptor {

	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		System.out.println("************TestInterceptor preHandle executed**********");
		return true;
	}

	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		System.out.println("************TestInterceptor postHandle executed**********");
	}

	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		System.out.println("************TestInterceptor afterCompletion executed**********");
	}
}      

iii)在SpringMVC的配置檔案中注冊這兩個攔截器:

<!-- 攔截器 -->
 	<mvc:interceptors>
		<!-- 對所有請求都攔截,公共攔截器可以有多個 -->
		<bean name="baseInterceptor" class="cn.zifangsky.interceptor.BaseInterceptor" />
		<!-- <bean name="testInterceptor" class="cn.zifangsky.interceptor.TestInterceptor" /> -->
		<mvc:interceptor>		
			<!-- 對/test.html進行攔截 -->
			<mvc:mapping path="/test.html"/>
			<!-- 特定請求的攔截器隻能有一個 -->
			<bean class="cn.zifangsky.interceptor.TestInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>      

(3)定義一個測試使用的controller:

package cn.zifangsky.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController {
	
	@RequestMapping("/test.html")
	public ModelAndView handleRequest(){
		System.out.println("---------TestController executed--------");
		return new ModelAndView("test");
	}
}      

(4)視圖頁面test.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>    
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<base href="<%=basePath%>">
<title>FilterDemo</title>
</head>
<body>
	<%
		System.out.println("test.jsp is loading");
	%>
	<div align="center">
		This is test page
	</div>
</body>
</html>      

(5)測試效果:

啟動此測試項目,可以看到控制台中輸出如下:

基于SpringMVC的攔截器(Interceptor)和過濾器(Filter)的差別與聯系

這就說明了過濾器的運作是依賴于servlet容器的,跟springmvc等架構并沒有關系。并且,多個過濾器的執行順序跟xml檔案中定義的先後關系有關

接着清空控制台中的輸出内容并通路:http://localhost:9180/FilterDemo/test.html

可以看到,此時的控制台輸出結果如下:

基于SpringMVC的攔截器(Interceptor)和過濾器(Filter)的差別與聯系

相信從這個列印輸出,大家就可以很清晰地看到有多個攔截器和過濾器存在時的整個執行順序了。當然,對于過個攔截器它們之間的執行順序跟在SpringMVC的配置檔案中定義的先後順序有關

注:對于整個SpringMVC的執行流程來說,如果加上上面的攔截器和過濾器,其最終的執行流程就如下圖所示:

基于SpringMVC的攔截器(Interceptor)和過濾器(Filter)的差別與聯系

PS:這個圖是我用畫圖軟體畫出來的,将就着看吧^_^