Jdbc为何需要反射加载驱动

看了好多反射机制的东西,感觉如果一个类的某些方法被设为私有的时候我们可以使用反射机制去访问。但是为什么jdbc驱动也都是使用反射机制去访问,很费解

我想题主想问的是如下代码为何要使用反射:

1
2
3
4
5
6
try {
Class.forName("com.mysql.jdbc.Driver") ;
} catch(ClassNotFoundException e) {
System.out.println("找不到驱动程序类 ,加载驱动失败!");
return; // or do something else
}

多年前写JDBC时年少无知,没有想过这个问题,今天题主提出来,觉得这个问题很好。

基本原因

首先,上面一段代码的主要作用,是在运行期以反射的方式来检查JDBC驱动的主类com.mysql.jdbc.Driver是否存在,若不存则表示运行环境中没有这个驱动,进入catch段。如果你确定一定以及肯定它会存在,可以直接写成

1
import com.mysql.jdbc.Driver;

效果基本是一样的(只是在编译期及运行期要都保证此类存在classpath中)

所以,以反射形式加载的一个好处是当驱动jar包不存在时,我们可以做更多的操作。(要知道,在很久很久以前,jdbc驱动一般都是放在运行环境的classpath中的,如tomcat/lib

另一个原因

如果你是一个有追求的程序员,那么另外一个很重要的原因是解耦。

首先要明白JDBC是Java的一种规范,通俗一点说就是JDK在java.sql.*下提供了一系列的接口(interface),但没有提供任何实现(也就是类)。 所以任何人都可以在接口规范之下写自己的JDBC实现(如MySQL)。而若调用者也只调用接口上的方法(如我们),那么当未来有任何变更需要时(例如要从MySQL迁移至Oracle),则理论上不需要对代码做任何修改就能直接切换(可惜SQL语法没能统一规范)

这意味着什么?意味着你的代码中不应该引用任何与实现相关的东西,你的代码只知道java.sql.*,而不应该知道com.mysql.*或是com.oracle.*,以避免或减少未来切换数据源时对代码的变更。

注意,我们使用的所有其他API包括Connection/Statement/ResultSet等都是java.sql.*的东西,甚至com.mysql.jdbc.Driver类也是:

1
2
3
4
package com.mysql.jdbc;
public class Driver ... implements java.sql.Driver {
...
}

因此,直接import com.mysql.jdbc.Driver;违反了开闭原则(OCP,对扩展开放,对修改关闭)。(有人说我用反射也必须要修改代码呀,事实上你可以将类名字符串存储至.properties文件,和数据库用户名密码放在一起,就像Hibernate做的那样)

引申问题

如果我可以保证JDBC驱动一定在classpath下,是不是可以不写这段反射代码,也不引用任何的Driver类?答案是否定的,请看下面这段代码源自com.mysql.jdbc.Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.mysql.jdbc;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
...
}

static代码块会在类加载时就被执行——也就是当我们执行Class.forName("com.mysql.jdbc.Driver")时(或import com.mysql.jdbc.Driver

转自:https://segmentfault.com/q/1010000000315618

lemon wechat
欢迎大家关注我的订阅号 SeeMoonUp
写的不错?鼓励一下?不差钱?