理解Servlet過濾器 (javax.servlet.Filter)


過濾器(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();

這樣處理后就不會再出現只出現部分頁面的問題了!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM