天天看點

了解Servlet過濾器 (javax.servlet.Filter)過濾器(Filter)的概念過濾器的鍊式結構實作過濾器Filter接口部署過濾器1.請求過濾器2.響應過濾器

過濾器(Filter)的概念

  • 過濾器位于用戶端和web應用程式之間,用于檢查和修改兩者之間流過的請求和響應。
  • 在請求到達Servlet/JSP之前,過濾器截獲請求。
  • 在響應送給用戶端之前,過濾器截獲響應。
  • 多個過濾器形成一個過濾器鍊,過濾器鍊中不同過濾器的先後順序由部署檔案web.xml中過濾器映射<filter-mapping>的順序決定。
  • 最先截獲用戶端請求的過濾器将最後截獲Servlet/JSP的響應資訊。

過濾器的鍊式結構

    可以為一個Web應用元件部署多個過濾器,這些過濾器組成一個過濾器鍊,每個過濾器隻執行某個特定的操作或者檢查。這樣請求在到達被通路的目标之前,需要經過這個過濾器鍊。

了解Servlet過濾器 (javax.servlet.Filter)過濾器(Filter)的概念過濾器的鍊式結構實作過濾器Filter接口部署過濾器1.請求過濾器2.響應過濾器

實作過濾器

在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過濾器 (javax.servlet.Filter)過濾器(Filter)的概念過濾器的鍊式結構實作過濾器Filter接口部署過濾器1.請求過濾器2.響應過濾器

這樣不行!輸出會從servlet直接傳回給客戶。但是我們的目标是壓縮輸出。

先來想想這樣一個問題…… servlet 實際上是從響應對象得到輸出流或書寫器。那麼,如果不把實際的相應對象傳給servlet,而是由過濾器換入一個定制的相應對象,而且這個定制響應對象有你能控制的一個輸出流,這樣可以嗎?需要建立我們自己的HttpServletResponse 接口定制實作,并把它通過chain.doFilter() 調用傳遞到servlet。而且這個定制實作還必須包含一個定制輸出流,因為這正是我們的目标,在servlet寫輸出之後并且在輸出傳回給客戶之前,過濾器就能拿到這個輸出。

了解Servlet過濾器 (javax.servlet.Filter)過濾器(Filter)的概念過濾器的鍊式結構實作過濾器Filter接口部署過濾器1.請求過濾器2.響應過濾器

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檔案,就可以發現傳回的内容變成了:

了解Servlet過濾器 (javax.servlet.Filter)過濾器(Filter)的概念過濾器的鍊式結構實作過濾器Filter接口部署過濾器1.請求過濾器2.響應過濾器

進而也就達到了我們想要的效果了。在文章開頭我也提到了說有一個問題,那就是有可能在運作的過程中頁面隻輸出一部分,尤其是在使用多個架構後(比如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