【Java EE 学习 19】【使用过滤器实现全站压缩】【使用ThreadLocal模式解决跨DAO事务回滚问题】
一、使用过滤器实现全站压缩
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事务回滚问题】的更多相关文章
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- Java EE 学习(6):IDEA + maven + spring 搭建 web(2)- 配置 Spring
参考:https://my.oschina.net/gaussik/blog/513353 注:此文承接上一文:Java EE 学习(5):IDEA + maven + spring 搭建 web(1 ...
- Java EE学习——Quartz的Cron表达式
经历过低谷后,还是要好好学习,越失落会越来越落后. 今天写一下Cron表达式的用法,虽然是之前自己写的,也过了挺长一段时间,这次就拿出来作为回顾吧. Cron表达式是Quartz的精髓(个人觉得),比 ...
- Java EE 学习(9):IDEA + maven + spring 搭建 web(5)- 博客文章管理
转载:Gaussic(一个致力于AI研究却不得不兼顾项目的研究生) . 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 web(1) Jav ...
- Java EE 学习(8):IDEA + maven + spring 搭建 web(4)- 用户管理
转载:Gaussic(一个致力于AI研究却不得不兼顾项目的研究生) 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 web(1) ava E ...
- Java EE 学习(7):IDEA + maven + spring 搭建 web(3)- 配置数据库
参考: https://my.oschina.net/gaussik/blog/513444 注:在阅读本文前,请先阅读: Java EE 学习(5):IDEA + maven + spring 搭建 ...
- 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 ...
- Java EE 学习(4):IDEA + maven 搭建 web(2)
参考:http://www.bubuko.com/infodetail-1855067.html 现使用 Maven 创建项目:本节接Java EE 学习(3):IDEA + maven 搭建 web ...
- 【Java EE 学习 54】【OA项目第一天】【SSH事务管理不能回滚问题解决】【struts2流程回顾】
一.SSH整合之后事务问题和总结 1.引入问题:DAO层测试 假设将User对象设置为懒加载模式,在dao层使用load方法. 注意,注释不要放开. 使用如下的代码块进行测试: 会报错:no sess ...
随机推荐
- Ubuntu 中安装 NetBeans IDE
NetBeans 8.2 刚刚发布,如果你还没有安装的话,这篇简短的教程将会演示如何在 Ubuntu 系统上快速的安装.对安装 NetBeans 需要帮助的开发者来说,这往篇文章会是不错的指导. 给那 ...
- Codeforces 727 D T-shirts Distribution
Description 有 \(6\) 种尺码的衣服,有的人只适合 \(1\) 种衣服,有的人适合相邻的 \(2\) 种,问是否存在合法方案,并输出. Sol 贪心. 首先,只适合 \(1\) 种衣服 ...
- 26 BasicUsageEnvironment基本使用环境——Live555源码阅读(三)UsageEnvironment
26 BasicUsageEnvironment基本使用环境--Live555源码阅读(三)UsageEnvironment 26 BasicUsageEnvironment基本使用环境--Live5 ...
- Python自动化之线程进阶篇(二)
queue队列 class queue.Queue(maxsize=0) #先入先出 class queue.LifoQueue(maxsize=0) #后入先出 class queue.Priori ...
- 17.1---编写一个函数交换两个变量的值(CC150)
用^来操作: public static int[] exchangeAB(int[] AB){ AB[0] = AB[0] ^ AB[1]; AB[1] = AB[0] ^ AB[1]; AB[0] ...
- 【GoLang】golang中 channel 实现同步 与mutex/atomic 实现同步的讨论
参考资料: https://groups.google.com/forum/#!topic/golang-china/q4pFH-AGnfs
- poj 2403
http://poj.org/problem?id=2403 题意:就是给你m个单词,以及n段对话.每一个单词都有所对应的价值.求对话中的价值总和 题解:很简单,就是用单词和价值对应起来,然后再寻找就 ...
- Shell数组相关操作
参考:http://www.cnblogs.com/chengmo/archive/2010/09/30/1839632.html 创建数组 a=( ) 获得数组长度 ${#a[@]} #${#变量} ...
- Spring MVC配置静态资源的正常访问
SpringMVC如果过滤器过滤范围配置了/或者/*,那么框架会过滤所有请求,包括自己写的请求和静态资源请求,这样静态资源就不能正常加载,包括js文件.css文件.图片资源访问的时候都会出现404页面 ...
- (Win7 x64)NetBeans 8.0.2 使用Tomcat 8作为服务器
1.下载Apache Tomcat,解压至本地硬盘的根目录. 2.运行CMD,输入: 解压盘符:\apache-tomcat-8.0.xx\bin\service.bat install 3.安装完成 ...