Filter对Response的改变:HttpServletResponseWrapper的工作原理

Filter对Response的改变:HttpServletResponseWrapper的工作原理
马克- to-win:马克 java社区:防盗版实名手机尾号: 73203。
马克-to-win:前面我们讲的知识,主要说的是由于Filter的参与,用户的访问路径被改变的问题。底下我们就要讲一点更难的话题,就是Filter 如何改变一个现有的html。比如我写的新浪博客,写完以后,一上传,内容有时有些改变,谁动的手脚?肯定是新浪公司编了什么Filter过滤器,把我的 html的内容给改变了。马克-to-win:现在问题是:这是如何实现的呢?这里核心问题其实就是如何改变Response?本来我的html在原来的 Response里,准备返回给客户端。但现在在Filter当中被改变了。但这又是怎么改变的呢?这里涉及到一个 HttpServletResponseWrapper的类实例myWrapper问题。Wrapper英文就是包裹者的意思。正常情况下,我们过去的认识是:chain.doFilter(request, response);的意思就是访问完后面的目标资源以后,目标资源把要返回给客户端的内容放在Response当中。而现在这里的例子就不同了:通过 chain.doFilter(request, myWrapper);目标资源就会把要返回给客户端的内容放在myWrapper当中了。这时在Filter当中,我们就可以从myWrapper当中取出返回给客户端的内容,接着就可以大大方方的对其进行改变了。要想做成这件事儿,当然还得符合sun公司制定的有关HttpServletResponseWrapper的所有规章制度。首先通过MarkToWinWrapper myWrapper = new MarkToWinWrapper((HttpServletResponse) response);让response和myWrapper联系起来。马克-to-win:之后,在我编的MarkToWinWrapper这个普通类当中,需要初始化一个CharArrayWriter的实例:myContent = new CharArrayWriter();和PrintWriter的实例pw=new PrintWriter(myContent);之后通过编写public PrintWriter getWriter() { return pw; }。当你执行chain.doFilter(request, myWrapper);时,系统其中一步会调用getWriter(),得到pw以后,就会把你myWrapper构造函数里得到的response和CharArrayWriter的实例:myContent联系起来。最后当你执行 public String getResultMarkToWin() { return myContent.toString(); }时,返回给客户端的内容就被你得到了,因为response和myContent已经被你联系起来了。注意要想正确应用 HttpServletResponseWrapper,必须遵守它的规则。下面的例子把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);
/*下句话之后目标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();
    }
}



当在浏览器中运行AAA.html时,console中输出如下:


content :

既然这是首页,像淘宝首页一样,这底下是首页的一些泛泛信息。




而在浏览器中,输出的是:

content :

既然这是首页,像百度首页一样,这底下是首页的一些泛泛信息。



7_a)HttpServletResponseWrapper对Servlet的过滤:


马克-to- win:上面这个MarkToWinWrapper也可以过滤Servlet:现在的问题是,改变Servlet的response有什么用处?比如:现在你的Servlet从数据库中获取回数据,放在response中,之后需要返回给客户浏览器,返回前做敏感字滤除。马克-to-win:如果你要在一百个Servlet当中做这件事儿,即使你可以编写一个普通类,然后每个Servlet都调用这个普通类的方法来转化,这其实就造成了耦合。一旦有一天哪个普通类的类名,方法名或参数什么的需要改变,(这是经常需要的,因为需求也在不断变化)你的一百个Servlet里头都需要改变。如果这件事要在 Filter当中做,就不是这种场景了。只需改动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 : 百度百度百度百度







总结:我可以把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,开始为空,当你把PrintWriter和CharArrayWriter连好后,chain.doFilter(request, myWrapper);内部调用getWriter,内部把response里的内容都写到CharArrayWriter的Buffer中, getResultMarkToWin需要你自己调用。myContent.toString()会返回buffer里的内容。