一、使用过滤器实现全站压缩

1.目标:对网站的所有JSP页面进行页面压缩,减少用户流量的使用。但是对图片和视频不进行压缩,因为图片和视频的压缩率很小,而且处理所需要的服务器资源很大。

2.实现原理

  (1)使用GZIPOutputStream工具对数据进行压缩,中间借助了ByteArrayOutputStream类进行结果的存储。

  (2)使用过滤器对浏览器请求进行拦截,通过自定义HttpServletResponse类(使用包装模式),重写getWriter方法,使得写出的目的地转变成ByteArrayOutputStream对象,即内存;通过查看JSP引擎解析之后的源代码,可以发现Servlet使用out对象向浏览器输出html代码,out对象是PageContext对象的一个成员变量,为JspWriter类的实例;而JspWriter实际上是对Writer类的包装,其本身和PrintWriter是兄弟关系;

  (3)JSP页面中使用的out对象和浏览器请求时产生的response对象通过方法getPrintWriter得到的对象是同一个对象。具体验证方法可以通过在自定义Response对象中的getWriter方法中进行打印输出验证。JspWriter类中有关键代码:  

 if (this.out == null) {
this.out = this.response.getWriter();
}

而out对象有定义:

public class JspWriterImpl
extends JspWriter
{
private Writer out;
......
}

  (5)具体过程:过滤器拦截浏览器向JSP页面发出的请求,并将请求的response对象替换成自定义的response对象,该对象重写了getWriter方法,使得JSP页面中的代码在经过JSP解析引擎解析之后的Servlet代码写出的目的地由浏览器变成了内存(ByteArrayOutputStream);过滤器拦截服务器向浏览器发出的响应信息,并将内存中的数据拿出来(ByteArrayOutputStream.toByteArray()),使用GZIPOutputStream进行压缩(压缩到ByteArrayOutputStream),最后通过getOutputSteam方法获得原生输出流对象,将压缩之后的数据写出到浏览器。

3.问题升级

  (1)现在又需求使用Servlet,而且要对Servlet进行压缩

  (2)Servlet使用getOutputStream方法获得输出流。

  (3)问:如果只是2.(5)的操作流程能解决这个需求吗?

答:不能,将会产生空指针异常,原因是ByteArrayOutputStream在重写的getWriter中被赋初值,如果没有调用该方法,则该变量一直为NULL,几乎不能对该变量进行任何操作,否则一定会报空指针异常。

  (4)解决方法:重写getOutputStream方法,通过和getWriter相同的目的将JSP页面的代码写到ByteArrayOutputStream(实现方式不同)

  (5)疑问:这样会不会对之前的JSP页面的压缩产生影响?之前的页面调用了response的getOutputStream方法~

    答:不会。最后输出的时候使用的response是原生的response对象,该对象的getOutputStream方法写到的目的地永远都是浏览器,而重写的getOutputStream方法只在自定义的HttpServletResponse,所以二者不会相互影响。

4.代码。

 package com.kdyzm.filter;
/*
* 使用过滤器拦截所有JSP页面并压缩文字,而且要实现低耦合性,一旦删掉过滤器
* 原来的jsp页面将不会被压缩。
* 全站压缩使用的模板代码!!!!!!!!!!!!!!!!!
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
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.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper; public class GzipFilter implements Filter { @Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse hsr=(HttpServletResponse) response;
MyResponse myResponse=new MyResponse(hsr);
chain.doFilter(request, myResponse);
ByteArrayOutputStream baso=myResponse.getArrayOutputStream(); //1.准备数据
byte []arr=baso.toByteArray();
ByteArrayOutputStream dest=new ByteArrayOutputStream();
//2.准备压缩工具
GZIPOutputStream gzip=new GZIPOutputStream(dest);
gzip.write(arr);
gzip.close();
//3.设置相关文件头
hsr.setContentType("text/html;charset=utf-8");
hsr.setHeader("Content-encoding", "gzip");
hsr.setContentLength(dest.toByteArray().length);
//3.1验证压缩结果
System.out.println("压缩之前:"+arr.length);
System.out.println("压缩之后:"+dest.toByteArray().length);
System.out.println("压缩比为:"+(1-dest.toByteArray().length*1.0/arr.length));
//4.发送到浏览器
OutputStream os=response.getOutputStream();
os.write(dest.toByteArray());
} @Override
public void destroy() {
}
}
class MyResponse extends HttpServletResponseWrapper
{
private ByteArrayOutputStream baos=null;
PrintWriter pw=null;
public MyResponse(HttpServletResponse response) {
super(response);
}
//重写Writer方法
@Override
public PrintWriter getWriter() throws IOException {
// System.out.println("拿到输出流!");//验证拿到的结果Writer就是在这里拿到的。
baos=new ByteArrayOutputStream();
pw=new PrintWriter(new OutputStreamWriter(baos,"utf-8"),true);
//因为调用的方法是write(String str)方法,所以就算写了true也不会自动刷新,所以必须关流才行
return pw;
}
//重写该类的getOutputStream方法,重写该方法的目的是为了防止某个Servlet使用了OutputStream输出了数据。
//这样即使对Servlet进行了过滤也不会产生任何错误!
@Override
public ServletOutputStream getOutputStream() throws IOException {
baos=new ByteArrayOutputStream();
ServletOutputStream sos=new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
baos.write(b);//这一步是最关键的步骤。
}
};
return sos;
}
public ByteArrayOutputStream getArrayOutputStream() {
if(pw!=null){
//关闭输出流的时机要充分把握好
pw.close();
}
return baos;
}
}

GzipFilter过滤器源代码

  注意:

  (1)自定义的PrintWriter对象一定要关闭,否则不会将内容输出缓冲区,因为JSP解析引擎调用的方法是out.write(String value);而不是out.println(String value),这样即使使用了自动刷新的构造方法创建PrintWriter对象也不会起作用;

  (2)重写getOutputStream方法的时候一定是将字节写到ByteArrayOutputStream对象中。

  (3)随时注意编码方式,有编码方式的地方一定要带上参数,否则极易发生乱码问题。


二、使用ThreadLocal模式解决跨DAO事务回滚问题。

1.目标:解决跨DAO的事务回滚问题从而实现使用ThreadLocal管理事务。

2.问题产生:由于DAO层的每个实例使用的都不是同一个Connection对象,因此如果想要实现事务回滚,就必须实现让DAO层使用的Connection是同一个对象,但是使用数据库连接池的时候分配的Connection对象是随机的,这样就很难办到让跨DAO的事务具有回滚功能。

3.解决方法:使用ThreadLocal管理事务,这样就能保证在DAO层涉及的Connection对象具有唯一性。

4.ThreadLocal简介

  (1)ThreadLocal内部维护了一个Map对象,该对象的键值是当前线程,即Thread.currentThread(),而值是希望与当前线程绑定的某个状态(一个对象),这样只要在同一个线程中所有代码均能获得该线程的绑定对象。再本例题中使用的对象是Connection。

  (2)ThreadLocal提供了set和get方法,表示设置当前线程的绑定对象和获取当前线程的绑定对象。

  (3)在java当前对象中有四种引用方式:强引用、软引用、弱引用、虚引用,而ThreadLocal使用内部使用的是若引用,因此可以不调用方法remove而删除和当前线程绑定的对象。

  (4)弱引用的表现:

 package com.kdyzm.test;

 import java.lang.ref.WeakReference;

 public class Test2 {
public static void main(String[] args) {
WeakReference<A>wr=new WeakReference<A>(new A()); System.gc();//弱引用在这里就会被垃圾回收器回收。
System.out.println("结果是:"+wr.get());
}
}
class A
{
@Override
protected void finalize() throws Throwable {
System.out.println("被回收!");
super.finalize();
}
}

弱引用WeakReference

5.解决思路:

  (1)为了解决DAO层的各个对象使用的Connection不相同的问题,需要在DataSource_c3p0中添加成员变量ThreadLocal<Connection>,该变量负责操作和Connection对象的绑定关系。在分配Connection对象的时候首先判断tl.get()有没有值,如果没有值,则从DataSource对象中分配出来一条Connection,并将该Connection和当前线程绑定。(ThreadLocal的set方法);否则直接返回通过get方法得到的Connection对象。

  (2)在过滤器中处理事务。当浏览器向服务器发起请求的时候,过滤器将会拦截请求,并且同时向数据库连接池请求一条连接,这时候会触发一个事件将当前线程和请求的Connection对象绑定。之后进行异常的捕捉,如果在doFilter方法执行的时候(执行的过程中只要是DAO层发出的请求Connection,返回值一定是最初过滤器请求的Connection对象,这样就保证了Connection对象的一致性)产生了异常(这里使用了Exception进行捕捉),这样一旦出现异常就进行回滚,否则就提交(在doFilter方法执行之前会首先调用方法setAutoCommit方法)。

6.问题升级。

  (1)第一次刷新请求Servlet没有问题,但是第二次刷新请求Servlet出现问题,原因是什么?

    答:出现的问题是不能操作已经关闭的连接问题,因为一次事务结束之后就会关闭连接,这时候连接将会成为空值。

    代码验证:

 public static void main(String[] args) throws SQLException {
Connection conn=DataSourceUtils_c3p0.getConnection();
System.out.println(conn);
conn.close();
System.out.println(conn);
}

验证连接关闭之后连接成为空值的问题

输出结果:

 com.mchange.v2.c3p0.impl.NewProxyConnection@10cccfb [wrapping: com.mysql.jdbc.JDBC4Connection@1658ade]
com.mchange.v2.c3p0.impl.NewProxyConnection@10cccfb [wrapping: null]

验证结果

  (2)解决方法

    答:在关闭连接的同时将和当前线程绑定的对象移除。使用remove方法。这样需要在DataSource_c3p0.java中添加一个remove方法,该方法实际上调用了ThreadLoal的remove方法。这样在下一次请求的时候,过滤器会重新向数据库连接池请求一个新的连接,同时该连接会和当前线程重新绑定关系,这样就不会发生空指针或者操作已关闭的Connection的情况了。

7.源代码(两个核心类)。

  (1)数据库连接池类DataSource_c3p0.java(这里使用c3p0数据库连接池)。

 package com.kdyzm.dbutils;
/**
* 数据库连接池工具类。
*/
import java.sql.Connection;
import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSourceUtils_c3p0 {
private static ThreadLocal<Connection>tl=new ThreadLocal<Connection>();
private static DataSource ds=null;
static{
ds=new ComboPooledDataSource("namedconfig");
}
public static Connection getConnection(){
Connection conn=tl.get();
if(conn==null)
{
try {
conn=ds.getConnection();
tl.set(conn);//这句代码十分重要,千万不能忘(将当前线程和请求的Connection对象绑定)
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
public static DataSource getDataSource(){
return ds;
}
public static void remove(){
tl.remove();//这句代码也十分重要,千万不能忘掉,将会在TransactionFilter中调用
}
}

DataSource_c3p0.java类源代码

  (2)过滤器TransactionFiler.java

 package com.kdyzm.filter;

 import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException; 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 com.kdyzm.dbutils.DataSourceUtils_c3p0; public class TransactionFilter implements Filter{ @Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化事务过滤器!");
} @Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("拦截过滤:");
Connection conn=null;
try{
conn=DataSourceUtils_c3p0.getConnection();
conn.setAutoCommit(false);
chain.doFilter(request, response);
conn.commit();
}
catch(Exception e){
System.out.println("出错了!回滚!!");
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
finally{
try {
conn.close();
DataSourceUtils_c3p0.remove();//这句代码十分重要,解决了两次刷新产生空指针异常、不能操作已经关闭的Connection的问题。
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void destroy() {
System.out.println("销毁事务过滤器!");
} }

TransactionFilter.java类源代码

8.扩展

  如果数据库的操作都成功了,只是JSP页面的一个/0的错误,这样的一个错误仍然会被过滤器拦截并导致数据库回滚,该怎样避免该问题的发生?

  答:在回滚之前判断异常类型,如果是SQLException类型的则回滚,否则不回滚。

【Java EE 学习 19】【使用过滤器实现全站压缩】【使用ThreadLocal模式解决跨DAO事务回滚问题】的更多相关文章

  1. 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...

  2. Java EE 学习(6):IDEA + maven + spring 搭建 web(2)- 配置 Spring

    参考:https://my.oschina.net/gaussik/blog/513353 注:此文承接上一文:Java EE 学习(5):IDEA + maven + spring 搭建 web(1 ...

  3. Java EE学习——Quartz的Cron表达式

    经历过低谷后,还是要好好学习,越失落会越来越落后. 今天写一下Cron表达式的用法,虽然是之前自己写的,也过了挺长一段时间,这次就拿出来作为回顾吧. Cron表达式是Quartz的精髓(个人觉得),比 ...

  4. Java EE 学习(9):IDEA + maven + spring 搭建 web(5)- 博客文章管理

    转载:Gaussic(一个致力于AI研究却不得不兼顾项目的研究生) . 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 web(1) Jav ...

  5. Java EE 学习(8):IDEA + maven + spring 搭建 web(4)- 用户管理

    转载:Gaussic(一个致力于AI研究却不得不兼顾项目的研究生) 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 web(1) ava E ...

  6. Java EE 学习(7):IDEA + maven + spring 搭建 web(3)- 配置数据库

    参考: https://my.oschina.net/gaussik/blog/513444 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 ...

  7. Java EE 学习(5):IDEA + maven + spring 搭建 web(1)

    参考:http://www.cnblogs.com/lonelyxmas/p/5397422.html http://www.ctolib.com/docs-IntelliJ-IDEA-c--1590 ...

  8. Java EE 学习(4):IDEA + maven 搭建 web(2)

    参考:http://www.bubuko.com/infodetail-1855067.html 现使用 Maven 创建项目:本节接Java EE 学习(3):IDEA + maven 搭建 web ...

  9. 【Java EE 学习 54】【OA项目第一天】【SSH事务管理不能回滚问题解决】【struts2流程回顾】

    一.SSH整合之后事务问题和总结 1.引入问题:DAO层测试 假设将User对象设置为懒加载模式,在dao层使用load方法. 注意,注释不要放开. 使用如下的代码块进行测试: 会报错:no sess ...

随机推荐

  1. 移动前端头部mete

    原文链接:http://caibaojian.com/mobile-meta.html//code from http://caibaojian.com/mobile-meta.html<!DO ...

  2. 二分图------》Hopcroft-Karp算法 hdu2389

    Hopcroft-Karp算法 该算法由John.E.Hopcroft和Richard M.Karp于1973提出,故称Hopcroft-Karp算法. 原理 为了降低时间复杂度,可以在增广匹配集合M ...

  3. Java abstract

    abstract修饰符可以修饰类和方法. (1)abstract修饰类,会使这个类成为一个抽象类,这个类将不能生成对象实例,但可以做为对象变量声明的类型,也就是编译时类型.抽象类就相当于一类的半成品, ...

  4. weiphp执行的流程

    微信交互   1.用户与微信进行交互,交互的事件包括:回复公众号,扫描与公众号有关的二微码,关注(取消关注)公众号,在公众号里点击自定义菜单等 2.微信把用户的交互事件及相关参数传递给weiphp的微 ...

  5. FWT

    Fast Walsh-Hadamard Transform .pre 今天本来想看FFT的应用的...翻翻picks的博客发现了好东西Fast Walsh-Hadamard Transform,感觉挺 ...

  6. python查找并删除相同文件-UNIQ File-wxPython-v6

    相比第一版,新增:菜单,对话框,文件过滤器,操作结果保存,配置功能(自己写了一个读写配置文件的功能),提示语优化,模块分化更合理. 截图: 源代码: UniqFile-wxPython-v6.py: ...

  7. Different Ways to Add Parentheses

    Given a string of numbers and operators, return all possible results from computing all the differen ...

  8. 【leetcode】First Missing Positive

    First Missing Positive Given an unsorted integer array, find the first missing positive integer. For ...

  9. poj 2403

    http://poj.org/problem?id=2403 题意:就是给你m个单词,以及n段对话.每一个单词都有所对应的价值.求对话中的价值总和 题解:很简单,就是用单词和价值对应起来,然后再寻找就 ...

  10. 华为 MATE7 调试 LOCAT 日志不输出问题

    [转]华为 MATE7 调试 LOCAT 日志不输出问题 http://www.cnblogs.com/glaivelee/p/4593221.html 用手机进行调试,在电脑上不显示logcat信息 ...