java加载自定义驱动,自定义类加载器动态加载 JDBC 驱动

我们可以用自定义的 URLClassLoader 从外部动态加载类,并使用它。但数据库驱动的管理类 DriverManager 却不比较苛刻,不承认非当前应用系统加载器加载的驱动类。见 DriverManager 的 JavaDoc

When the method getConnection is called, the DriverManager will attempt to locate a suitable driver from amongst those loaded at initialization and those loaded explicitly using the same classloader as the current applet or application

对于有有应用自定义类加载器加载数据库驱动类的需求时,就要对原 Driver 简单包装一下。继续往后会说介绍为什么要这么做。

说明一下,DriverManager 能够根据 JDBC 连接字符串匹配到驱动类,所以一般来说都不需要显式调用 DriverManager.registerDriver() 方法。

先看 DriverManager 在应用外部驱动类时会出现什么情况

package cc.unmi;

public class JdbcDriverLoader {

public static void main(String[] args) {

notWork();

}

public static void notWork() throws Exception {

URL url = new URL("file:~/drivers/mysql-connector-java-5.1.43.jar");

String driverClass = "com.mysql.jdbc.Driver";

URLClassLoader classLoader = new URLClassLoader(new URL[] {url});

Class.forName(driverClass, true, classLoader);

DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false", "root", "");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

packagecc.unmi;

publicclassJdbcDriverLoader{

publicstaticvoidmain(String[]args){

notWork();

}

publicstaticvoidnotWork()throwsException{

URLurl=newURL("file:~/drivers/mysql-connector-java-5.1.43.jar");

StringdriverClass="com.mysql.jdbc.Driver";

URLClassLoaderclassLoader=newURLClassLoader(newURL[]{url});

Class.forName(driverClass,true,classLoader);

DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false","root","");

}

}

上面的代码在执行最后一行获取数据库连接时报出的异常是

java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/test?useSSL=false

但是能正确用自定义的类加载器加载到驱动类 com.mysql.jdbc.Driver, 否则会报出 ClassNotFound 的异常。把上面代码中的

Class.forName(driverClass, true, classLoader)

改成

Driver driver = (Driver) Class.forName(driverClass, true, classLoader).newInstance();

DriverManager.registerDriver(driver);

也无济于事。但是只要是系统加载器的数据库驱动就没问题,下面执行命令正常

java -cp ~/drivers/mysql-connector-java-5.1.43.jar cc.unmi.JdbcDriverLoader

到底发生了什么呢?还是那个  DriverManager, 进到它的 getConnection(....) 方法,有兴趣的可以去阅读 DriverManager 类的源代码,这里不细究的,简单来讲就是

在获取连接时,DriverManager 会用加载 cc.unmi.JdbcDriverLoader 类的加载器(Launcher$AppClassLoader)检验一下是否能加载到数据库驱动类,显然这里要卡壳了。com.mysql.jdbc.Driver 对于应用程序类加载器是不可能见的,所以报出驱动找不到的异常。

为了解决能动态的加载外部数据库驱动,我们需要引入下面那个 DriverShim 包装类

import java.sql.*;

class DriverShim implements Driver {

private Driver driver;

DriverShim(Driver d) {

this.driver = d;

}

public boolean acceptsURL(String u) throws SQLException {

return this.driver.acceptsURL(u);

}

public Connection connect(String u, Properties p) throws SQLException {

return this.driver.connect(u, p);

}

public int getMajorVersion() {

return this.driver.getMajorVersion();

}

public int getMinorVersion() {

return this.driver.getMinorVersion();

}

public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {

return this.driver.getPropertyInfo(u, p);

}

public boolean jdbcCompliant() {

return this.driver.jdbcCompliant();

}

public Logger getParentLogger() throws SQLFeatureNotSupportedException {

return null;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

importjava.sql.*;

classDriverShimimplementsDriver{

privateDriverdriver;

DriverShim(Driverd){

this.driver=d;

}

publicbooleanacceptsURL(Stringu)throwsSQLException{

returnthis.driver.acceptsURL(u);

}

publicConnectionconnect(Stringu,Propertiesp)throwsSQLException{

returnthis.driver.connect(u,p);

}

publicintgetMajorVersion(){

returnthis.driver.getMajorVersion();

}

publicintgetMinorVersion(){

returnthis.driver.getMinorVersion();

}

publicDriverPropertyInfo[]getPropertyInfo(Stringu,Propertiesp)throwsSQLException{

returnthis.driver.getPropertyInfo(u,p);

}

publicbooleanjdbcCompliant(){

returnthis.driver.jdbcCompliant();

}

publicLoggergetParentLogger()throwsSQLFeatureNotSupportedException{

returnnull;

}

}

然后加从外部加载驱动时应该是 DriverShim 包装类型

public static void notWork() throws Exception {

URL url = new URL("file:~/drivers/mysql-connector-java-5.1.43.jar");

String driverClass = "com.mysql.jdbc.Driver";

URLClassLoader classLoader = new URLClassLoader(new URL[] {url});

Driver driver = (Driver) Class.forName(driverClass, true, classLoader).newInstance();

DriverManager.registerDriver(new DriverShim(driver));

DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false", "root", "");

}

1

2

3

4

5

6

7

8

publicstaticvoidnotWork()throwsException{

URLurl=newURL("file:~/drivers/mysql-connector-java-5.1.43.jar");

StringdriverClass="com.mysql.jdbc.Driver";

URLClassLoaderclassLoader=newURLClassLoader(newURL[]{url});

Driverdriver=(Driver)Class.forName(driverClass,true,classLoader).newInstance();

DriverManager.registerDriver(newDriverShim(driver));

DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false","root","");

}

这样就行了,可以正确找到 "com.mysql.jdbc.Driver", 为什么如此简单的包装就把骄傲的 DriverManager 给骗了呢?就这么简单。

使用了 DriverShim 包装类后,在 getConnection() 时 DriverManager 同样要验证驱动是否对应用程序类加载器可见,只是这时候要验证的是这个 DriverShim 而非通过自定义类加载器弄进来的 "com.mysql.jdbc.Driver" 了。而后在使用 DriverShim 桥接到实际的 com.mysql.jdbc.Driver 时 DriverManager 就管不着了。

大致意思就是:你 DriverManager 不是想验证数据库驱动是否是应用程序类加载器加载的吗?给个壳逗你玩一下,我在壳里面呢,你管我是由哪个类加载器加载的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值