過濾器(Filter)的概念
- 過濾器位于用戶端和web應用程式之間,用于檢查和修改兩者之間流過的請求和響應。
- 在請求到達Servlet/JSP之前,過濾器截獲請求。
- 在響應送給用戶端之前,過濾器截獲響應。
- 多個過濾器形成一個過濾器鍊,過濾器鍊中不同過濾器的先後順序由部署檔案web.xml中過濾器映射<filter-mapping>的順序決定。
- 最先截獲用戶端請求的過濾器将最後截獲Servlet/JSP的響應資訊。
過濾器的鍊式結構
可以為一個Web應用元件部署多個過濾器,這些過濾器組成一個過濾器鍊,每個過濾器隻執行某個特定的操作或者檢查。這樣請求在到達被通路的目标之前,需要經過這個過濾器鍊。
實作過濾器
在Web應用中使用過濾器需要實作javax.servlet.Filter接口,實作Filter接口中所定義的方法,并在web.xml中部署過濾器。
public class MyFilter implements Filter {
public void init(FilterConfig fc) {
//過濾器初始化代碼
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
//在這裡可以對用戶端請求進行檢查
//沿過濾器鍊将請求傳遞到下一個過濾器。
chain.doFilter(request, response);
//在這裡可以對響應進行處理
}
public void destroy( ) {
//過濾器被銷毀時執行的代碼
}
}
Filter接口
public void init(FilterConfig config)
web容器調用本方法,說明過濾器正被加載到web容器中去。容器隻有在執行個體化過濾器時才會調用該方法一次。容器為這個方法傳遞一個FilterConfig對象,其中包含與Filter相關的配
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
每當請求和響應經過過濾器鍊時,容器都要調用一次該方法。需要注意的是過濾器的一個執行個體可以同時服務于多個請求,特别需要注意線程同步問題,盡量不用或少用執行個體變量。 在過濾器的doFilter()方法實作中,任何出現在FilterChain的doFilter方法之前地方,request是可用的;在doFilter()方法之後response是可用的。
public void destroy()
容器調用destroy()方法指出将從服務中删除該過濾器。如果過濾器使用了其他資源,需要在這個方法中釋放這些資源。
部署過濾器
在Web應用的WEB-INF目錄下,找到web.xml檔案,在其中添加如下代碼來聲明Filter。
<filter>
<filter-name>TlwModifyResponseFilter</filter-name>
<filter-class>
com.Common.action.TlwModifyResponseFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>TlwModifyResponseFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
以上是我的項目工程中的action路徑
在2.4版本的servlet規範在部屬描述符中新增加了一個<dispatcher>元素,這個元素有四個可能的值:即 REQUEST,FORWARD,INCLUDE和ERROR, 可以在一個<filter-mapping>元素中加入任意數目的<dispatcher>,使得filter将會作用于直接從用戶端過來的request,通過forward過來的request,通過include過來的request和通過<error-page>過來的request。如果沒有指定任何<dispatcher>元素,預設值是REQUEST。 可以通過下面幾個例子來輔助了解。 例1:
1 <filter-mapping>
2 <filter-name>Logging Filter</filter-name>
3 <url-pattern>/products/*</url-pattern>
4 </filter-mapping>
這種情況下,過濾器将會作用于直接從用戶端發過來的以/products/…開始的請求。因為這裡沒有制定任何的<dispatcher>元素,預設值是REQUEST。
例2:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<servlet-name>ProductServlet</servlet-name>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
這種情況下,如果請求是通過request dispatcher的include方法傳遞過來的對ProductServlet的請求,則要經過這個過濾器的過濾。其它的諸如從用戶端直接過來的對ProductServlet的請求等都不需要經過這個過濾器。
指定filter的比對方式有兩種方法:直接指定url-pattern和指定servlet,後者相當于把指定的servlet對應的url-pattern作為filter的比對模式,filter的路徑比對和servlet是一樣的,都遵循servlet規範中《SRV.11.2 Specification of Mappings》一節的說明 。
例3:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<url-pattern>/products/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
在這種情況下,如果請求是以/products/…開頭的,并且是通過request dispatcher的forward方法傳遞過來或者直接從用戶端傳遞過來的,則必須經過這個過濾器。
1.請求過濾器
web.xml中配置如下
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>cn.telling.Filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
public class MyFilter implements Filter{
FilterConfig config;
/**
*
* @Description: TODO
* @param filterConfig
* @throws ServletException
* @author xingle
* @data 2015-10-26 下午4:32:44
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("begin do the log filter!");
this.config = filterConfig;
}
/**
*
* @Description: TODO
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
* @author xingle
* @data 2015-10-26 下午4:32:44
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletContext context = this.config.getServletContext();
System.out.println("before the log filter!");
HttpServletRequest hreq = (HttpServletRequest) request;
System.out.println("Log Filter已經截獲到使用者的請求的位址:"+hreq.getServletPath() );
// Filter 隻是鍊式處理,請求依然轉發到目的位址。
chain.doFilter(request, response);
}
/**
*
* @Description: TODO
* @author xingle
* @data 2015-10-26 下午4:32:44
*/
@Override
public void destroy() {
this.config = null;
}
}
2.響應過濾器
比如要實作輸出壓縮:
這樣不行!輸出會從servlet直接傳回給客戶。但是我們的目标是壓縮輸出。
先來想想這樣一個問題…… servlet 實際上是從響應對象得到輸出流或書寫器。那麼,如果不把實際的相應對象傳給servlet,而是由過濾器換入一個定制的相應對象,而且這個定制響應對象有你能控制的一個輸出流,這樣可以嗎?需要建立我們自己的HttpServletResponse 接口定制實作,并把它通過chain.doFilter() 調用傳遞到servlet。而且這個定制實作還必須包含一個定制輸出流,因為這正是我們的目标,在servlet寫輸出之後并且在輸出傳回給客戶之前,過濾器就能拿到這個輸出。
servlet中使用HttpServletResponseWrapper截獲傳回的頁面内容
要截獲頁面傳回的内容,整體的思路是先把原始傳回的頁面内容寫入到一個字元Writer,然後再組裝成字元串并進行分析,最後再傳回給用戶端。代碼如下:
package cn.telling.Filter;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* 自定義一個響應結果包裝器,将在這裡提供一個基于記憶體的輸出器來存儲所有
* 傳回給用戶端的原始HTML代碼。
* @ClassName: ResponseWrapper TODO
* @author xingle
* @date 2015-10-27 上午9:22:14
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter cachedWriter;
private CharArrayWriter bufferedWriter;
/**
* @param response
*/
public ResponseWrapper(HttpServletResponse response) {
super(response);
// 這個是我們儲存傳回結果的地方
bufferedWriter = new CharArrayWriter();
// 這個是包裝PrintWriter的,讓所有結果通過這個PrintWriter寫入到bufferedWriter中
cachedWriter = new PrintWriter(bufferedWriter);
}
public PrintWriter getWriter(){
return cachedWriter;
}
/**
* 擷取原始的HTML頁面内容。
* @return
*/
public String getResult() {
return bufferedWriter.toString();
}
}
然後再寫一個過濾器來截獲内容并處理:
package cn.telling.Filter;
import java.io.IOException;
import java.io.PrintWriter;
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.HttpServletResponse;
/**
*
* @ClassName: MyServletFilter TODO
* @author xingle
* @date 2015-10-27 上午9:24:34
*/
public class MyServletFilter implements Filter {
/**
*
* @Description: TODO
* @param filterConfig
* @throws ServletException
* @author xingle
* @data 2015-10-27 上午9:24:47
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
/**
*
* @Description: TODO
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
* @author xingle
* @data 2015-10-27 上午9:24:47
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 使用我們自定義的響應包裝器來包裝原始的ServletResponse
ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse) response);
// 這句話非常重要,注意看到第二個參數是我們的包裝器而不是response
chain.doFilter(request, wrapper);
// 處理截獲的結果并進行處理,比如替換所有的“名稱”為“鐵木箱子”
String result = wrapper.getResult();
result = result.replace("名稱", "替換後的");
// 輸出最終的結果
PrintWriter out = response.getWriter();
out.write(result);
out.flush();
out.close();
}
/**
*
* @Description: TODO
* @author xingle
* @data 2015-10-27 上午9:24:47
*/
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
然後将該servlet配置在web.xml檔案中,如下:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>cn.telling.Filter.MyServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
然後我們在web應用根目錄下建立一個jsp檔案echo.jsp,内容如下:
<%@page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<title>頁面傳回結果過濾測試</title></head>
</head>
<body>
你好,我叫“名稱”。
</body>
</html>
配置完後,部署到tomcat,然後通路應用下的echo.jsp檔案,就可以發現傳回的内容變成了:
進而也就達到了我們想要的效果了。在文章開頭我也提到了說有一個問題,那就是有可能在運作的過程中頁面隻輸出一部分,尤其是在使用多個架構後(比如sitemesh)出現的可能性非常大,在探究了好久之後終于發現原來是響應的ContentLength惹的禍。因為在經過多個過濾器或是架構處理後,很有可能在其他架構中設定了響應的輸出内容的長度,導緻浏覽器隻根據得到的長度頭來顯示部分内容。知道了原因,處理起來就比較友善了,我們在處理結果輸出前重置一下ContentLength即可,如下:
// 重置響應輸出的内容長度
response.setContentLength(-1);
// 輸出最終的結果
PrintWriter out = response.getWriter();
out.write(result);
out.flush();
out.close();
這樣處理後就不會再出現隻出現部分頁面的問題了!
轉載于:https://www.cnblogs.com/xingele0917/p/3673877.html