博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
classloader实战:一个程序使用相同数据库的两个不同版本的jar包
阅读量:5924 次
发布时间:2019-06-19

本文共 4100 字,大约阅读时间需要 13 分钟。

问题场景

现在很多工程为了功能扩展,都给出了插件化的方式。只需要用户配置好配置文件,提供好需要的jar包,就能完成响应功能。本文说一下,数据转存数的功能实现。现在项目一般都离不开数据库,自己本身的项目就会带这驱动包,但是也会有这样的一种需求,就是数据额外存储的定制化,当产生的数据在自己项目的流程中不满足现在使用。例如做报表,项目本身产生数据,但是需要把里面的一部分数据拿出来和其他文本数据结合,产生新的数据。或者现有的数据进行时间的整合,直接变成周数据,月数据,年数据。这些都是需要根据不同用户自己设定的。这些数据往往是需要另外的数据库的,这就带来了一个问题,项目本身有一个jar包,例如Mysql4.5的,客户想组织信息存入mysql5.5,jar包的类是相同的,这样就带来了驱动加载的问题,因为类的相同的,而且驱动不是完全不兼容,而是在使用上会出问题。典型的就是ojdbc14,用这个版本的驱动建立数据库使用语句池缓存会有问题,ojdbc5,6就没事。(毕竟语句池缓存能带来性能上优化,不能说为了兼容驱动放弃性能,而且其他潜在的问题还没有暴露)。

问题分析

本质上就是一个获取正确连接的问题,我们正常使用驱动第一件事就是class.forname("xxxxx"),mysql(注册肯定是需要注册的,但是不一定发生在class.forname上)的话就是把驱动类注册到了DriverManager上,然后我们通过DriverManager来获取connection对象。getConnection的过程可以分为以下步骤。

1,验证对象

private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {        boolean result = false;        if(driver != null) {            Class
aClass = null; try { aClass = Class.forName(driver.getClass().getName(), true, classLoader); } catch (Exception ex) { result = false; } result = ( aClass == driver.getClass() ) ? true : false; } return result; }

这里的classloader的调用getConnection的对象的classloader,driver则是驱动注册到DriverManager的对象,这里通过classloader再次加载driver类,这里如果加载到的class和driver是一样的,当前的classloader要不是加载驱动的classloader,要不就是其子classloader,以此保持不会出现类A非类A的问题(classloader的经典问题)。

2,获取连接

for(DriverInfo aDriver : registeredDrivers) {            if(isDriverAllowed(aDriver.driver, callerCL)) {                try {                    println("    trying " + aDriver.driver.getClass().getName());                    Connection con = aDriver.driver.connect(url, info);                    if (con != null) {                        println("getConnection returning " + aDriver.driver.getClass().getName());                        return (con);                    }                } catch (SQLException ex) {                    if (reason == null) {                        reason = ex;                    }                }            } else {                println("    skipping: " + aDriver.getClass().getName());            }}

这里就是获取连接的过程,registeredDrivers就是已经注册的驱动,它是一个集合,下面是声明。

private final static CopyOnWriteArrayList
registeredDrivers = new CopyOnWriteArrayList
();

这样可以看到,只要通过检验就获取连接,获取成功就返回,这就是我们获取的connection对象。这里的一个明显问题就是集合是一个线程安全的链表,谁先被加载谁先去获取connection对象。如果按照以往的方式,这里有两个难题,一个是classloader的验证,一个是遍历的顺序。看起来只要让classloader验证正确,那么就可以保证遍历的的时候正确的loader加载正确的驱动了。很明显jdk不让你这么玩,因为这个方法是private的,这就必须保证你调用的classloader和注册的classloader必须是同一个,注册的还简单,但是调用的classloader也这样,那就必须把代码实现打包出来,这样很不利于我们管理。

以上我们看通了java的连接流程,那么我们尝试做精简,提取关键代码。

Connection con = aDriver.driver.connect(url, info);

其实获取连接就这样一句话,剩下的内容都是为了保证这句话正确执行的校验,现在我们自己来保证这个校验,这样就把关键逻辑简化成获取Driver对象,获取Connection对象,2个过程。无论怎么思考,都绕不过classloader,所以自定义classloader是肯定绕不过去了。我们借助classloader来保证加载的驱动类是正确的,这点就比较容易的实现了。

实现

首先需要一个违背双亲委托的classloader,不知道怎么写的可以查看我上一篇文章。地址:

这种classloader很好的保证了我们加载的安全性,因为地址是我们指定的,用户扔进去多个相同版本那和在was的lib目录下扔入效果一样,都不保证正常运行。简单写一个使用方式。

public static void main(String[] args) throws Exception {        // TODO Auto-generated method stub        String url ="jdbc:mysql://127.0.0.1/test";        String user="root";        String password ="";        String driverClass="com.mysql.jdbc.Driver";        File file = new File("c:/mysql-connector-java-5.1.8.jar");        MyClassloader loader = new MyClassloader(new URL[]{file.toURL()});        Driver driver = (Driver) loader.loadClass(driverClass).newInstance();        java.util.Properties info = new java.util.Properties();        if (user != null) {            info.put("user", user);        }        if (password != null) {            info.put("password", password);        }        Connection connect = driver.connect(url, info);        if(connect!=null){            System.out.println("success");        }else{            System.out.println("error");        }    }

上面的url,username等等信息都是连接数据库必要的信息,可以写死也可以指定,然后那个file路径是我指定的地址,在c盘,根据项目来设定。这里直接生成driver对象,此处就是com.mysql.jdbc.Driver,这里就体现到面向接口编程的好处了,Driver类是jdk的,只要保证路径就都能加载到,不会出现冲突。运行代码,显示success就表示你成功获取connection对象,剩下就是操作数据库语句的逻辑了。

转载地址:http://pyovx.baihongyu.com/

你可能感兴趣的文章
合并Spark社区代码的正确姿势
查看>>
【神经网络】神经网络结构在命名实体识别(NER)中的应用
查看>>
@Springboot搭建项目controller层接收json格式的对象失败
查看>>
实现网站验证码切换功能
查看>>
Atom编辑Markdown文件保存后行尾的空格自动消失的问题解决
查看>>
jquery.cookie.js 使用小结
查看>>
3.菜鸟教你一步一步开发 web service 之 axis 服务端创建
查看>>
Rabbitmq~对Vhost的配置
查看>>
HTML基础第四讲---图像
查看>>
html5--3.2 input元素(1)
查看>>
JAVA生成并导出json文件
查看>>
详谈如何定制自己的博客园皮肤【转】
查看>>
ios元素定位
查看>>
基于FPGA的异步FIFO设计
查看>>
MPMoviePlayerController属性方法简介
查看>>
带参数的宏替换
查看>>
linux内存回收机制
查看>>
智能家居系统结构标准化
查看>>
翻译:用户变量(User-Defined Variable)(已提交到MariaDB官方手册)
查看>>
016-Spring Boot JDBC
查看>>