一开始学习JDBC的时候到现在一直认为连接数据库之前首先需要
Class.forName("com.mysql.jdbc.Driver");
直到某一天至少是在写这篇博客之前。为什么注释掉了还是可以连接数据库?这个问题先等一等看。
先回忆下实例化对象的几种方式
//1.直接new
Hello hello = new Hello();
hello.say();
//2.Class.forName.newInstance
Hello hello2 = (Hello) Class.forName("com.mySpi.Hello").newInstance();
hello2.say();
//3.需实现Serializable, 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。
继续我们问题,既然不需要显示加载,那肯定是有其他的类替我们加载了,首先看DriverManager,因为除了main方法所在的类,最先调的只有他了,再找下该类下面有没有静态代码块,因为它是最先被jvm加载的,然后我们会发现
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
然后执行loadInitialDrivers();代码如下
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it. 如果驱动是作为服务提供,就去加载
// Get all the drivers through the classloader 通过classloader 获取所有的驱动
// exposed as a java.sql.Driver.class service. Driver接口的有实现类作为服务
// ServiceLoader.load() replaces the sun.misc.Providers() 加载实现类
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
在loadInitialDrivers()方法中,我们发现通过ServiceLoader机制加载java.sql.Driver接口的实现类,然后对所有实现类进行遍历,来完成了驱动类的加载。
那么问题来了,jdk是如何知道java.sql.Driver有哪些实现类的呢?
主角登场:
SPI,官方话是JDK内置的一种服务提供发现机制。比如有一个接口,想在运行时动态地给它添加实现,只需要按照SPI的规矩来办,那么SPI机制在程序运行时就会发现该实现类;规则如下:
在classpath下META-INF/services 新建一以接口名(如java.sql.Driver)为名称的文件,文件内容是接口实现类(如mysql驱动包下META-INF/services/java.sql.Driver里的com.mysql.jdbc.Driver),mysql-connect--的示例文件如下
![](https://img2020.cnblogs.com/blog/2222655/202011/2222655-20201128183938413-173937018.png)
![](https://img2020.cnblogs.com/blog/2222655/202011/2222655-20201128190524735-164845821.png)
所以不用显示Class.forName的原因是:核心ServiceLoader类提供了一个静态的load()方法,用于加载指定接口的所有实现类。调用该方法后,classpath下META-INF/services目录的java.sql.Driver文件中指定的所有实现类都会被加载。
现在我们知道spi机制的核心方法,可以写一个demo 实现下这种情况
首先新建SayHello接口,然后新建它的两个实现类Hello,Hi,代码如下
public interface SayHello {
void say();
}
public class Hello implements SayHello{
@Override
public void say() {
System.out.println("hello world--hello");
}
}
public class Hi implements SayHello {
@Override
public void say() {
System.out.println("hello world --hi");
}
}
首先在resource文件下新建META-INF文件夹,然后META-INF下新建services文件夹,该文件夹下新建com.mySpi.SayHello文件,内容如下
下面我们写一个main 方法测试是否可以加载实现类,如果已经加载,就可以打印
public static void main(String[] args) {
ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class);
Iterator<SayHello> driversIterator = sayHellos.iterator();
while (driversIterator.hasNext()){
SayHello hello=driversIterator.next();
hello.say();
}
}
如果com.mySpi.SayHello里内容,那么将无法打印hello-world,SPI简单入门就到这里了。
--本文作者:【ngLee 】
--关于博文:如果有错误的地方,还请留言指正。如转载请注明出处!如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!