一、Servlet Filter概述java
凡是開發過J2EE的web application的人員都知道,常常須要處理如下幾種狀況:web
訪問特定資源(Web 頁、JSP 頁、servlet)時的身份認證apache
應用程序級的訪問資源的審覈和記錄設計模式
應用程序範圍內對資源的加密訪問,它創建在定製的加密方案基礎上安全
對被訪問資源的及時轉換, 包括從 servlet 和 JSP 的動態輸出服務器
在servlet2.3以前這些功能處理是很難實現的,可是Java Servlet 2.3 規範新增了很多激動人心的功能,其中之一即是過濾器(Filter),其實這就是咱們所說的管道和過濾器體系架構在J2EE中的應用實踐. 經過使用該模式使得Web Application開發者可以在請求到達Web資源以前截取請求,在處理請求以後修改應答。其結構圖以下:架構
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1>併發 |
一個執行過濾器的Java 類必須實現javax.servlet.Filter 接口。這一接口含有三個方法:app
init(FilterConfig):這是容器所調用的初始化方法。它保證了在第一次 doFilter() 調用前由容器調用。它能獲取在 web.xml 文件中指定的filter初始化參數。模塊化
doFilter(ServletRequest, ServletResponse, FilterChain):這是一個完成過濾行爲的方法。它一樣是上一個過濾器調用的方法。引入的 FilterChain 對象提供了後續過濾器所要調用的信息。
destroy():容器在銷燬過濾器實例前,doFilter()中的全部活動都被該實例終止後,調用該方法。
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
二、Filter鏈介紹
全部過濾器都服從調用的過濾器鏈,並經過定義明確的接口獲得執行。WebApplication能夠指定許多過濾器來完成相關的工做.那麼它們就組成一個過濾器鏈來完成相應的工做.其結構以下圖:
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
三、例子
3.1 簡單filter
在PetStore1.3.1中的就存在兩個Filter過濾器.其中一個過濾器,完成字符集的編碼的轉化,如你們常常遇到的漢字編碼問題,你只需配置爲GBK便可.它從Web.xml之中讀取這些參數的配置信息,而後進行編碼的轉化.另外一個是安全校驗Fliter,它負責進行安全檢查哪些頁面能夠進行,哪些不可。它們組成一個Filter鏈,結構圖和實現代碼以下:
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
public class EncodingFilter implements Filter { private FilterConfig config = null; // default to ASCII private String targetEncoding = "ASCII"; public void init(FilterConfig config) throws ServletException { this.targetEncoding = config.getInitParameter("encoding"); } //在過濾器中實現字符集編碼轉化 public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)srequest; request.setCharacterEncoding(targetEncoding); // move on to the next chain.doFilter(srequest,sresponse); } public void destroy() { ................. } } public class SignOnFilter implements Filter { public void init(FilterConfig config) throws ServletException { this.config = config; URL protectedResourcesURL = null; try { protectedResourcesURL = config.getServletContext().getResource("/WEB-INF/signon-config.xml"); ............... } catch (java.net.MalformedURLException ex) { System.out.println("SignonFilter: malformed URL exception: " + ex); } } public void destroy() { config = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ........ } } |
容器經過 Web 應用程序中的配置描述符 web.xml 文件解析過濾器配置信息。有兩個新的標記與過濾器相關:<filter> 和 <filter-mapping>。<filter> 標記是一個過濾器定義,它一定有一個 <filter- name> 和 <filter-class> 子元素。<filter-name> 子元素給出了一個與過濾器實例相關的名字。<filter-class> 指定了由容器載入的實現類。您能隨意地包含一個 <init-param> 子元素爲過濾器實例提供初始化參數。<filter-mapping> 標記表明了一個過濾器的映射,指定了過濾器會對其產生做用的 URL 的子集。
<!-- Encoding Filter Declaration Start --> <filter> <filter-name>EncodingFilter</filter-name> <display-name>Encoding Filter</display-name> <description>no description</description> <filter-class>com.sun.j2ee.blueprints.encodingfilter.web.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <!-- Encoding Filter Declaration End --> <!-- Signon Filter Declaration Start --> <filter> <filter-name>SignOnFilter</filter-name> <display-name>SignOn Filter</display-name> <description>no description</description> <filter-class>com.sun.j2ee.blueprints.signon.web.SignOnFilter</filter-class> </filter> <!-- Signon Filter Declaration End --> <!-- Encoding Filter Mapping Start--> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Encoding Filter Mapping End --> <!-- Signon Filter Mapping Start--> <filter-mapping> <filter-name>SignOnFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Signon Filter Mapping End --> |
3.2 複雜的filter
上面是petstore的例子,演示了經過Fliter修改字符編碼和安全認證的功能。下面提供一個示例演示經過修改返回數據(經過過濾器把response的字符串變成大寫)。
public class UCaseResponse extends HttpServletResponseWrapper { public UCaseResponse(HttpServletResponse response) { super(response); } public PrintWriter getWriter() throws IOException { return new UCaseWriter(super.getWriter()); } } public class UCaseWriter extends PrintWriter { public UCaseWriter(Writer out) { super(out); } public void write(int c) { super.write(Character.toUpperCase( (char) c)); } public void write(char buf[], int off, int len) { for (int i = 0;i < len;i++) { write(buf[off + i]); } } public void write(String s, int off, int len) { for (int i = 0;i < len;i++) { write(s.charAt(off + i)); } } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) { try { filterChain.doFilter(request, new UCaseResponse((HttpServletResponse)(response))); }catch(Exception sx) { filterConfig.getServletContext().log(sx.getMessage()); } |
該示例使用HttpServletResponseWrapper技術,它是對HttpServletResponse的包裝,其實就是裝飾(decorate)設計模式的應用.這個例子可以工做的關鍵是UCaseResponse和UCaseWriter類,它實現了對每一個要輸出的字符都轉成了大寫後再寫入實際的輸出流的功能。
四、體系架構的實現
實現一個管道和過濾器通常要注意如下幾個方面:
把系統任務分紅一系列處理階段。
根據管道和過濾器的設計方案,必須把系統處理的任務分割成相應獨立的任務,如日誌,數據轉化,安全認證等。這樣每一個階段僅依賴其前一階段的輸出。經過數據流將全部階段相連起來。而且你能夠進行替換每一個步驟,或者能夠調整它們之間的順序,以產生新的結果.如petstore中的編碼轉化Filter和安全Filter,分紅兩個獨立的處理階段。
定義沿每一個管道傳輸的數據格式。
咱們知道每一個過濾器,定義一個統一格式以得到最大的靈活性,由於它使過濾器的重組變得容易。如:每一個過濾器的方法是doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)它們的參數是必須相同的。
決定如何實現每一個管道鏈接
Filter過濾器的鏈接是推得方式來實現的.前一個過濾器主動的調用filterChain.doFilter(request, response);來實現轉向下一個過濾器。
設計和實現過濾器
設計每一個Filter具備獨立的功能,如編碼轉化,安全校驗,等功能.而且每一個Fliter都應該在實現javax.servlet.Filter接口。
創建處理流水線
過濾器的部署是在Web.xml中進行配置,描述過濾器的實現類以及它們的map關係,來肯定它們的順序。
其餘應用實例
一、JBOSS
若是你們對EJB瞭解,應該知道客戶調用EJB實際上並非直接引用EJB實例(ejb instance).它經過容器來截獲客戶端的請求,而後按照EJB描述符作些許多相應的工做如,安全校驗,事務的處理,線程併發處理等.這樣就可使開發人員僅關心本身的業務邏輯,而不需對複雜的基礎服務進行實現.使開發人員從繁瑣的工做中解脫出來.集中精力處理本身的業務邏輯,它的結構圖以下:
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
筆者有幸閱讀了JBOSS的源碼,分析了Jboss的EJB容器的實現. EJB的容器經過許多攔截器(Inteceptor)來實現,每一個攔截器處理必定的功能,一個處理結束後轉發給下一個攔截器,最後一個攔截器才把真正調用EJB的實例.其中它的EntityBean 容器的攔截器的結構以下:
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
咱們看其中的log攔截器
public class LogInterceptor extends AbstractInterceptor{ public Object invoke(Invocation invocation) throws Exception { boolean trace = log.isTraceEnabled(); // Log call details if (callLogging) { ......進行log的處理 } //處理結束,把請求轉發給下一個攔截器 return getNext().invoke(invocation); } |
這些攔截器配置在standardjboss.xml文件中,以下:
<container-interceptors> <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor> <interceptor metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor> <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor</interceptor> </container-interceptors> |
這就是Jboss容器架構最巧妙的地方,最初這個架構就是由天才少年Rickard Oberg提出的.其實這個架構就應用咱們討論的管道和過濾器模式,其實每一個攔截器就是一個過濾器,使用該模式給Jboss帶來了以下的好處:
使系統的架構更容易理解,由於每一個過濾器完成單一的功能。
使系統更加模塊化,有利於系統的模塊重用和擴展,若是系統想增長某種功能只需增長一個實現Interceptor接口的攔截器,而後配置在standardjboss.xml文件中便可。
使系統的容易進行錯誤處理,若是在一個攔截器中發現錯誤(error)或者異常(exception),只需返回便可.
二、AXIS
無獨有偶,一樣在Axis上也應用了管道和過濾器模式.Aixs是apache開源的webservice實現服務器。簡單的說,axis就是處理Message,它首先截獲客戶端的請求,而後轉發到真正的實現業務邏輯上處理客戶端的請求,在這以前通過一系列的handler處理.它的結構很像EJB容器.其實就是管道和過濾器模式的應用,Handler就是過濾器.它的處理順序主要考慮兩個方面一個是部署描述符(deployment configuration )另外一個就是是客戶端仍是服務器端。Handler處理的對象是MessageContext它的由3個重要的部分組成,一是一個request Message,一個是response message,還有許多屬性。
咱們經研究源碼分析,在服務器端,有一個Transport Listener 它監聽客戶端的請求, 能夠經過多種協議,一旦有客戶請求,它將按照協議的規範把數據解析生成生成一個Message對象,而後把它設置到MessageContext,而後調用一系列的Handler進行處理。
其結構圖以下:
500){this.resized=true;this.style.width=500;}" align=center vspace=1 border=1> |
相關設計模式
在使用管道和過濾器模式時,通常會使用如下的GOF設計模式。
一、職責鏈模式(Chain Of Responsibility)
職責鏈設計模式的意圖就是使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。其實管道和過濾器模式就是職責鏈模式的抽象,把它應用到軟件體系架構中。
二、 命令模式(Command)
命令模式的意圖將一個請求封裝爲一個對象,從而使你可用不一樣的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤消的操做。在管道和過濾器模式中每一個過濾器通常使用命令模式,把請求封裝成一個命令進行處理。
三、裝飾模式(Decorator)
裝飾模式的意圖就是動態地給一個對象添加一些額外的職責。就增長功能來講,Decorator模式相比生成子類更爲靈活。
在管道和過濾器模式中,在每一個過濾器中常常須要對請求進行動態的增長功能,或者修改請求的內容,這時通常會使用裝飾模式.如Servlet filter的javax.servlet.http.HttpServletRequestWrapper, javax.servlet.http.HttpServletResponseWrapper就是裝飾模式的應用.
總結
本文討論了管道和過濾器模式的解決方案,以及它的優缺點.而後以J2EE規範的Servlet Filter爲例詳細介紹了怎樣應用該模式,同時簡單的介紹了在Jboss,Axis中的應用。