过滤器Filter的案例4——实现全站的数据的压缩
7、案例六: 实现全站的数据的压缩
(1)IO知识回顾
过滤器流:
过滤器输入流:从已经存在的输入流(比如:FileInputStream)中读取数据,对数据进行适当的处理和改变后再送入程序。
过滤器输出流:向已经存在的输出流(比如:FilterOutputStream)写入数据,在数据抵达底层流之前进行转换处理等工作。
(2)全站的数据的压缩:Servlet实现
GzipServlet类
package com.zhku.jsj144.zk.filter.gzipFilter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//在实际开发过程中,你访问的资源文件,输出数据时,是会对数据进行压缩的。
//为什么要压缩?
//是为了节省带宽,提高速度,增强用户体验【很多时候,对于网站来说,也需要进行数据压缩】
//如何进行压缩?
//写Java代码压缩,目前市场用的最多的压缩方式是gzip压缩。 gzip ,tar--bzip
//Javaweb(压缩)--浏览器去看数据时,当数据时被压缩了,name就会去做戒烟所的动作
//完成压缩和解压缩的功能
//压缩:读取服务器端要发给客户端的信息,进行压缩后,再写给客户端。
//原理:对于要压缩的数据,以某种输出流的形式进行压缩
public class GzipServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//需要压缩的数据源
String data="时间是说或或或或或或或或或或或或或或或或或或或asdflajsdlfjasldf"
+"asdfajsdofawfewwwwwwwwwwwww阿诗丹顿多多多多多多多多多多多多多多"
+"阿诗丹顿多多多多多多多多多多多多多多的点点滴滴多多多多多多多多多多多多多多asdfasdfasdf"
+"说话吧思考对方还未发布猥琐地发生的不可发红丝带发斯蒂芬哈斯蒂法是否合适地方";
byte[] b=data.getBytes();
System.out.println("压缩前数据长度:"+b.length);
//字节数组流(底层流)
ByteArrayOutputStream baos=new ByteArrayOutputStream();
//采用gzip压缩,这里压缩是,需要的是一个输出流,这里的输出流是一个底层流
//GZIPOutputStream:此类为使用 GZIP 文件格式写入压缩数据--【压缩】
//GZIPInputStream:此类为读取 GZIP 文件格式的压缩数据 --【解压缩】
GZIPOutputStream gos=new GZIPOutputStream(baos);
gos.write(data.getBytes());
gos.close();//确保数据可以写到底层流中去
//说明:由于数据是写到底层流中baos中去的,gos默认的是有缓冲区的
b= baos.toByteArray();
System.out.println("压缩后数据长度:"+b.length);
//注意,如果数据压缩了,一定要告诉浏览器,因为浏览器要知道怎样读取数据
//告诉浏览器 需要 解压缩 数据-- 通过 http的响应头 实现
response.setHeader("content-encoding","gzip");//压缩格式告诉浏览器
response.setContentLength(b.length); //数据的长度
response.getOutputStream().write(b);//压缩后的数据写给浏览器
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
(3)全站的数据的压缩:过滤器实现
3.1思路分析
思考:如何解决拿到压缩前的数据问题?
整体思路:
1.应用HttpServletResponseWrapper对象,压缩响应正文内容通过filter向目标页面传递一个自定义的response对象。
2.在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。
3.在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。
4.当页面完成输出后,在filter中就可得到页面写出的数据【压缩前的数据】,从而我们可以调用GzipOuputStream对数据进行压缩后【压缩】再写出给浏览器,以此完成响应正文件压缩功能【压缩后】。
3.2代码实现【TestServlet + MyHttpServletResponseWrapper + MyServletOutputStream +GzipFilter +web.xml文件配置】
3.2.1 TestServlet (测试的servlet)
package com.zhku.jsj144.zk.filter.gzipFilter;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String data="时间是说或或或或或或或或或或或或或或或或或或或asdflajsdlfjasldf"
+"asdfajsdofawfewwwwwwwwwwwww阿诗丹顿多多多多多多多多多多多多多多"
+"阿诗丹顿多多多多多多多多多多多多多多的点点滴滴多多多多多多多多多多多多多多asdfasdfasdf"
+"说话吧思考对方还未发布猥琐地发生的不可发红丝带发斯蒂芬哈斯蒂法是否合适地方";
response.getWriter().write(data);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
3.2.2 MyHttpServletResponseWrapper
package com.zhku.jsj144.zk.filter.gzipFilter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/*
思路:
通过filter向目标页面传递一个自定义的response对象。
在自定义的response对象中,重写getOutputStream方法和getWriter方法,
使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。
在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。
当页面完成输出后,在filter中就可得到页面写出的数据,
从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。
*/
public class MyHttpServletResponseWrappe rextends HttpServletResponseWrapper {
private HttpServletResponse response;
private ByteArrayOutputStream bout = new ByteArrayOutputStream();//数据写入到字节输出流中
private PrintWriter pw;
publi cMyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
this.response=response;
}
//获得压缩前的数据
public byte[] getOldData(){
if(pw!=null){
pw.close();//将缓冲区中的数据都写到bout中
}
return bout.toByteArray();//字节数据中的数据
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
// ServletOutputStream outputStream = response.getOutputStream();
// outputStream.write(b);//原来的写方法
//目的:重写outputStream里面的write方法,达到目的
//增强后的outputStream,里面的write方法已经改写了
//数据写入到字节输出流中
MyServletOutputStream myoutputStream=new MyServletOutputStream(bout);
return myoutputStream;
// return super.getOutputStream();
}
@Override
public PrintWriter getWriter() throws IOException {
//字符输出流,有缓缓从去
if(pw==null){
//PrintWriter(OutputStreamout)
//pw=newPrintWriter(bout);//没有设置编码,失败
//字符流-->字节流
pw=new PrintWriter(newOutputStreamWriter(bout,"utf-8"));
}
return pw;
// return super.getWriter();
}
}
3.2.3 MyServletOutputStream
package com.zhku.jsj144.zk.filter.gzipFilter;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
//增强后的outputStream
public class MyServletOutputStream extends ServletOutputStream {
// private ServletOutputStream out;
private OutputStream outputStream;
public MyServletOutputStream(OutputStream outputStream){
this.outputStream=outputStream;//获得原来的outputStream
}
//思考:如何改写write方法
//这里没有一个接受字节数组的write方法
//但是我们通过找ServletOutputStream的父类OutputStream,可以发现OutputStream里面的write(byte[]b)
//里面的最终调用的方法是write(intb)
//思考方式:当继承的父类方法受限,我们要考虑Java的多态机制,要找一个突破口:调用父类的父类的方法进行继承,
// 来摆脱方法不够的限制
//详细解释:
//OutputStream里面的write(int b)方法 :
//是一个抽象方法:publicabstract void write(int b) throws IOException;
//OutputStream里面的write的字节数组的方法
// public void write(byte b[], int off, int len) throws IOException {
// if (b == null) {
// throw new NullPointerException();
// } else if ((off < 0) || (off > b.length) || (len < 0) ||
// ((off + len) > b.length)|| ((off + len) < 0)) {
// throw new IndexOutOfBoundsException();
// } else if (len == 0) {
// return;
// }
// for (int i = 0 ; i < len ; i++) {
// write(b[off + i]); --------------->write(int b)
// }
// }
@Override
public void write(int b) throws IOException {
outputStream.write(b);//调用父类的父类的方法
//所有的数据 都进入 到了outputStream中去 了,
//而这里outputStream又 是 MyHttpServletResponseWrapper里的bout[ByteArrayOutputStream ]了,
//所以 所有的数据 都 在bout[ByteArrayOutputStream:字节数组输出流]中了
}
}
3.2.4 GzipFilter
package com.zhku.jsj144.zk.filter.gzipFilter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
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.HttpServletRequest;
//全站数据的压缩
//压缩的对象:服务器写给浏览器的数据,进行压缩后,然后才写给浏览器
//思考:
//1.如何获取压缩的数据源?
//2.压缩针对的是什么?repsonse对象
//3.压缩针对的是什么方法?response对象的:response.getOutputStream().write() 以及 response.getWriter().write()
// 因为写给浏览器的数据,都是通过它们实现的。
import javax.servlet.http.HttpServletResponse;
//分析:
//1.我们要获得改造后的response,为此,我们要使用装饰者模式:装饰response,重写里面的getOutputStream() 以及 getWriter()
// 主要目的是为了:获得压缩的数据源
//2.然后调用压缩相关对象:GZIPOutputStream:此类为使用 GZIP 文件格式写入压缩数据--【压缩】,进行压缩操作
//3.将压缩后的数据发送给浏览器端
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest)request;
HttpServletResponse resp=(HttpServletResponse)response;
//目的:装饰resp
MyHttpServletResponseWrapper myResponse=new MyHttpServletResponseWrapper(resp);
chain.doFilter(req, myResponse);//放行
//目标资源得到执行
//获取压缩的原始数据---------------------------------------------
//重点!!!!
byte[] b=myResponse.getOldData();//获得压缩前的原始数据
System.out.println("压缩前---数据的长度:"+b.length);
//压缩代码----------------------------------------------
//底层流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//采用gzip压缩
//这里 压缩的时候, 需要的是一个 输出流, 这里的输出流是一个底层流
GZIPOutputStream gout= new GZIPOutputStream(baos);
gout.write(b);
gout.close(); //确保 数据可以写到 底层流 baos中去
//这里 要注意: 由于 数据是写到 底层流 baos中去的, gout 默认的是有 缓冲区的 .
b = baos.toByteArray();
System.out.println("压缩后---数据的长度:"+b.length);
//告诉浏览器 需要 解压缩 数据-- 通过 http的响应头 实现
resp.setHeader("content-encoding", "gzip");
resp.setContentLength(b.length); //数据的长度
//将压缩后的数据写给浏览器
response.getOutputStream().write(b);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
3.2.5 web.xml文件配置
<!-- 解决全站的数据压缩 -->
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.zhku.jsj144.zk.filter.gzipFilter.GzipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>/testServlet</url-pattern>
</filter-mapping>