Java Servlet 過濾器與 springmvc 攔截器的區別?
前言:在工作中,遇到需要記錄日誌的情況,不知道該選擇過濾器還是攔截器,故總結了一下。
servlet 過濾器
定義
java過濾器能夠對目標資源的請求和響應進行截取。過濾器的工作方式分為四種
應用場景
可以通過 doFilter 方法的 request、response 提前過濾一些不想要的信息,統一設置一些參數、統一設置字符集、控制權限是否登錄等。
配置
<!-- 定義Filter --> <filter> <!-- Filter的名字 --> <filter-name>loginFilter</filter-name> <!-- Filter的實現類 --> <filter-class>com.yule.common.filters.LoginFilter</filter-class> </filter> <!-- 定義Filter攔截的URL地址 --> <filter-mapping> <!-- Filter的名字 --> <filter-name>loginFilter</filter-name> <!-- Filter負責攔截的URL 全部以/的請求,如果/*,將會所有的請求--> <url-pattern>/*</url-pattern> </filter-mapping>
過濾器的4種工作方式
<filter-mapping> <filter-name>myFilter</filter-name> <servlet-name>目標資源</servlet-name> <dispatcher>REQUEST</dispatcher> </filter-mapping>
四中工作方式通過配置 <dispatcher> 標簽來決定
- request 過濾器:不配 <dispatcher> 標簽,或者配置為 <dispatcher>REQUEST</dispatcher> 。說明只有直接訪問該目標資源時該過濾器才會起作用,對轉發到該目標資源的請求將忽略不處理。
- forward 過濾器:配置為 <dispatcher>FORWARD</dispatcher> 。表示對轉發到目標資源的請求過濾,如果直接訪問目標資源,過濾器則不起作用。
- include 過濾器:配置為 <dispatcher>INCLUDE</dispatcher> 。表示對包含了目標資源的請求過濾,如果直接訪問目標資源,則此過濾器將不起作用 include 包含以下語句: 在 JSP 頁面中的動作:<jsp:include page=....... 在 Java 代碼中的 request.getRequestDispatcher("....").include 註意:如果目標資源一通過 <%@ include file="目標資源二"%> 指令包含,這時此過濾器不工作,因為這個是指令,在JSP 編譯時插入一個包含文本或代碼的文件,這個包含的過程是靜態的。
- error 過濾器:配置為
<filter-mapping> <filter-name>myFilter</filter-name> <url-pattern>/error.jsp</url-pattern> <dispatcher>ERROR</dispatcher> </filter-mapping> <error-page> <error-code>404</error-code> <location>/error.jsp</location> </error-page>
當我們訪問一個web目標資源時,如果服務器沒有找到該目標資源,那麽服務器就會給出一個404錯誤代碼。如果我們給404錯誤代碼定義一個頁面,那麽當404發生時就會調用該頁面。 當我們訪問一個不存在的文件時,就會訪問error.jsp,但是配置了過濾器對錯誤頁面進行過濾,所以過濾器先接受到請求,然後再轉發給error.jsp。 如果我們訪問一個已經存在的頁面,會不會調用error.jsp呢?如果這個頁面中有response.sendError(404,"出錯了!");那麽該錯誤頁面仍然會被調用,過濾器也會工作。
執行順序
根據 web.xml 的代碼順序來決定過濾器的執行順序。Filter 鏈: 一個Web應用中,可以編寫多個Filter,這些 Filter 組合起來稱之為一個Filter鏈。
當第一個 Filter 的 doFilter 方法被調用時,web 服務器會創建一個代表 Filter 鏈的 FilterChain 對象傳遞給該方法。在 doFilter 方法中,如果調用了 FilterChain 對象的 doFilter 方法,則 web 服務器會檢 FilterChain 對象中是否還有 filter ,如果有,則調用第下一個 filter,如果沒有,則調用目標資源。
init() 方法和 destroy() 方法隨著項目的啟動和關閉才會被調用,且僅一次。
舉個栗子
web.xml 中
<!-- 定義Filter --> <filter> <!-- Filter的名字 --> <filter-name>demoFilter</filter-name> <!-- Filter的實現類 --> <filter-class>com.yule.common.filters.DemoFilter</filter-class> </filter> <!-- 定義Filter攔截的URL地址 --> <filter-mapping> <!-- Filter的名字 --> <filter-name>demoFilter</filter-name> <!-- Filter負責攔截的URL 全部以/的請求,如果/*,將會所有的請求--> <url-pattern>/*</url-pattern> </filter-mapping>
Java 代碼
package com.yule.common.filters; import javax.servlet.*; import java.io.IOException; /** * 過濾器 * @author yule * @date 2018/7/2 21:52 */ public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("demo過濾器init。。。"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("demo過濾器doFilter。。。此處省略業務處理邏輯"); //通過判斷是否繼續往下走 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("demo過濾器destroy。。。"); } }
springmvc 攔截器
定義
Spring Web MVC的處理器攔截器。類似於Servlet開發中的過濾器Filter,用於對處理器進行預處理和後處理。攔截器是面向切面編程的,依賴的技術就是Java的動態代理。
應用場景
日誌記錄:記錄請求日誌等
。
權限檢查:白名單等
;
性能監控:
可以通過攔截器在進入處理器之前記錄開始時間,在處理完後記錄結束時間,從而得到該請求的處理時間;
通用行為:
讀取cookie得到用戶信息並將用戶對象放入請求,從而方便後續流程使用,還有如提取Locale、Theme信息等,只要是多個處理器都需要的即可使用攔截器實現。
OpenSessionInView:
如Hibernate,在進入處理器打開Session,在完成後關閉Session。
本質是AOP(面向切面編程),符合 AOP 的所有功能都可以使用攔截器實現。
配置
在 spring-mvc.xml 中
<mvc:interceptors> <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 --> <!-- <bean class="com.bybo.aca.web.interceptor.Login"/> --> <mvc:interceptor> <!--進行攔截的地址--> <mvc:mapping path="/**"/> <bean class="com.yule.common.interceptors.DemoInterceptor"/> </mvc:interceptor> </mvc:interceptors>
執行順序
根據 xml 中的配置順序來執行。攔截器的執行順序在過濾器之間。
方法說明
preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)
方法,該法在請求處理之前進行調用。SpringMVC 中的 Interceptor 是鏈式調用的,在一個應用中或者說是在一個請求中可以同時存在多個 Interceptor 。每個 Interceptor 的調用會依據它的聲明順序依次執行,而且最先執行的都是 Interceptor 中的 preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求做一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值 Boolean 類型的,當它返回為 false 時,表示請求結束,後續的 Interceptor 和 Controller 都不會再執行;當返回值為 true 時,就會繼續調用下一個 Interceptor 的 preHandle 方法,如果已經是最後一個 Interceptor 的時候,就會是調用當前請求的 Controller 中的方法。postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)
方法,通過 preHandle 方法的解釋咱們知道這個方法包括後面要說到的 afterCompletion 方法都只能在當前所屬的 Interceptor 的 preHandle 方法的返回值為 true 的時候,才能被調用。postHandle 方法在當前請求進行處理之後,也就是在 Controller 中的方法調用之後執行,但是它會在 DispatcherServlet 進行視圖返回渲染之前被調用,所以咱們可以在這個方法中對 Controller 處理之後的 ModelAndView 對象進行操作。postHandle 方法被調用的方向跟 preHandle 是相反的,也就是說,先聲明的 Interceptor 的 postHandle 方法反而會後執行。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
方法,也是需要當前對應的 Interceptor 的 preHandle 方法的返回值為 true 時才會執行。因此,該方法將在整個請求結束之後,也就是在 DispatcherServlet 渲染了對應的視圖之後執行,這個方法的主要作用是用於進行資源清理的工作。afterCompletion 方法被調用的方向也跟 preHandle 是相反的,也就是說,先聲明的 Interceptor 的 afterCompletion 方法反而會後執行
舉個栗子
spring-mvc 中
<mvc:interceptors> <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 --> <!-- <bean class="com.bybo.aca.web.interceptor.Login"/> --> <mvc:interceptor> <!--進行攔截的地址--> <mvc:mapping path="/**"/> <bean class="com.yule.common.interceptors.DemoInterceptor"/> </mvc:interceptor> </mvc:interceptors>
Java 代碼
package com.yule.common.interceptors; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定義攔截器方式一 * Created by yule on 2018/7/2 22:37. */ public class DemoInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //return true 表示繼續下一個攔截器或者 control 層 //return false 表示被攔截下來 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 { } }
package com.yule.common.interceptors; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定義攔截器方式二 * 一般都是通過實現HandlerInterceptor接口或者繼承HandlerInterceptorAdapter抽象類,復寫preHandle()、postHandle()和afterCompletion()這 3 個方法來對用戶的請求進行攔截處理 * Created by yule on 2018/7/2 22:43. */ public class Demo2Interceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } }
區別
其實兩者還是有相似之處,就是都可以用作權限檢查、日誌記錄等情況,但是在這些情況下如何選擇就要知道不同之處。
不同之處
使用範圍不同:Filter 只能用於 Web 程序中。而攔截器可以用於 Web 程序,Application、Swing 程序中。
規範不同:Filter 是 servlet 規範規定的,是 servlet 支持的。而攔截器是在 spring 容器內,是 spring 框架支持的。
使用資源不同:Filter 不能直接使用 spring 的資源、對象等。而攔截器是一個 spring 組件,歸 spring 管理,配置在 spring 文件中,因此能使用 spring 的任何資源、對象,例如 Service 對象、數據源、事務管理等,通過 IoC 註入到攔截器即可。也就是說在攔截器中可以註入一個 service ,用於業務邏輯或者訪問數據庫。
深度不同:Filter 只在 Servlet 前後起作用。而攔截器能夠深入到方法前後、異常拋出前後等,因此攔截器的使用具有更大的彈性。
作用範圍不同:攔截器只能對 Controller 層請求起作用,而過濾器則可以對幾乎所有的請求起作用(如 .js、.css等)。
所以,在 Spring 構架的程序中,要優先使用攔截器。
註意
攔截器是在過濾器之間運行的。
執行順序舉例
攔截器,spring-mvc.xml 中:
<mvc:interceptors> <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 --> <!-- <bean class="com.bybo.aca.web.interceptor.Login"/> --> <mvc:interceptor> <!--進行攔截的地址--> <mvc:mapping path="/**"/> <bean class="com.yule.common.interceptors.DemoInterceptor"/> </mvc:interceptor> <mvc:interceptor> <!--進行攔截的地址--> <mvc:mapping path="/**"/> <bean class="com.yule.common.interceptors.Demo2Interceptor"/> </mvc:interceptor> </mvc:interceptors>
java 代碼:
package com.yule.common.interceptors; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定義攔截器方式一 * Created by yule on 2018/7/2 22:37. */ public class DemoInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //return true 表示繼續下一個攔截器或者 control 層 //return false 表示被攔截下來 System.out.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("afterCompletion"); } }View Code
package com.yule.common.interceptors; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定義攔截器方式二 * 一般都是通過實現HandlerInterceptor接口或者繼承HandlerInterceptorAdapter抽象類,復寫preHandle()、postHandle()和afterCompletion()這 3 個方法來對用戶的請求進行攔截處理 * Created by yule on 2018/7/2 22:43. */ public class Demo2Interceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle 2222222..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle 22222222"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion 2222222"); } }View Code
過濾器, web.xml 中:
<!-- 定義Filter --> <filter> <!-- Filter的名字 --> <filter-name>demoFilter</filter-name> <!-- Filter的實現類 --> <filter-class>com.yule.common.filters.DemoFilter</filter-class> </filter> <!-- 定義Filter攔截的URL地址 --> <filter-mapping> <!-- Filter的名字 --> <filter-name>demoFilter</filter-name> <!-- Filter負責攔截的URL 全部以/的請求,如果/*,將會所有的請求--> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>demo2Filter</filter-name> <filter-class>com.yule.common.filters.Demo2Filter</filter-class> </filter> <filter-mapping> <filter-name>demo2Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
java 代碼:
package com.yule.common.filters; import javax.servlet.*; import java.io.IOException; /** * 過濾器 * @author yule * @date 2018/7/2 21:52 */ public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("demo過濾器init。。。"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("demo過濾器doFilter。。。此處省略業務處理邏輯"); //通過判斷是否繼續往下走 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("demo過濾器destroy。。。"); } }View Code
package com.yule.common.filters; import javax.servlet.*; import java.io.IOException; /** * Created by yule on 2018/7/2 22:18. */ public class Demo2Filter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("demo2過濾器init 2222222"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("demo過濾器doFilter 222222"); //通過判斷是否繼續往下走 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("demo2過濾器destroy 22222 "); } }View Code
調用 controller 打印結果:
Java Servlet 過濾器與 springmvc 攔截器的區別?