//推荐使用的! 亲测可用
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.zip.GZIPOutputStream; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //注解+动态代理 public class GzipFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) resp; final ByteArrayOutputStream bout = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(new OutputStreamWriter(bout,response.getCharacterEncoding())); //response.getOuputSstream getWriter //ResponseProxy.get chain.doFilter(request, (ServletResponse) Proxy.newProxyInstance(GzipFilter.class.getClassLoader(), response.getClass().getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("getOutputStream")){ //MyServletOutputStream.write return new MyServletOutputStream(bout); //需要压缩的话先要获得数据,但是先不写出去,先放在bout流.其实就是返回一个bout和重写了write的流 }else if(method.getName().equals("getWriter")){ return pw; }else{ return method.invoke(response, args); } } })); if(pw!=null){ pw.close(); } //此时数据已经封装到了bout中,因为拿到的就是 new MyServletOutputStream(bout); byte data[] = bout.toByteArray(); //获得写出去的数据,截留下来, 不管调用是哪个流,最终都会被写到bout中去,这时要注意,filter要捕获servlet的输出,并把servlet输出写到buffer,然后,把数据再写给浏览器. ByteArrayOutputStream bout2 = new ByteArrayOutputStream(); //再定义一个低层流,此类实现了一个输出流,其中的数据被写入一个 byte 数组。 //缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。 GZIPOutputStream gout = new GZIPOutputStream(bout2); //把获得的数据压缩之后先不写出去,先写到bout2流 gout.write(data); //将数据写到一个流中 gout.close(); byte gzip[] = bout2.toByteArray(); //获得写完了之后的压缩数据 response.setHeader("content-encoding", "gzip"); response.setContentLength(gzip.length); response.getOutputStream().write(gzip); //最后采用response把压缩后的数据写出去 } class MyServletOutputStream extends ServletOutputStream{ private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout){ this.bout = bout; } @Override public void write(int b) throws IOException { this.bout.write(b); } } public void destroy() { // TODO Auto-generated method stub } public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } }
扩展阅读代码:
package cn.icoke.filter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.zip.GZIPOutputStream; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpSession; /* *author xiaozhe * * */ public class GzipFilter implements Filter { /** * Constructor of the object. */ public GzipFilter() { super(); } private FilterConfig filterConfig; public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) { try { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; HttpSession session = request.getSession(); ProxyResponse rpoxy = new ProxyResponse(response); filterChain.doFilter(request, rpoxy.getResponse()); byte[] result = rpoxy.toByteArray(); System.out.println("压缩前的数据大小:"+ result.length); //实现压缩数据 byte[] gzipResult = gzip(result); System.err.println("压缩后的数据大小:"+gzipResult.length); response.setHeader("Content-Encoding","gzip"); response.setHeader("Content-Length", gzipResult.length+""); response.getOutputStream().write(gzipResult); } catch (IOException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } }//end method doFilter() private byte[] gzip(byte[] result) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(out); gout.write(result); gout.close(); return out.toByteArray(); } public void destroy() { } }//end class /* * response 装饰设计模式实现 */ /* class RaiseResponse extends HttpServletResponseWrapper{ private ByteArrayOutputStream out = new ByteArrayOutputStream(); private PrintWriter pw = null; private HttpServletResponse response = (HttpServletResponse) super.getResponse(); public RaiseResponse(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { return new RaiseOutputStream(out); } @Override public PrintWriter getWriter() throws IOException { //字符流在再转换成字节流是 注意要用转换流OutputStreamWriter去转换并设置编码集 不然会出现乱码现象 pw = new PrintWriter(new OutputStreamWriter(out,response.getCharacterEncoding())); return pw; } public byte[] toByteArray(){ if(pw != null){ pw.close(); } return out.toByteArray(); } }//end class class RaiseOutputStream extends ServletOutputStream{ ByteArrayOutputStream out = null; public RaiseOutputStream(ByteArrayOutputStream out){ this.out = out; } @Override public void write(int b) throws IOException { out.write(b); } }//end class */ /* *代理模式 实现数据压缩 */ class ProxyResponse { private HttpServletResponse response; private ByteArrayOutputStream out = new ByteArrayOutputStream(); private PrintWriter pw = null; public ProxyResponse(HttpServletResponse response){ this.response = response; } public HttpServletResponse getResponse(){ return (HttpServletResponse) Proxy.newProxyInstance(ProxyResponse.class.getClassLoader(), response.getClass().getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("getOutputStream")){ return new RaiseServletOutputStream(out); } if(method.getName().equals("getWriter")){ pw = new PrintWriter( new OutputStreamWriter(out,response.getCharacterEncoding())); return pw; } return method.invoke(response, args); } }); }//end method public byte[] toByteArray(){ if(pw != null){ pw.close(); } return out.toByteArray(); }//end method }//end class class RaiseServletOutputStream extends ServletOutputStream{ ByteArrayOutputStream out = null; public RaiseServletOutputStream(ByteArrayOutputStream out ){ this. out = out; } @Override public void write(int b) throws IOException { out.write(b); } } /* * 在配置一个 压缩功能的filter时要注意 <dispatcher>FORWARD</dispatcher> * 拦截方式要大写 可以同时配置多个拦截方式 * * <filter> <filter-name>GzipFilter</filter-name> <filter-class>cn.icoke.filter.GzipFilter</filter-class> </filter> <filter-mapping> <filter-name>GzipFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> </filter-mapping> * * */
其他应用案例
案例1: 分析:因为客户端接受的数据只有那么几种,其中GZIP是最长用的一种,所以我们就需要知道怎么在服务器把数据压缩后发给客户端 1:首先服务端要有数据 2:把数据读到内存中 3:把内存中的数据读到流中 具体如下: public void doGet(HttpServletRequest request, HttpServletResponse respose) throws ServletException, IOException { String str="xxxxxxxxxxxxxxxxx"; ByteArrayOutputStream bos=newByteArrayOutputStream(); GZIPOutputStream gos=new GZIPOutputStream(bos); gos.write(str.getBytes()); gos.close(); byte[] buff=bos.toArrayByte();//读取压缩后的数据 response.setHeader("Content-Encoding","gzip");//通知客户端浏览器的数据压缩格式 response.getOutputStream().write(buff);//把压缩后的数据打印到客户端 } 案例2: 响应消息头(302与setHeader())的请求重定向: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(302); response.setHeader("Location", "/day/1.html"); } 案例3: 在一个页面上每隔3秒钟产生一个随机数 代码如下: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Random r=new Random(); response.setHeader("Refresh", "3");//如果想3秒后转到一个页面只需这样既可("Refresh","3;URL="/day/2.html""); response.getOutputStream().write((r.nextInt()+"").getBytes()); } 案例4: 分析: 1:首先要得到图片的路径,通过ServletContext得到 2:把图片读取到流中 3:通知浏览器下载 4:把数据打印出去 注意:可能这个案例目前有些简单,因为关于图片名字为中文时出现乱码该怎么处理,在这里没有具体说出,不过 在以后会细说。 图片的下载问题 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String realPath=ServletContext().getRealPath("/xxxxx");//得到图片的路径 InputStream in=new FileInputStream(realPath);//把图片读取到流中 response.setHeander("Content-Disposition","attachment;fileName=1.jpg");//通知浏览器下载 byte[] buf=new byte[1024];//定义一个缓冲区 int len=0; OutputStream os=response.getOutputStream();//得到输出流对象 while((in.read(buf))!=-1){ out.write(buf,0,len); } out.close(); in.close(); } 案例5: 缓冲问题 如何不让浏览器缓冲我们服务端的数据,因为动态数据是不需要缓存的 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String str="aaaaaaaa"; response.setHeader("Expires","-1");//通知浏览器不要缓存数据,当第二个参数小于0时,表示不缓存 response.setHeader("Cache-Control","no-cache");//这是针对HTTP1.1 response.setHeader("Pragma","no-cache");//这是针对HTTP1.0 }