当前位置: 首页 > news >正文

classloader实战:一个程序使用相同数据库的两个不同版本的jar包

问题场景

现在很多工程为了功能扩展,都给出了插件化的方式。只需要用户配置好配置文件,提供好需要的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<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

这样可以看到,只要通过检验就获取连接,获取成功就返回,这就是我们获取的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,不知道怎么写的可以查看我上一篇文章。地址:https://my.oschina.net/xpbob/blog/761436

这种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对象,剩下就是操作数据库语句的逻辑了。

相关文章:

  • 卷积核与特征提取
  • 常用的几个vagrant命令
  • SQL优化笔记
  • 【总结整理】关于二手交易平台的讨论
  • jdk1.8 HashMap源码分析(resize函数)
  • (转)使用VMware vSphere标准交换机设置网络连接
  • 阿里云服务反射攻击,解决办法
  • MySQL运维进阶-MySQL双主(master-master)+半同步(Semisync Repl
  • 细说setTimeout/setImmediate/process.nextTick的区别
  • ORA-28040: No matching authentication protocol
  • chmod-chown-umask-lsattr-chattr
  • java实现图片转ascii字符画
  • [CF494C]Helping People
  • oracle自带函数
  • 菜鸟要投120亿港币,在香港建超级eHub
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • 0x05 Python数据分析,Anaconda八斩刀
  • Android Volley源码解析
  • C++类的相互关联
  • ES6 学习笔记(一)let,const和解构赋值
  • Fabric架构演变之路
  • HTTP请求重发
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • Unix命令
  • vue--为什么data属性必须是一个函数
  • 初探 Vue 生命周期和钩子函数
  • 关于springcloud Gateway中的限流
  • 简析gRPC client 连接管理
  • 鱼骨图 - 如何绘制?
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (二)fiber的基本认识
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转) Android中ViewStub组件使用
  • (转)ORM
  • (转载)从 Java 代码到 Java 堆
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net refrector
  • .net 流——流的类型体系简单介绍
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • @media screen 针对不同移动设备
  • @ModelAttribute 注解
  • @property python知乎_Python3基础之:property
  • [Android]How to use FFmpeg to decode Android f...
  • [BZOJ] 2427: [HAOI2010]软件安装
  • [C#] 我的log4net使用手册
  • [EWS]查找 文件夹