Filter對Response的改變:HttpServletResponseWrapper的工作原理

Filter對Response的改變:HttpServletResponseWrapper的工作原理
馬克- to-win:馬克 java社區(qū):防盜版實名手機(jī)尾號: 73203。
馬克-to-win:前面我們講的知識,主要說的是由于Filter的參與,用戶的訪問路徑被改變的問題。底下我們就要講一點更難的話題,就是Filter 如何改變一個現(xiàn)有的html。比如我寫的新浪博客,寫完以后,一上傳,內(nèi)容有時有些改變,誰動的手腳?肯定是新浪公司編了什么Filter過濾器,把我的 html的內(nèi)容給改變了。馬克-to-win:現(xiàn)在問題是:這是如何實現(xiàn)的呢?這里核心問題其實就是如何改變Response?本來我的html在原來的 Response里,準(zhǔn)備返回給客戶端。但現(xiàn)在在Filter當(dāng)中被改變了。但這又是怎么改變的呢?這里涉及到一個 HttpServletResponseWrapper的類實例myWrapper問題。Wrapper英文就是包裹者的意思。正常情況下,我們過去的認(rèn)識是:chain.doFilter(request, response);的意思就是訪問完后面的目標(biāo)資源以后,目標(biāo)資源把要返回給客戶端的內(nèi)容放在Response當(dāng)中。而現(xiàn)在這里的例子就不同了:通過 chain.doFilter(request, myWrapper);目標(biāo)資源就會把要返回給客戶端的內(nèi)容放在myWrapper當(dāng)中了。這時在Filter當(dāng)中,我們就可以從myWrapper當(dāng)中取出返回給客戶端的內(nèi)容,接著就可以大大方方的對其進(jìn)行改變了。要想做成這件事兒,當(dāng)然還得符合sun公司制定的有關(guān)HttpServletResponseWrapper的所有規(guī)章制度。首先通過MarkToWinWrapper myWrapper = new MarkToWinWrapper((HttpServletResponse) response);讓response和myWrapper聯(lián)系起來。馬克-to-win:之后,在我編的MarkToWinWrapper這個普通類當(dāng)中,需要初始化一個CharArrayWriter的實例:myContent = new CharArrayWriter();和PrintWriter的實例pw=new PrintWriter(myContent);之后通過編寫public PrintWriter getWriter() { return pw; }。當(dāng)你執(zhí)行chain.doFilter(request, myWrapper);時,系統(tǒng)其中一步會調(diào)用getWriter(),得到pw以后,就會把你myWrapper構(gòu)造函數(shù)里得到的response和CharArrayWriter的實例:myContent聯(lián)系起來。最后當(dāng)你執(zhí)行 public String getResultMarkToWin() { return myContent.toString(); }時,返回給客戶端的內(nèi)容就被你得到了,因為response和myContent已經(jīng)被你聯(lián)系起來了。注意要想正確應(yīng)用 HttpServletResponseWrapper,必須遵守它的規(guī)則。下面的例子把AAA.html的“淘寶”倆字兒都變成了“百度”。

例 1.2.7

AAA.html


既然這是首頁,像淘寶首頁一樣,這底下是首頁的一些泛泛信息。





package com;
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;
public class MarkToWinFilter implements Filter {
    public void destroy() {
    }
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        response.setCharacterEncoding("GBK");
        PrintWriter out = response.getWriter();
        MarkToWinWrapper myWrapper = new MarkToWinWrapper(
                (HttpServletResponse) response);
/*下句話之后目標(biāo)jsp的東西都裝在myWrapper中了,不像Hello World的filter,那時都裝在response的out中,本例到目前為止,out是空的*/       
        chain.doFilter(request, myWrapper);
        String result=myWrapper.getResultMarkToWin();
        System.out.println("content : " +result );
        result=result.replace("淘寶", "百度");
        out.println("content : " + result);
    }
    public void init(FilterConfig fConfig) throws ServletException {
    }
}






package com;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MarkToWinWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter myContent;
    private PrintWriter pw;
    public MarkToWinWrapper(HttpServletResponse res) {
        super(res);
        myContent = new CharArrayWriter();
        pw=new PrintWriter(myContent);
    }
    public PrintWriter getWriter() {
        return pw;
    }
    public String getResultMarkToWin() {
        return myContent.toString();
    }
}



當(dāng)在瀏覽器中運行AAA.html時,console中輸出如下:


content :

既然這是首頁,像淘寶首頁一樣,這底下是首頁的一些泛泛信息。




而在瀏覽器中,輸出的是:

content :

既然這是首頁,像百度首頁一樣,這底下是首頁的一些泛泛信息。



7_a)HttpServletResponseWrapper對Servlet的過濾:


馬克-to- win:上面這個MarkToWinWrapper也可以過濾Servlet:現(xiàn)在的問題是,改變Servlet的response有什么用處?比如:現(xiàn)在你的Servlet從數(shù)據(jù)庫中獲取回數(shù)據(jù),放在response中,之后需要返回給客戶瀏覽器,返回前做敏感字濾除。馬克-to-win:如果你要在一百個Servlet當(dāng)中做這件事兒,即使你可以編寫一個普通類,然后每個Servlet都調(diào)用這個普通類的方法來轉(zhuǎn)化,這其實就造成了耦合。一旦有一天哪個普通類的類名,方法名或參數(shù)什么的需要改變,(這是經(jīng)常需要的,因為需求也在不斷變化)你的一百個Servlet里頭都需要改變。如果這件事要在 Filter當(dāng)中做,就不是這種場景了。只需改動Filter代碼即可,Servlet無需知道這件事兒。




例 1.2.7_a:
如果我們的Servlet代碼是:

package com;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletHello1 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
          try {
            PrintWriter pWriter=response.getWriter();
            pWriter.println("淘寶淘寶淘寶淘寶");
        } catch (IOException e) {
            e.printStackTrace();
        }        
    }
}


我們在瀏覽器中運行:http://localhost:8080/ServletHello/MarkToWinServlet

這時console中輸出如下:



content : 淘寶淘寶淘寶淘寶




瀏覽器中輸出如下:

content : 百度百度百度百度







總結(jié):我可以把Wrapper程序變成如下,也能工作:

package com;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MarkToWinWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter myContent;
    public MarkToWinWrapper(HttpServletResponse res) {
        super(res);
    }
    public PrintWriter getWriter() {
        myContent = new CharArrayWriter();
        return new PrintWriter(myContent);
    }
    public String getResultMarkToWin() {
        return myContent.toString();
    }
}


或者下面的版本都可以:

package com;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MarkToWinWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter myContent;
    public MarkToWinWrapper(HttpServletResponse res) {
        super(res);
        myContent = new CharArrayWriter();
    }
    public PrintWriter getWriter() {
        return new PrintWriter(myContent);
    }
    public String getResultMarkToWin() {
        return myContent.toString();
    }
}


原理:馬克-to-win:CharArrayWriter自帶buffer,開始為空,當(dāng)你把PrintWriter和CharArrayWriter連好后,chain.doFilter(request, myWrapper);內(nèi)部調(diào)用getWriter,內(nèi)部把response里的內(nèi)容都寫到CharArrayWriter的Buffer中, getResultMarkToWin需要你自己調(diào)用。myContent.toString()會返回buffer里的內(nèi)容。