栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SPI机制 JDBC中的应用 驱动类加载

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SPI机制 JDBC中的应用 驱动类加载

SPI机制 JDBC中的应用 驱动类加载

SPI(Service Provider Interface)机制

SPI简单示例 SPI之DriverManager

1.原始方式2.SPI方式

SPI(Service Provider Interface)机制

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。
Java中SPI机制主要思想是将装配的控制权移到程序之外(这里的意思是开发人员不需要显示的加载接口的对应实现,而是交由SPI机制来加载),在模块化设计中这个机制尤其重要,其核心思想就是 解耦
如下图:

当服务的提供者提供了一种接口的实现之后,需要在classpath下的meta-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的meta-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。

SPI简单示例

我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索。
1.先定义接口

public interface Search {
    public List searchDoc(String keyword);   
}

2.文件搜索实现

public class FileSearch implements Search{
    @Override
    public List searchDoc(String keyword) {
        System.out.println("文件搜索 "+keyword);
        return null;
    }
}

3.数据库搜索实现

public class DatabaseSearch implements Search{
    @Override
    public List searchDoc(String keyword) {
        System.out.println("数据搜索 "+keyword);
        return null;
    }
}

3.resources 接下来可以在resources下新建meta-INF/services/目录,然后创建名字为接口全限定类名的文件,里面加上我们需要用到的实现类(可以包含多个):

4.进行测试

ServiceLoader load = ServiceLoader.load(Search.class);
Iterator iterator = load.iterator();
while (iterator.hasNext()){
      Search search = iterator.next();
      search.searchDoc("hello word");
}

测试结果:

数据搜索 hello word
文件搜索 hello word

这就是spi的思想,接口的实现由provider实现,provider只用在提交的jar包里的meta-INF/services下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。

SPI之DriverManager 1.原始方式

首先,大家是否熟悉如下这段代码?

//1.加载MySQl驱动
Class.forName("com.mysql.jdbc.Driver");
//2.指明url
String url = "jdbc:mysql://localhost:3306/databasename";
//3.通过DriverManager获取connection
Connection conn = DriverManager.getConnection(url, username, password);

当时在学习这一段的时候有无好奇过,为什么第一步加载了驱动类,第三步就可以建立一个MySQL的连接。从代码上看他们直接并没有直接的联系。但是当我们打开MySQL驱动类时可以看到如下一段代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager  将自己注册到DriverManager中
    //
    // 可以出这是一个静态代码块,我们都知道,当使用到该类的时候,一定会执行类的静态代码块!
    static {
        try {
            //这句话至关重要,他调用了DriverManager的registerDriver()方法,
            //把自己注册到了DriverManager中,从这里看是不是豁然开朗了,执行第一步的
            //Class.froname("XXX")时,会执行这个静态代码块,而这个静态代码块,将自己
            //给了DriverManager;
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

接下来看一下java.sql.DriverManager.registerDriver(new Driver());的具体逻辑:

public static synchronized void registerDriver(java.sql.Driver driver,
       DriverAction da)
    throws SQLException {
    
    if(driver != null) {
        //private final static CopyOnWriteArrayList registeredDrivers 
        //= new CopyOnWriteArrayList<>();
        // 从这一句可以看到,他把driver封装成一个 DriverInfo  放到了一个静态的线程安全的数组中 
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);

}

最后看一下DriverManager.getConnection(url, username, password);的具体过程,程序遍历上一步中的集合,进行连接的建立,当建立成功,就返回。

for(DriverInfo aDriver : registeredDrivers) {
	// If the caller does not have permission to load the driver then
	// skip it.
	if(isDriverAllowed(aDriver.driver, callerCL)) {
		try {
			println("    trying " + aDriver.driver.getClass().getName());
			Connection con = aDriver.driver.connect(url, info);
			if (con != null) {
				// Success!
				println("getConnection returning " + aDriver.driver.getClass().getName());
				return (con);
			}
		} catch (SQLException ex) {
			if (reason == null) {
				reason = ex;
		}
 }
2.SPI方式

在引入数据库驱动的情况下,使用如下代码也可以正常获取连接:

//2.指明url
String url = "jdbc:mysql://localhost:3306/databasename";
//3.通过DriverManager获取connection
Connection conn = DriverManager.getConnection(url, username, password);

从上面的代码中可以明显看出,少了第一步中的类加载,也就是说肯定有逻辑帮我们实现了类加载的过程,而这个过程就是由SPI机制中的服务查找实现的!打开DriverManager类,可以看到如下一段静态代码块:

static {
	//这句话很重还要,在我们直接使用DriverManager,该静态代码块会被执行
	loadInitialDrivers();
	println("JDBC DriverManager initialized");
}

==========================================================》
//下面是loadInitialDrivers()方法中较为重要的方法

private static void loadInitialDrivers() {
	//这一步返回了new ServiceLoader<>(service, loader);对象
	//类中有个静态属性private static final String PREFIX = "meta-INF/services/";
	ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
	//这一步返回了一个迭代器
	Iterator driversIterator = loadedDrivers.iterator();

	try{
		while(driversIterator.hasNext()) {
			//参照下面
			driversIterator.next();
			}
		} catch(Throwable t) {
			// Do nothing
		}
			return null;
	} 
===========================================================》
// driversIterator.next();
c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());

从上面可以看到,SPI机制最终通过扫描meta-INF/services/最终同样执行了Class.forName(XXX);,这样就与前面的方法接上了。

参考文章

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/782162.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号