动态代理在WEB与JDBC开发中的应用(JDBC篇)

来源:互联网 发布:linux glibc 安装 编辑:程序博客网 时间:2024/06/04 19:44

作者:rcom10002

非常感谢原作者!!

背景描述

如果之前看过《动态代理在WEB与JDBC开发中的应用(WEB篇)》,这篇的内容可以全当是另一种应用的进阶举例,而在实现上确实没有太多进步的地方。我们先看一下项目所面临问题以及期望解决方案。在作者所接触的这个项目中,直接使用原始JDBC技术,java.sql.PreparedStatement和java.sql.ResultSet几乎占领了数据访问层,没有半点OR Mapping的迹象,看起来是不是很悲催?命啊~在项目开发至一半的时候,突然发现要对日文字符进行支持,而在之前一直使用英文进行测试。好了,问题来了,一个编码为8859_1的数据库怎么来接收Java中的UTF-8呢?并且很多CRUD功能已经完成,每个实现中涉及的字段数目相当之大,难道针对每个字段逐一调整不成?

问题分析

针对上面描述的情况,我们先做一个简单的分析。目前所面临的问题可以归为两种类型,第一类就是乱码的问题,这个可以通过重新编码得到解决;第二类则是面临大量的代码实现,如果对所有内容进行调整的话,很多功能就得重新测试,成本较高,但确实可行。

针对乱码问题,从实现的角度考虑,可以分别针对输入数据和输出数据进行编码转换。是在使用PreparedStatement的时候,对所有调用setString的地方进行编码转换;而对于数据读取的情况,可以对ResultSet.getString所返回的结果字符串进行编码转换。下面以UTF-8转8859_1为例子,实现编码转换(逆向转换只需交换编码名称的位置即可):

new String(new String("abc").getByte("UTF-8"), "8859_1")
如果以这种方式对每处PreparedStatement.setString和ResultSet.getString的调用都进行编码转换操作的话,工作量不仅繁重,而且不利于代码的维护与移植。从API上看,其实我们可以针对setString和getString进行“HOOK”,使用代理模式比较适合,动态代理最为适宜。

解决方案

编码问题解决了,利用动态代理统一地对setString和getString接口进行自制,让所有的调用都在我们的控制之下还何愁不能统一江山!

public static ResultSet createResultSetJPWrapper(ResultSet rs) {    return (ResultSet)(Proxy.newProxyInstance(ResultSet.class.getClassLoader(),        new Class[] {ResultSet.class},            new ResultSetJPWrapper(rs)));}private static class ResultSetJPWrapper implements InvocationHandler {private ResultSet wrappedResultSet;    public ResultSetJPWrapper(ResultSet rs) {        wrappedResultSet = rs;    }    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        Object result = method.invoke(wrappedResultSet, args);        if ("getString".equals(method.getName()) && null != result) {        result = CommonUtils.convertCharset((String)result, ISAConstants.CHARSET_8859_1, ISAConstants.CHARSET_UTF_8);        }        return result;    }}public static PreparedStatement createPreparedStatementJPWrapper(PreparedStatement pstmt) {    return (PreparedStatement)(Proxy.newProxyInstance(PreparedStatement.class.getClassLoader(),        new Class[] {PreparedStatement.class},            new PreparedStatementJPWrapper(pstmt)));}private static class PreparedStatementJPWrapper implements InvocationHandler {private PreparedStatement wrappedStatement;    public PreparedStatementJPWrapper(PreparedStatement pstmt) {        wrappedStatement = pstmt;    }    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        if ("setString".equals(method.getName()) && args.length == 2 && null != args[1] && String.class.equals(args[1].getClass())) {        args[1] = CommonUtils.convertCharset((String)args[1], ISAConstants.CHARSET_UTF_8, ISAConstants.CHARSET_8859_1);        }        return method.invoke(wrappedStatement, args);    }}
接下来就是在需要使用PreparedStatement和ResultSet的地方进行重构。
// 重构前PreparedStatement stmt = conn.prepareStatement(sql);// 重构后PreparedStatement stmt = DBUtils.createPreparedStatementJPWrapper(conn.prepareStatement(sql));// 重构前ResultSet rs = stmt.executeQuery();// 重构后ResultSet rs = DBUtils.createResultSetJPWrapper(stmt.executeQuery());

重构后不论是stmt还是rs,setString和getString都会被我们定义的动态代理所劫持,在数据输入前或数据输出后重新编码,而这一切对于开发人员来说一切都是透明的,并且不会对现有逻辑造成污染。


原创粉丝点击