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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 问题场景现在很多工程为了功能扩展,都给出了插件化的方式。只需要用户配置好配置文件,提供好需要的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 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对象,剩下就是操作数据库语句的逻辑了。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
7月前
|
Java
[JarEditor]可直接修改jar包的IDEA插件
### 修改JAR包变得更简单:JarEditor插件简介 **背景:** 开发中常需修改JAR包中的class文件,传统方法耗时费力。JarEditor插件让你一键编辑JAR包内文件,无需解压。 **插件使用:** 1. **安装:** 在IDEA插件市场搜索JarEditor并安装。 2. **修改class:** 打开JAR文件中的class,直接编辑,保存后一键构建更新JAR。 3. **文件管理:** 右键菜单支持在JAR内新增/删除/重命名文件等操作。 4. **搜索:** 使用内置搜索功能快速定位JAR包内的字符串。
645 2
[JarEditor]可直接修改jar包的IDEA插件
|
6月前
|
数据库连接 数据库
实现加载驱动、得到数据库对象、关闭资源的代码复用,将代码提取到相应的工具包里边。优化程序
该博客文章展示了如何通过创建工具类`Connectiontools`实现数据库连接、语句执行以及资源关闭的代码复用,以优化程序并提高数据库操作的效率和安全性。
|
7月前
|
弹性计算 Java Serverless
Serverless 应用引擎操作报错合集之上传自定义JAR包,启动时报错,是什么导致的
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
7月前
|
关系型数据库 MySQL 分布式数据库
PolarDB产品使用问题之使用polardb for mysql数据库的外网地址在程序中连接经常超时,如何解决
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
7月前
|
Java Linux Shell
Linux后台运行jar程序
【7月更文挑战第23天】
136 1
|
7月前
|
关系型数据库 Java 分布式数据库
PolarDB产品使用问题之部署到服务器上的Java应用(以jar包形式运行)无法连接,如何解决
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
6月前
|
Java Maven Windows
java -jar 启动 boot 程序 no main manifest attribute, in .\vipsoft-model-web-0.0.1-SNAPSHOT.jar
java -jar 启动 boot 程序 no main manifest attribute, in .\vipsoft-model-web-0.0.1-SNAPSHOT.jar
85 0
|
7月前
|
Java 应用服务中间件
tomcat7 与 tomcat8 加载 jar包的顺序
tomcat7 与 tomcat8 加载 jar包的顺序
239 0
|
7月前
|
Java 应用服务中间件 API
java 启动查看jar包加载顺序并设置classpath
java 启动查看jar包加载顺序并设置classpath
469 0