2005年3月17日 作者:l_walker Matrix-與Java共舞
你可以在兩種情況下使用本文:學習過濾器的功用,或者作為你寫過濾器時的輔助。我將從幾個簡單的例子開始然后繼續更多高級的過濾器。最后,我將向你介紹我為了支持多路請求而寫的一個文件上傳過濾器。
Servlet 過濾器
也許你還不熟悉情況,一個過濾器是一個可以傳送請求或修改響應的對象。過濾器并不是servlet,他們并不實際創建一個請求。他們是請求到達一個servlet前的預處理程序,和/或響應離開servlet后的后處理程序。就像你將在后面的例子中看到的,一個過濾器能夠:
·在一個servlet被調用前截獲該調用
·在一個servlet被調用前檢查請求
·修改在實際請求中提供了可定制請求對象的請求頭和請求數據
·修改在實際響應中提供了可定制響應對象的響應頭和響應數據
·在一個servlet被調用之后截獲該調用
你可以一個過濾器以作用于一個或一組servlet,零個或多個過濾器能過濾一個或多個servlet。一個過濾器實現java.servlet.Filter接口并定義它的三個方法:
1. void init(FilterConfig config) throws ServletException:在過濾器執行service前被調用,以設置過濾器的配置對象。
2. void destroy();在過濾器執行service后被調用。
3. Void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) throws IOException,ServletException;執行實際的過濾工作。
服務器調用一次init(FilterConfig)以為服務準備過濾器,然后在請求需要使用過濾器的任何時候調用doFilter()。FilterConfig接口檢索過濾器名、初始化參數以及活動的servlet上下文。服務器調用destory()以指出過濾器已結束服務。過濾器的生命周期和servelt的生命周期非常相似 ——在Servlet API 2.3 最終發布稿2號 中最近改變的。先前得用setFilterConfig(FilterConfig)方法來設置生命周期。
在doFilter()方法中,每個過濾器都接受當前的請求和響應,而FilterChain包含的過濾器則仍然必須被處理。doFilter()方法中,過濾器可以對請求和響應做它想做的一切。(就如我將在后面討論的那樣,通過調用他們的方法收集數據,或者給對象添加新的行為。)過濾器調用
chain.doFilter()將控制權傳送給下一個過濾器。當這個調用返回后,過濾器可以在它的doFilter()方法的最后對響應做些其他的工作;例如,它能記錄響應的信息。如果過濾器想要終止請求的處理或或得對響應的完全控制,則他可以不調用下一個過濾器。
循序漸進
如果想要真正理解過濾器,則應該看它們在實際中的應用。我們將看到的第一個過濾器是簡單而有用的,它記錄了所有請求的持續時間。在Tomcat 4.0發布中被命名為ExampleFilter。代碼如下:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TimerFilter implements Filter {
private FilterConfig config = null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
public void destroy() {
config = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long before = System.currentTimeMillis();
chain.doFilter(request, response);
long after = System.currentTimeMillis();
String name = "";
if (request instanceof HttpServletRequest) {
name = ((HttpServletRequest)request).getRequestURI();
}
config.getServletContext().log(name + ": " + (after - before) + "ms");
}
} |
當服務器調用init()時,過濾器用config變量來保存配置類的引用,這將在后面的doFilter()方法中被使用以更改ServletContext。當調用doFilter()時,過濾器計算請求發生到該請求執行完畢之間的時間。該過濾器很好的演示了請求之前和之后的處理。注意doFilter()方法的參數并不是HTTP對象,因此要調用HTTP專用的getRequestURI()方法時必須將request轉化為HttpServletRequest類型。
使用此過濾器,你還必須在web.xml文件中用<filter>標簽部署它,見下:
<filter>
<filter-name>timerFilter</filter-name>
<filter-class>TimerFilter</filter-class>
</filter> |
這將通知服務器一個叫timerFiter的過濾器是從TimerFiter類實現的。你可以使用確定的URL模式或使用<filter-mapping>標簽命名的servelt 來注冊一個過濾器,如:
<filter-mapping>
<filter-name>timerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> |
這種配置使過濾器操作所有對服務器的請求(靜態或動態),正是我們需要的計時過濾器。如果你連接一個簡單的頁面,記錄輸出可能如下:
2001-05-25 00:14:11 /timer/index.html: 10ms
在Tomcat 4.0 beta 5中,你可以在server_root/logs/下找到該記錄文件。
此過濾器的WAR文件從此下載:
http://www.javaworld.com/jw-06-2001/Filters/timer.war 誰在你的網站上?他們在做什么?
我們下一個過濾器是由OpenSymphony成員寫的clickstream過濾器。這個過濾器跟蹤用戶請求(比如:點擊)和請求隊列(比如:點擊流)以向網絡管理員顯示誰在她的網站上以及每個用戶正在訪問那個頁面。這是個使用LGPL的開源庫。
在clickstream包中你將發現一個捕獲請求信息的ClickstreamFilter類,一個像操作結構一樣的Clickstream類以保存數據,以及一個保存會話和上下文事件的ClickstreamLogger類以將所有東西組合在一起。還有個BotChecker類用來確定客戶端是否是一個機器人(簡單的邏輯,像“他們是否是從robots.txt來的請求?”)。該包中提供了一個clickstreams.jsp摘要頁面和一個viewstream.jsp詳細頁面來查看數據。
我們先看ClickstreamFilter類。所有的這些例子都做了些輕微的修改以格式化并修改了些可移植性問題,這我將在后面將到。
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
public class ClickstreamFilter implements Filter {
protected FilterConfig filterConfig;
private final static String FILTER_APPLIED = "_clickstream_filter_applied";
public void init(FilterConfig config) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 確保該過濾器在每次請求中只被使用一次
if (request.getAttribute(FILTER_APPLIED) == null) {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
HttpSession session = ((HttpServletRequest)request).getSession();
Clickstream stream = (Clickstream)session.getAttribute("clickstream");
stream.addRequest(((HttpServletRequest)request));
}
// 傳遞請求
chain.doFilter(request, response);
}
public void destroy() { }
} |
doFilter()方法取得用戶的session,從中獲取Clickstream,并將當前請求數據加到Clickstream中。其中使用了一個特殊的FILTER_APPLIED標記屬性來標注此過濾器是否已經被當前請求使用(可能會在請求調度中發生)并且忽略所有其他的過濾器行為。你可能疑惑過濾器是怎么知道當前session中有clickstream屬性。那是因為ClickstreamLogger在會話一開始時就已經設置了它。ClickstreamLogger代碼:
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ClickstreamLogger implements ServletContextListener,
HttpSessionListener {
Map clickstreams = new HashMap();
public ClickstreamLogger() { }
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().setAttribute("clickstreams", clickstreams);
}
public void contextDestroyed(ServletContextEvent sce) {
sce.getServletContext().setAttribute("clickstreams", null);
}
public void sessionCreated(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
Clickstream clickstream = new Clickstream();
session.setAttribute("clickstream", clickstream);
clickstreams.put(session.getId(), clickstream);
}
public void sessionDestroyed(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
Clickstream stream = (Clickstream)session.getAttribute("clickstream");
clickstreams.remove(session.getId());
}
}
|
logger(記錄器)獲取應用事件并將使用他們將所有東西幫定在一起。當context創建中,logger在context中放置了一個共享的流map。這使得clickstream.jsp頁面知道當前活動的是哪個流。而在context銷毀中,logger則移除此map。當一個新訪問者創建一個新的會話時,logger將一個新的Clickstream實例放入此會話中并將此Clickstream加入到中心流map中。在會話銷毀時,由logger從中心map中移除這個流。
下面的web.xml部署描述片段將所有東西寫在一塊:
<filter>
<filter-name>clickstreamFilter</filter-name>
<filter-class>ClickstreamFilter</filter-class>
</filter> |
<filter-mapping>
<filter-name>clickstreamFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>clickstreamFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<listener>
<listener-class>ClickstreamLogger</listener-class>
</listener> |
這注冊了ClickstreamFilter并設置其處理*.jsp和*.html來的請求。這也將ClickstreamLogger注冊為一個監聽器以在應用事件發生時接受他們。
兩個JSP頁面從會話中取clickstream數據和context對象并使用HTML界面來顯示當前狀態。下面的clickstream.jsp文件顯示了個大概:
<%@ page import="java.util.*" %>
<%@ page import="Clickstream" %>
<%
Map clickstreams = (Map)application.getAttribute("clickstreams");
String showbots = "false";
if (request.getParameter("showbots") != null) {
if (request.getParameter("showbots").equals("true"))
showbots = "true";
else if (request.getParameter("showbots").equals("both"))
showbots = "both";
}
%>
<font face="Verdana" size="-1">
<h1>All Clickstreams</h1>
<a href="clickstreams.jsp?showbots=false">No Bots</a> |
<a href="clickstreams.jsp?showbots=true">All Bots</a> |
<a href="clickstreams.jsp?showbots=both">Both</a> <p>
<% if (clickstreams.keySet().size() == 0) { %>
No clickstreams in progress
<% } %>
<%
Iterator it = clickstreams.keySet().iterator();
int count = 0;
while (it.hasNext()) {
String key = (String)it.next();
Clickstream stream = (Clickstream)clickstreams.get(key);
if (showbots.equals("false") && stream.isBot()) {
continue;
}
else if (showbots.equals("true") && !stream.isBot()) {
continue;
}
count++;
try {
%>
<%= count %>.
<a href="viewstream.jsp?sid=<%= key %>"><b>
<%= (stream.getHostname() != null && !stream.getHostname().equals("") ?
stream.getHostname() : "Stream") %>
</b></a> <font size="-1"><%= stream.getStream().size() %> reqs</font><br>
<%
}
catch (Exception e) {
%>
An error occurred - <%= e %><br>
<%
}
}
%> |
這個包很容易從OpenSymphony下載并安裝。將Java文件編譯并放在
WEB-INF/classes下,將JSP文件放到Web應用路徑下,按幫助修改web.xml文件。為防止在這些工作前的爭論,你可以從
http://www.javaworld.com/jw-06-2001/Filters/clickstream.war處找到打好包的WAR文件。
為能讓此過濾器能在Tomcat 4.0 beta 5下工作,我發現我不得不做一些輕微的改動。我做的改動顯示了一些在servlet和過濾器的可移植性中通常容易犯的錯誤,所以我將他們列在下面:
·我不得不將在JSP中添加一個額外的導入語句:<%@ page import=”Clickstream” %>。在Java中你并不需要導入在同一包下的類,而在服務器上JSP被編譯到默認包中,你并不需要這句導入行。但在像Tomcat這樣的服務器上,JSP被編譯到一個自定義的包中,你不得不明確地從默認包中導入類。
·我不得不將<listener>元素移動到web.xml文件中的<filter>和<filter-mapping>元素之后,就像部署描述DTD要求的那樣。并不是所有服務器對元素都要求固定的順序。但Tomcat必須要。
·我不得不將web.xml中的映射由/*.html和/*.jsp改成正確的*.html和*.jsp。一些服務器會忽略開頭的/,但Tomcat強硬的規定開頭不能有/。
·最后,我得將ClickstreamFilter類升級到最新的生命周期API,將setFilterConfig()改成新的init()和destory()方法。
可下載的WAR文件已經包含了這些修改并能通過服務器在包外運行,雖然我并沒有廣泛的進行測試。
壓縮響應
第三個過濾器是自動壓縮響應輸出流,以提高帶寬利用率并提供一個很好的包裝響應對象的示例。這個過濾器是由來自SUN的Amy Roh編寫的,他為Tomcat 4.0 的“examples”Web程序做出過貢獻。你將從webapps/examples/WEB-INF/classes/compressionFilters下找到原始代碼。這里的例子代碼以及WAR下的都已經為了更清晰和更簡單而編輯過了。
CompressionFilter類的策略是檢查請求頭以判定客戶端是否支持壓縮,如果支持,則將響應對象用自定義的響應來打包,它的getOutputStream()和getWriter()方法已經被定義為可以利用壓縮過的輸出流。使用過濾器允許如此簡單而有效的解決問題。
我們將從init()開始看代碼:
public void init(FilterConfig filterConfig) {
config = filterConfig;
compressionThreshold = 0;
if (filterConfig != null) {
String str = filterConfig.getInitParameter("compressionThreshold");
if (str != null) {
compressionThreshold = Integer.parseInt(str);
}
else {
compressionThreshold = 0;
}
}
} |
注意在檢索請求頭前必須把request轉化為HttpServletRequest,就想在第一個例子里那樣。過濾器使用wrapper類CompressResponseWrapper,一個從
HttpServletResponseWrapper類繼承下來的自定義類。這個wrapper的代碼相對比較簡單:
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
protected ServletOutputStream stream = null;
protected PrintWriter writer = null;
protected int threshold = 0;
protected HttpServletResponse origResponse = null;
public CompressionResponseWrapper(HttpServletResponse response) {
super(response);
origResponse = response;
}
public void setCompressionThreshold(int threshold) {
this.threshold = threshold;
}
public ServletOutputStream createOutputStream() throws IOException {
return (new CompressionResponseStream(origResponse));
}
public ServletOutputStream getOutputStream() throws IOException {
if (writer != null) {
throw new IllegalStateException("getWriter() has already been " +
"called for this response");
}
if (stream == null) {
stream = createOutputStream();
}
((CompressionResponseStream) stream).setCommit(true);
((CompressionResponseStream) stream).setBuffer(threshold);
return stream;
}
public PrintWriter getWriter() throws IOException {
if (writer != null) {
return writer;
}
if (stream != null) {
throw new IllegalStateException("getOutputStream() has already " +
"been called for this response");
}
stream = createOutputStream();
((CompressionResponseStream) stream).setCommit(true);
((CompressionResponseStream) stream).setBuffer(threshold);
writer = new PrintWriter(stream);
return writer;
}
} |
所有調用getOutputStream() 或者getWriter()都返回一個使用
CompressResponseStream類的對象。CompressionResponseStrteam類沒有顯示在這個例子中,因為它繼承于ServletOutputStream并使用java.util.zip.GZIPOutputStream類來壓縮流。
Tomcat的”examples”Web程序中已經預先配置了這個壓縮過濾器并加載了一個示例servlet。示例servlet響應/CompressionTestURL(確定先前的路徑是/examples)。使用我制作的有用的WAR文件,你可以用/servlet/compressionTest(再次提醒,別忘了適當的前導路徑)訪問此測試servlet。你可以使用如下的web.xml片段來配置這個測試:
<filter>
<filter-name>compressionFilter</filter-name>
<filter-class>CompressionFilter</filter-class>
<init-param>
<param-name>compressionThreshold</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>compressionFilter</filter-name>
<servlet-name>compressionTest</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
compressionTest
</servlet-name>
<servlet-class>
CompressionTestServlet
</servlet-class>
</servlet> |
CompressionTestServlet(這里沒有顯示)輸出壓縮是否可用,如果可用,則輸出壓縮響應成功!
//~~~~~~~~~~CSDN這里到底有多少字數限制?
請各位斧正了:)
來自:csdn