使用URLClassLoader加载SPI类(如JDBC驱动)出现ClassNotFoundException求助
问题根因
DriverManager 内部的 getDrivers()、getConnection() 方法自带类加载器校验逻辑:只有调用方的类加载器可以加载到对应驱动实现类时,该驱动才会被对外暴露返回。
DriverManager 属于 JDK 核心类,由启动类加载器加载,默认的类加载上下文是系统类加载器,而你通过自定义类加载器加载的 com.mysql.cj.jdbc.Driver 不在系统类加载器的扫描路径下,自然无法通过校验,所以哪怕驱动已经存入了 registeredDrivers 内部列表,也不会对外返回。
解决方案
你可以根据框架设计选择以下任意一种可行方案:
方案1:绕过 DriverManager 直接用驱动实例创建连接
该方案实现最简单,完全规避了 DriverManager 的类加载校验逻辑,也是 Flink、Tomcat 这类插件化框架处理 JDBC 驱动的常用方案:
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader); java.sql.Driver driver = (java.sql.Driver) clazz.getConstructor().newInstance(); Properties connProps = new Properties(); connProps.put("user", this.userName); connProps.put("password", this.password); Connection connection = driver.connect(this.hostName, connProps);
方案2:操作 DriverManager 前后临时切换线程上下文类加载器
在注册驱动、获取连接的逻辑执行前后,临时将当前线程的上下文类加载器替换为加载驱动的 pluginClassloader,执行完后恢复原类加载器即可:
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(pluginClassloader); // 注册驱动 Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader); java.sql.Driver driver = (java.sql.Driver) clazz.getConstructor().newInstance(); DriverManager.registerDriver(driver); // 获取连接 Connection connection = DriverManager.getConnection(this.hostName, this.userName, this.password); } finally { // 务必恢复原类加载器,避免影响框架其他逻辑 Thread.currentThread().setContextClassLoader(originalClassLoader); }
注意该方案仅在注册、获取连接的逻辑都被类加载器切换的代码包裹时才生效,如果其他业务逻辑需要使用该驱动,也需要做同样的上下文类加载器切换。
内容的提问来源于stack exchange,提问作者Alceatraz
相关产品推荐
相关产品推荐

