架構---Spring-Mvc中的監聽器-攔截器-過濾器
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