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

背景描述

如果之前看过《动态代理在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都会被我们定义的动态代理所劫持,在数据输入前或数据输出后重新编码,而这一切对于开发人员来说一切都是透明的,并且不会对现有逻辑造成污染。

后记

这里谈论的只是动态代理应用案例,并不意味着必须使用动态代理是该案例中解决问题的唯一方案。其实动态代理在这里不一定是最佳的实现方案,如果可以的话最好对数据库进行重新设置修改默认编码,或者是在应用层统一编码,但这些往往都不适用于一个经过十多年、几十人、且风格各异且“烂”的发臭的项目了。其实实际的数据库VARCHAR类型中存放的字符串编码五花八门,EUC-JP/SHIFTJIS/UTF-8/8859_1,并且这些编码可能同时存在于一张表,要了命了!

时间: 2024-11-09 03:42:08

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

Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题

Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只能为接口创建代理实例. 如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生成被代理接口的新的匿名实现类. JDK动态代理具体实现原理: 通过实现InvocationHandlet接口创建自己的调用处理器: 通过为Proxy类指定ClassLoader对象和一组in

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

WEB案例 目前有一个2005年开始,基于Struts1的Web项目A,其验证部分依赖于主站的SSO(单点登录).在请求站点A的时候,用户会被强制带去做SSO验证,通过身份验证后后,主站会自动地把请求转发至A站点,并在request header中添加了用于保存登录用户ID的新属性SM_USER,然后A站点根据用户ID提供相应的服务.由于该项目是一个既存项目,所以其中残余大量像下面一样的测试代码. [java] view plain copy String user_id = request.g

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

WEB案例 目前有一个2005年开始,基于Struts1的Web项目A,其验证部分依赖于主站的SSO(单点登录).在请求站点A的时候,用户会被强制带去做SSO验证,通过身份验证后后,主站会自动地把请求转发至A站点,并在request header中添加了用于保存登录用户ID的新属性SM_USER,然后A站点根据用户ID提供相应的服务.由于该项目是一个既存项目,所以其中残余大量像下面一样的测试代码. String user_id = request.getHeader("XX_USER"

spring,web,java 开发中乱码解决方案

修改HTML/JSP页面编码格式: <meta charset="UTF-8">或<%@ page contentType="text/html; charset=UTF-8"%> web.xml中的编码设置(  需要设置forceEncoding参数值为true,强制以目标编码为编码格式 ) <filter>        <filter-name>CharacterEncoding</filter-name&

在Filter中使用动态代理解决请求中文乱码问题

使用动态代理解决请求中文乱码问题 1.增强一个类我们常用的几种解决方案: 1.继承 a) 优点简单 b) 必须有被增强类的实现类 2.装饰者模式 a) 要求:1实现相同的接口.2持有被增强的对象 b) 优点:不必知道被增强的实现是谁 c) 缺点:必须实现所有没被增强方法的原始对象的原样调用 3.动态代理 a) 要求:1实现相同接口,2持有被增强的对象 b) 优点:不必手动实现所有不增强方法的原样调用.对方法进行增强时有类似过滤器的功能. c) 缺点:学习成本高. 代理(Proxy): 一个代理对

java中的动态代理(一)

今天我们来学习java中的另一个重要的特性叫做动态代理.所谓动态代理是可以在java运行过程中动态的创建一个类去实现一个或多个接口,可以在不修改原有类代码的基础上动态的添加功能和方法.正是因为这个特性使动态代理被java的许多框架中被广泛的使用. 在介绍动态代理之前我需要先介绍一下什么是静态代理.在设计模式那一章我曾经介绍过一种设计模式叫做代理模式.在代理模式中有一个公共的接口,代理对象和实际的对象都需要去实现这个接口.在代理对象中有一个成员属性变量指向实际对象并在代理对象的构造函数中初始化,在

MyBatis - Mapper动态代理开发

采用Mapper动态代理方法只需要编写相应的Mapper接口(相当于Dao接口),那么Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同Dao接口实现类方法. - Mapper接口开发需要遵循以下规范: ① Mapper.xml文件中的namespace与mapper接口的全类名相同. ② Mapper接口方法名和Mapper.xml中定义的statement的id相同. ③ Mapper接口方法的输入参数类型和mapper.xml中定义的statement的paramet

Java中动态代理技术生成的类与原始类的区别 (转)

用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后来定义的逻辑.这样就达到了动态的在原有类上增加一些功能.比如日志打印,拦截信息等. 这里只关心动态代理技术生成新的类,先不管虚拟机是如何去生成类,用了什么字节码生成技术,怎么产生字节码等这一系列动作.现在只关心最后生成的新类长什么样,它和老类有什么区别.为了获取到生成后的代理类的字节码并且反编译成我

Web 开发中很有用的8款在线工具

在工作中借助一些非常好用的工具可以让你专注于更重要的事情,进而提高工作效率.本文收集了一些在 Web 设计和开发中很有帮助的在线工具分享给大家,希望对你有帮助. ProCSSor ProCSSor是一个很不错的CSS代码美化工具,它可以帮助你很轻松的把代码转换成很美观. jsFiddle JsFiddle有多种用途,其中一个用法就是可以在线编辑HTML, CSS 和JavaScript片段,而且代码可以分享,还可以嵌入到你的博客中等等. Frame Box Frame Box是一个非常好用的用于