Spring-Mvc框架中的攔截器和過濾器
一.知識背景介紹
首先我們要知道該博客的監聽器和攔截器和過濾器概念以及實例是建立在SSM框架之上的,對於SSM框架內部運行原理不懂的大家可以去參考我的另一篇博客:
spring架構---spring-Mvc運行原理解讀 https://blog.csdn.net/weixin_42504145/article/details/84074628
1.監聽器
Listener是實現了javax.servlet.ServletContextListener接口的服務器端程序,它也是隨web應用的啓動而啓動,只初始化了一次,隨web應用的停止而銷燬.
2.攔截器
Interceptor是動態攔截Action調用的對象。它提供了一種機制可以使開發者可以定義在一個Action執行的前後執行的代碼,也可以在一個Action執行前阻止其執行 。同時也提供了一種可以提取Action中可重用的部分的方式。
3.過濾器
Filter是實現了javax.servlet.Filter接口的服務器端程序,主要的用途是過濾字符編碼,做一些業務邏輯判斷,過濾器隨web應用啓動而啓動,只初始化一次,只有當web應用停止或重新部署才銷燬
二.三種方式代碼示例
再看代碼實例之前我們先看看Spring-Mvc負責分發請求的Dispatcher
DispatcherServlet是ssm框架前置控制器,所有的請求都通過DispatcherServlet來進行分發,配置在web.xml文間中.DispatcherServlet攔截匹配的請求也就是Servlet攔截匹配規則可以自己定義,像下面一行代碼<url-pattern>/</url-pattern>就是缺省匹配,定義規則可以參考下面這篇博客
servlet的url-pattern匹配規則 http://www.cnblogs.com/canger/p/6084846.html
首先進入方法doDispatch(HttpServletRequest request, HttpServletResponse response)經過一系列步驟,找到你要請求的目標Controller,最後交給他處理(沒有其他的攔截器)來處理。如果沒有handle處理器就會返回去,不再執行後面,所以說只能攔截severlet請求,但是不能對這個請求進行處理(缺陷解決在下面).
web.xml配置:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup> <!-- 代表servlet啓動就初始化 -->
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/</url-pattern> <!-- 匹配規則攔截 -->
</servlet-mapping>
1.監聽器(Listener)
Servlet的監聽器Listener,它是實現了javax.servlet.ServletContextListener接口的服務器端程序,它也是隨web應用的啓動而啓動,只初始化一次,隨web應用的停止而銷燬。主要作用是:做一些初始化的內容添加工作、設置一些基本的內容、比如一些參數或者是一些固定的對象等等。
在javax.servlet.ServletContextListener接口中定義了2種方法:
void contextInitialized(ServletContextEvent sce) 監聽器的初始化
void contextDestroyed(ServletContextEvent sce) 監聽器銷燬
使用樣例如下:
package com.cn.util;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ServletContextListenerUtil implements ServletContextListener{
//監聽器的初始化
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("監聽器ServletContextListenerUtil初始化");
}
//監聽器銷燬
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("監聽器ServletContextListenerUtil銷燬");
}
}
需要注意的是在Spring-Mvc項目啓動時,先啓動監聽器,再啓動過濾器。
2.過濾器(filter)
Servlet中的過濾器Filter是實現了javax.servlet.Filter接口的服務器端程序,主要的用途是過濾字符編碼、做一些業務邏輯判斷等.其工作原理是,只要你在web.xml文件配置好要攔截的客戶端請求,它都會幫你攔截到請求,此時你就可以對請求或響應(Request、Response)統一設置編碼,簡化操作;同時還可以進行邏輯判斷,如用戶是否已經登錄、有沒有權限訪問該頁面等等工作,它是隨你的web應用啓動而啓動的,只初始化一次,以後就可以攔截相關的請求,只有當你的web應用停止或重新部署的時候才能銷燬。
在javax.servlet.Filter接口中定義了3個方法:
void init(FilterConfig filterConfig) 用於完成過濾器的初始化
void destroy() 用於過濾器銷燬前,完成某些資源的回收
void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) 實現過濾功能,該方法對每個請求增加額外的處理
package com.cn.util;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FilterUtil implements Filter{
@SuppressWarnings("unused")
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
System.out.println("過濾器Filter初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("FilterUtil just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpRequest.setCharacterEncoding(this.filterConfig.getInitParameter("encoding"));
httpResponse.setCharacterEncoding(this.filterConfig.getInitParameter("encoding"));
chain.doFilter(httpRequest, httpResponse);
}
@Override
public void destroy() {
System.out.println("過濾器Filter銷燬");
}
}
web.xml配置如下:
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>com.qcbylearn.utils.LoginFilter</filter-class>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<!-- 這裏表示對所有的以jsp後綴的文件有效,其他的無效 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<!-- 這裏表示對所有的以html後綴的文件有效,其他的無效 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>requestFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 這裏寫Filter的匹配規則 -->
</filter-mapping>
3.自定義攔截器(Interceptor)
攔截器是在面向切面編程中應用的,就是在你的service或者一個方法前調用一個方法,或者在方法後調用一個方法比如動態代理就是攔截器的簡單實現,在你調用方法前打印出字符串(或者做其它業務邏輯的操作),也可以在你調用方法後打印出字符串,甚至在你拋出異常的時候做業務邏輯的操作。攔截器不是在web.xml配置的,比如struts在struts.xml配置,在springMVC在spring與springMVC整合的配置文件中配置。
首先請求通過統一入口進入DispatcherServlet,再DispatcherServlet裏面有個方法尋找這個這個請求的處理器和Interceptor,這時候就會尋找到HandlerInterceptor 接口,或者是這個類繼承實現了HandlerInterceptor 接口的類,就會找到你自定義的攔截器,繼承HandlerInterceptor 有是三個方法,preHandle , postHandle 和 afterCompletion,preHandle 在業務處理器處理請求之前被調用,然後處理完請求就會調用postHandle,或者在視圖渲染之前調用它,最後請求執行完,視圖渲染完調用,DispatcherServlet完全處理完請求後被調用afterCompletion,可用於清理資源,如果定義多個攔截器,先定義的攔截器先執行preHandle,但是沒有另外的兩個方法,而是等待其他攔截器執行prehandle方法,知道最後一個攔截器執行完畢,也是從最後一個攔截器逆序執行這兩個方法。
在springmvc中,定義攔截器要實現HandlerInterceptor接口,並實現該接口中提供的三個方法:
1.preHandle方法:進入Handler方法之前執行。可以用於身份認證、身份授權。比如如果認證沒有通過表示用戶沒有登陸,需要此方法攔截不再往下執行(return false),否則就放行(return true)。
2.postHandle方法:進入Handler方法之後,返回ModelAndView之前執行。可以看到該方法中有個modelAndView的形參。應用場景:從modelAndView出發:將公用的模型數據(比如菜單導航之類的)在這裏傳到視圖,也可以在這裏同一指定視圖。
3.afterCompletion方法:執行Handler完成之後執行。應用場景:統一異常處理,統一日誌處理等。
在springmvc中,攔截器是針對具體的HandlerMapping進行配置的,也就是說如果在某個HandlerMapping中配置攔截,經過該 HandlerMapping映射成功的handler最終使用該攔截器。
給出樣例代碼:
package com.qcbylearn.utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class HandleInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 所有請求第一個進入的方法
RequestWrapper myRequestWrapper = new RequestWrapper((HttpServletRequest) request);
String reqURL = request.getRequestURL().toString();
String ip = request.getRemoteHost ();
InputStream is = request.getInputStream ();
StringBuilder responseStrBuilder = new StringBuilder ();
BufferedReader streamReader = new BufferedReader (new InputStreamReader (is,"UTF-8"));
String inputStr;
while ((inputStr = streamReader.readLine ()) != null)
responseStrBuilder.append (inputStr);
// System.out.println("請求參數: " + responseStrBuilder.toString ());
String parmeter = responseStrBuilder.toString();
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
if (handler instanceof HandlerMethod) {
StringBuilder sb = new StringBuilder(1000);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
sb.append("-----------------------").append(df.format(new Date())).append("-------------------------------------\n");
HandlerMethod h = (HandlerMethod) handler;
//Controller 的包名
sb.append("Controller: ").append(h.getBean().getClass().getName()).append("\n");
//方法名稱
sb.append("Method : ").append(h.getMethod().getName()).append("\n");
//請求方式 post\put\get 等等
sb.append("RequestMethod : ").append(request.getMethod()).append("\n");
//所有的請求參數
sb.append("Params : ").append(parmeter).append("\n");
//部分請求鏈接
sb.append("URI : ").append(request.getRequestURI()).append("\n");
//完整的請求鏈接
sb.append("AllURI : ").append(reqURL).append("\n");
//請求方的 ip地址
sb.append("request IP: ").append(ip).append("\n");
System.out.println(sb.toString());
}
// 修改request中的參數並保存到request中
// request.setAttribute("parmeter_json", parmeter);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
Spring-mvc.xml配置如下:
<!-- 攔截器配置 -->
<mvc:interceptors>
<!--多個攔截器,順序執行 -->
<!-- 登陸認證攔截器 -->
<mvc:interceptor>
<!-- /** 表示攔截所有url包括子url路徑,/*只攔截根下的url -->
<mvc:mapping path="/**"/>
<bean id="handleInterceptor" class="com.qcbylearn.utils.HandleInterceptor">
</mvc:interceptor>
<!-- 其他攔截器 -->
</mvc:interceptors>
三.三者問題對比
問題一:過濾器和攔截器的區別?
①攔截器是基於java的反射機制的,而過濾器是基於函數回調.
②攔截器不依賴與servlet容器,過濾器依賴與servlet容器。
③攔截器只能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用。
④攔截器可以訪問action上下文、值棧裏的對象,而過濾器不能訪問。
⑤在action的生命週期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次。
⑥攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器裏注入一個service,可以調用業務邏輯。
問題二:過濾器和攔截器的執行順序?
過濾器和攔截器的執行順序:過濾前 - 攔截前 - Action處理 - 攔截後 - 過濾後。攔截器是被包裹在過濾器之中的。
問題三:各自應用場景?
兩者的本質區別:從靈活性上說攔截器功能更強大些,Filter能做的事情,Interceptor都能做,而且可以在請求前,請求後執行,比較靈活。Filter主要是針對URL地址做一個編碼的事情、過濾掉沒用的參數、安全校驗(比較泛的,比如登錄狀態之類),太細的話,還是建議用interceptor。Filter只在Servlet前後起作用。而攔截器能夠深入到方法前後、異常拋出前後等,因此攔截器的使用具有更大的彈性。所以在Spring構架的程序中,優先使用攔截器。
參考博客鏈接:
https://blog.csdn.net/aqsunkai/article/details/51813121
https://blog.csdn.net/baidu_37050701/article/details/56286513