JSP Filters(過濾器)


  Filter是攔截Request請求的對象:在用戶的請求訪 問資源前處理ServletRequest以及ServletResponse,它可 用於日志記錄、加解密、Session檢查、圖像文件保護 等。通過Filter可以攔截處理某個資源或者某些資源。 Filter的配置可以通過Annotation或者部署描述來完成。 當一個資源或者某些資源需要被多個Filter所使用到, 且它的觸發順序很重要時,只能通過部署描述來配置。

一.Filter API

1. Filter 相關接口,包含Filter, FilterConfig, FilterChain

  Filter的實現必須繼承javax.servlet.Filter接口。這個 接口包含了Filter的3個生命周期:init、doFilter、 destroy。

  Servlet容器初始化Filter時,會觸發Filter的init方 法,一般來說是在應用開始時。也就是說,init方法並 不是在該Filter相關的資源使用到時才初始化的,而且 這個方法只調用一次,用於初始化Filter。init方法的定 義如下:

oid init(FilterConfig filterConfig)

注意: FilterConfig實例是由Servlet容器傳入init方法中的.

  當Servlet容器每次處理Filter相關的資源時,都會調 用該Filter實例的doFilter方法。Filter的doFilter方法包含 ServletRequest、ServletResponse、FilterChain這3個參 數。

  doFilter的定義如下:

void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)

  接下來,說明一下doFilter的實現中訪問 ServletRequet、ServletResponse。這也就意味着允許給 ServletRequest增加屬性或者增加Header。當然也可以修 飾ServletRequest或者ServletRespone來改變它們的行 為。

  在Filter的doFilter的實現中,最后一行需要調用 FilterChain中的doFilter方法。注意Filter的doFilter方法 里的第3個參數,就是filterChain的實例:

filterChain.doFilter(request, response)

  一個資源可能需要被多個Filter關聯到(更專業一 點來說,這應該叫作Filter鏈條),這時Filter.doFilter() 的方法將觸發Filter鏈條中下一個Filter。只有在Filter鏈 條中最后一個Filter里調用的FilterChain.doFilter(),才會 觸發處理資源的方法。

  如果在Filter.doFilter()的實現中,沒有在結尾處調 用FilterChain.doFilter()的方法,那么該Request請求中 止,后面的處理就會中斷。

注意: FilterChain接口中,唯一的方法就是doFilter。該方法與Filter中的 doFilter的定義是不一致的:在FilterChaing中,doFilter方法只有兩個參 數,但在Filter中,doFilter方法有三個參數。

  Filter接口中,最后一個方法是destroy,它的定義 如下:

Void destroy()

  該方法在Servlet容器要銷毀Filter時觸發,一般在應 用停止的時候進行調用。

  除非Filter在部署描述中被多次定義到,否則Servlet 窗口只會為每個Filter創建單一實例。由於Serlvet/JSP的 應用通常要處理用戶並發請求,此時Filter實例需要同 時被多個線程所關聯到,因此需要非常小心地處理多線 程問題。

三. Filter配置

  當完成Filter的實現后,就可以開始配置Filter了。 Filter的配置需要如下步驟:

  • 確認哪些資源需要使用這個Filter攔截處理。
  • 配置Filter的初始化參數值,這些參數可以在Filter的 init方法中讀取到;
  • 給Filter取一個名稱。一般來說,這個名稱沒有什么 特別的含義,但在一些特殊的情況下,這個名字十 分有用。例如,要記錄Filter的初始化時間,但這個 應用中有許多的Filter,這時它就可以用來識別Filter 了。

  FilterConfig接口允許通過它的getServletContext的 方法來訪問ServletContext:

ServletContext getServletContext(

  如果配置了Filter的名字,在FilterConfig的 getFilterName中就可以獲取Filter的名字。getFilterName 的定義如下:

java.lang.String getFilterName()

  當然,最重要的還是要獲取到開發者或者運維給 Filter配置的初始化參數。為了獲取這些初始化參數, 需要用到FilterConfig中的兩個方法,第一個方法是 getParameterNames:

java.util.Enumeration<java.lang.String> getInitParameterNames()

  這個方法返回Filter參數名字的Enumeration對象。 如果沒有給這個Filter配置任何參數,該方法返回的是 空的Enumeration對象。

  第二個方法是getParameter:

java.lang.String getInitParameter(java.lang.String parameterName)

  有兩種方法可以配置Filter:一種是通過WebFilter 的Annotation來配置Filter,另一種是通過部署描述來注 冊。使用@WebFilter的方法,只需要在Filter的實現類 中增加一個注解即可,不需要重復地配置部署描述。當 然,此時要修改配置參數,就需要重新構建Filter實現 類了。換句話說,使用部署描述意味着修改Filter配置 只要修改一下文本文件就可以了。

  使用@WebFilter,你需要熟悉下表中所列出來的 參數,這些參數是在WebFilter的Annotation里定義的。 所有參數都是可選的。

WebFilter的屬性
屬性 描述
asyncSupported Filter是否支持異步操作
description Filter的描述
dispatcerTypes Filter所生效范圍
displayName Filter的顯示名
filterName Filter的名稱
initParams Filter的初始化參數
largeIcon Filter的大圖名稱
servletName Filter所生效的Servlet名稱
smallIcon Filter的小圖名稱
urlPatterns Filter所生效的URL路徑
value Filter所生效的URL路徑

 

三. 示例1: 日志 Filter

  作為第1個例子,將做一個簡單的Filter:在app09a 的應用中把Request請求的URL記錄到日志文本文件 中。日志文本文件名通過Filter的初始化參數來配置。 此外,日志的每條記錄都會有一個前綴,該前綴也由 Filter初始化參數來定義。通過日志文件,可以獲得許 多有用的信息,例如在應用中哪些資源訪問最頻繁; Web站點在一天中的哪個時間段訪問量最多。

   這個Filter的類名叫LoggingFilter。 一般情況下,Filter的類名都以*Filter結尾。

 

package filter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
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.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;

@WebFilter(filterName = "LoggingFilter", urlPatterns = { "/ *" }, initParams = {
        @WebInitParam(name = "logFileName", value = "log.txt"), @WebInitParam(name = "prefix", value = "URI: ") })
public class LoggingFilter implements Filter {
    private PrintWriter logger;
    private String prefix;
    
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        prefix = filterConfig.getInitParameter("prefix"); //得到URI
        String logFileName = filterConfig.getInitParameter("logFileName");
        String appPath = filterConfig.getServletContext().getRealPath("/"); //得到項目路徑
// without path info in logFileName, the log file will be
// created in $TOMCAT_HOME/bin
        System.out.println("logFileName:" + logFileName);
        try {
            logger = new PrintWriter(new File(appPath, logFileName)); //打開文件
            logger.println("I have the output");
            logger.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }
    }

    @Override
    public void destroy() {
        System.out.println("destroying filter");
        if (logger != null) {
            logger.close();
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("LoggingFilter.doFilter");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        logger.println(new Date() + " " + prefix + httpServletRequest.getRequestURI()); //寫入數據
        logger.flush(); //刷新緩沖
        filterChain.doFilter(request, response); //如果沒有doFilter方法,后面發Filter處理就會中斷
    }
    
}

  下面來仔細分析一下Filter類。 首先,該Filter的類實現了Filter的接口並聲明兩個 變量:PrintWriter類型的logger和String類型的prefix。

  其中PrintWriter用於記錄日志到文本文件,prefix的 字符串用於每條日志的前綴。 Filter的類使用了@WebFilter的Annotation,將兩個 參數(logFilteName、prefix)傳入到該Filter中.

  在Filter的init方法中,通過FilterConfig里傳入的 getInitParameter方法來獲取prefix和getFileName的初始 化參數。其中把prefix參數中賦給了類變量prefix, logFileName則用於創建一個PrintWriter

  如果Servlet/JSP應用是通過Servlet/JSP容器啟動 的,那么當前應用的工作目錄是當前JDK所在的目錄。 如果是在Tomcat中,該目錄是Tomcat的安裝目錄。在 應用中創建日志文件,可以通過 ServletContext.getRealPath來獲取工作目錄,結合應用 工作目錄以及初始化參數中的logFilterNmae,就可以得 到日志文件的絕對路徑.

  當Filter的init方法被執行時,日志文件就會創建出 來。如果在應用的工作目錄中該文件已經存在,那么該 日志文件的內容將會被覆蓋。 當應用關閉時,PrintWriter需要被關閉。因此在 Filter的destroy方法中,需要:關閉日志

  Filter的doFilter實現中記錄着所有從ServletRequest 到HttpServletRequest的Request,並調用了它的 getRequestURI方法,該方法的返回值將記錄通過 PrintWriter的pringln記錄下來

  每條記錄都有一個時間戳以及前綴,這樣可以很方 便地標識每條記錄。接下來 Filter的doFilter實現調用 PrintWriter的flush方法以及FilterChain.doFilter,以喚起 資源的調用:

   如果使用Tomcat,Filter的初始化並不會等到第一 個Request請求時才觸發進行。這點可以在控制台中打 印出來的logFileName參數值中可以看到。在app09a應 用中通過URL調用test.jsp頁面,就可以測試該Filter了

  通過檢查日志文件的內容,就可以驗證這個Filter 是否運行正常。

四.  示例2:圖像文件保護Filter

  本例中的圖像文件保護Filter用於在瀏覽器中輸入 圖像文件的URL路徑時,防止下載圖像文件。應用中的 圖像文件只有當圖像鏈接在頁面中被點擊的時候才會顯 示。該Filter的實現原理是檢查HTTP Header的referer 值。如果該值為null,就意味着當前的請求中沒有 referer值,即當前的請求是直接通過輸入URL來訪問該 資源的。如果資源的Header值為非空,將返回Request 語法的原始頁面作為referer值。注意Header的referer的 屬性名中,在第2個e以及第3個e中僅有一個r。

   ImageProtectorFilter的Filter實現類,如清單所 示。從WebFilter的Annotation中,可以看到該Filter應用 於所有的.png、.jpg、.gif文件后綴。

ImageProtectorFilter實現類

package filter;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;


//過濾所有以.png , .jpg  .gif 為后綴的訪問
@WebFilter(filterName = "ImageProtetorFilter", urlPatterns = { "*.png", "*.jpg", "*.gif" })
public class ImageProtectorFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        //直接輸入圖片地址訪問http://localhost:8080/app09/圖片.jpg會被攔截
        System.out.println("ImageProtectorFilter");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String referrer = httpServletRequest.getHeader("referer");
        System.out.println("referrer:" + referrer);
        if (referrer != null) {
         filterChain.doFilter(request, response); }
else { throw new ServletException("Image not available"); } } }

 

   這里並沒有init和destroy方法。其中doFilter方法讀 取到Header中的referer值,要確認是要繼續處理這個資 源還是給個異常.

  測試該Filter,可以在瀏覽器中輸入如下ULR路 徑,嘗試訪問logo.png圖像:

http://localhost:8080/app09/圖片.jpg

  接下來,通過image.jsp的頁面來訪問該圖像:

http://localhost:8080/app09/test.jsp

 

 

五.示例3:下載計數Filter

  本例子中,下載計數Filter將會示范如何在Filter中 計算資源下載的次數。這個示例特別有用,它將會得到 文檔、音頻文件的受歡迎程度。作為簡單的示例,這里 將數值保存在屬性文件中,而不保存在數據庫中。其中 資源的ULR路徑將作為屬性名保存在屬性文件中。

  因為我們把值保存在屬性文件中,並且Filter可以 被多線程訪問,因此涉及線程安全問題。用戶訪問一個 資源時,Filter需要讀取相應的屬性值加1,然后保存該 值。如果第二個用戶在第一個線程完成前同時訪問該資 源,將會發生什么呢?計算值出錯。在本例中,讀寫的 同步鎖並不是一個好的解決這個問題的方法,因為它會 導致擴展性問題。

   本示例中,解決這個線程安全問題是通過Queue以 及Executor。

  簡而言之,進來的Request請求將會保存在單線程 Executor的隊列中。替換這個任務十分方便,因為這是 一個異步的方法,因此你不需要等待該任務結束。 Executor一次從隊列中獲取一個對象,然后做相應屬性 值的增加。由於Executor只在一個線程中使用,因此可 以消除多個線程同時訪問一個屬性文件的影響。

DownloadCounterFilter實現類

package filter;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

// 攔截所有URL
//書上的是 urlPatterns={"/"},親測無效
@WebFilter(filterName = "DownloadCounterFilter", urlPatterns = { "*.JPG" })
public class DownloadCounterFilter implements Filter {
    // 獲取單個線程池的Executor 由於Executor只在一個線程中使用,因此可 以消除多個線程同時訪問一個屬性文件的影響。
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    // propeties 屬性集
    Properties downloadLog;
    File logFile;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("DownloadCounterFilter");
        //獲取路徑
        String appPath = filterConfig.getServletContext().getRealPath("/");
        // 獲取downLoadLog.txt文件
        logFile = new File(appPath, "downloadLog.txt");
        if (!logFile.exists()) {
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        downloadLog = new Properties();
        try {
            downloadLog.load(new FileReader(logFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {
        executorService.shutdown();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        final String uri = httpServletRequest.getRequestURI();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                String property = downloadLog.getProperty(uri);
                if (property == null) {
                    downloadLog.setProperty(uri, "1");
                } else {
                    int count = 0;
                    try {
                        count = Integer.parseInt(property);
                    } catch (NumberFormatException e) {
// silent
                    }
                    count++;
                    downloadLog.setProperty(uri, Integer.toString(count));
                }
                try {
                    downloadLog.store(new FileWriter(logFile), "");
                } catch (IOException e) {
                }
            }
        });
        filterChain.doFilter(request, response);
    }
}

  如果在當前應用的工作目錄中不存在 downloadLog.txt文件,這個Filter的init方法就會創建 它

  接着創建Properties對象,並讀取該文件.

  注意,Filter的實現類中引用到了 ExecutorService(Executor的子類).

  且當Filter銷毀時,會調用ExecutorService的 shutdown方法.

  Filter的doFilter實現中大量地使用到這個Job。每次 URL請求都會調用到ExecutorService的execute方法,然 后才調用FilterChaing.doFilter()。該任務的execute實現 非常好理解:它將URL作為一個屬性名,從Properties 實例中獲取該屬性的值,然后加1,並調用flush方法寫 回到指定的日志文件中

  這個Filter可在許多資源上生效,但也可以非常簡 單地配置,限定為PDF或者AVI文件資源。

properties文件

 

六. Filter順序

  如果多個Filter應用於同一個資源,Filter的觸發順 序將變得非常重要,這時就需要使用部署描述來管理 Filter:指定哪個Filter先被觸發。例如:Filter 1需要在 Filter 2前被觸發,那么在部署描述中,Filter 1需要配置 在Filter 2之前:

<filter>
<filter-name>Filter1</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>

通過部署描述之外的配置來指定Filter觸發的順序 是不可能的

 


免責聲明!

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



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