【从加载数据库驱动包,理解java SPI】

SPI(Service Provider Interface)

从1.6引入,基于ClassLoader 来加载并发现服务的机制

对于msyql驱动

引入依赖

    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency>

在mysql:mysql-connector-java:8.0.27.jar中,可以找到META-INF/services/java.sql.Driver文件:

com.mysql.cj.jdbc.Driver

找到com.mysql.cj.jdbc.Driver源码:

package com.mysql.cj.jdbc;import java.sql.DriverManager;
import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}
  1. 它实现了java.sql.Driver
  2. 静态代码块中,向DriverManager注册了自己。

对于oracle驱动

引入依赖

<dependency><groupId>com.hynnet</groupId><artifactId>oracle-driver-ojdbc6</artifactId><version>12.1.0.1</version>
</dependency>

在com.hynnet:oracle-driver-ojdbc6-12.1.0.1.jar中,可以找到META-INF/services/java.sql.Driver文件:

oracle.jdbc.OracleDriver

找到oracle.jdbc.OracleDriver源码:

public class OracleDriver implements Driver {//static {try {if (defaultDriver == null) {defaultDriver = new oracle.jdbc.OracleDriver();DriverManager.registerDriver(defaultDriver);}} catch  // ... }
}
  1. 它实现了java.sql.Driver
  2. 静态代码块中,向DriverManager注册了自己。

从DriverManager跟踪源码

对于 DriverManager:
public class DriverManager {static {loadInitialDrivers();println("JDBC DriverManager initialized");}private static void loadInitialDrivers() {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();while(driversIterator.hasNext()) { // 断点进到这里driversIterator.next();}}});// 最后还是根据拿到了从META-INF/services/java.sql.Driver里的Driver,如com.mysql.cj.jdbc.DriverClass.forName(aDriver, true, ClassLoader.getSystemClassLoader());}
}
在ServiceLoader里:
public final class ServiceLoader<S> implements Iterable<S> {private static final String PREFIX = "META-INF/services/";public boolean hasNext() { // 进入driversIterator.hasNext() 后追到这里if (acc == null) {return hasNextService();}}    private boolean hasNextService() {String fullName = PREFIX + service.getName();configs = ClassLoader.getSystemResources(fullName); // 断点没有进configs = loader.getResources(fullName); // 断点进到这里}
}

是在某个时机,触发DriverManager静态代码块,且只会执行这一次,紧接着一系列调用:
–> loadInitialDrivers()
–> ServiceLoader.load()、driversIterator.hasNext()
–> hasNextService()
–> loader.getResources(fullName)
使用classLoader.getResources() 加载所有包下"META-INF/services/java.sql.Driver"文件

获取连接测试

当使用Class.forName时
Class.forName("com.mysql.cj.jdbc.Driver"); // 断点由此进入DriverManager静态代码块断点
Connection conn = DriverManager.getConnection("***", "root", "123456");

Class.forName() 中有一个本地方法

 private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;

经过这个本地方法后,断点进入DriverManager静态代码块断点,本地方法里做了啥? 如何会触发DriverManager的类加载,不知道。。

但DriverManager静态代码确实只会执行一次,即便再次使用 Class.forName(“com.mysql.cj.jdbc.Driver”)时, 不会进入DriverManager静态代码块断点了

当不使用Class.forName,而直接DriverManager.getConnection时
Connection conn = DriverManager.getConnection("***", "root", "123456"); // 断点由此进入DriverManager静态代码块断点

getConnection() 是静态方法,会触发执行DriverManager静态代码块,接着也是一系列的调用,加载所有包下"META-INF/services/java.sql.Driver"文件。

总结

通过 DriverManager、ServiceLoader,在第一次获取连接前,总会去执行一次loader.getResources(fullName),加载所有包下"META-INF/services/java.sql.Driver"文件中指定的类。

过程都由DriverManager、ServiceLoader设计好,获取驱动包名的目录“META-INF/services”也有ServiceLoader定义为常量。

所以实现一个驱动,需要按照约定,在META-INF/services/java.sql.Driver中准备好驱动所在的全
限定名。便会有ClassLoader来发现和加载相应驱动。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/20734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11.2 选择排序

目录 11.2 选择排序 11.2.1 算法特性 11.2 选择排序 选择排序&#xff08;selection sort&#xff09;的工作原理非常简单&#xff1a;开启一个循环&#xff0c;每轮从未排序区间选择最小的元素&#xff0c;将其放到已排序区间的末尾。 设数组的长度为 &#x1d45b;…

华东师范大学研究团队《Ecology Letters 》揭示植物如何改变其物候以响应全球变化

自工业革命以来&#xff0c;人类活动导致多种环境因子同时发生变化&#xff0c;包括气候变暖、降水模式改变、氮沉降增加和大气CO2升高。这些变化预计会影响植物生命周期事件的季节时序—植物物候&#xff08;Nature Reviews Earth & Environment | 傅伯杰院士团队发文阐述…

[C][栈帧]详细讲解

目录 1.栈帧1.进程地址空间2.栈帧说明 2.认识相关寄存器3.认识相关汇编命令4.过程理解5.栈帧总结6.补充 1.栈帧 1.进程地址空间 .进程地址空间 2.栈帧说明 调用函数&#xff0c;形成栈帧函数返回&#xff0c;释放栈帧局部变量是存放在栈区上的栈区内存的使用习惯是&#xff…

BPTT算法详解:深入探究循环神经网络(RNN)中的梯度计算【原理理解】

引言 在深度学习领域中&#xff0c;我们经常处理的是独立同分布&#xff08;i.i.d&#xff09;的数据&#xff0c;比如图像分类、文本生成等任务&#xff0c;其中每个样本之间相互独立。然而&#xff0c;在现实生活中&#xff0c;许多数据具有时序结构&#xff0c;例如语言模型…

什么是PLAB?

接上文PLAB---》 可以看到和TLAB很像&#xff0c;PLAB即 Promotion Local Allocation Buffers。用在年轻代对象晋升到老年代时。 在多线程并行执行YGC时&#xff0c;可能有很多对象需要晋升到老年代&#xff0c;此时老年代的指针就"热"起来了&#xff0c;于是搞了个…

Google Cloudbuild yaml file 中 entrypoint 和 args 的写法

编写cloudbuild.yaml 时有几个关键参数 entrypoint 和 args 的基本介绍 id: 显示在 cloud build logs 里的item 名字 name: docker 镜像名字 - 下面的命令会在这个镜像的1个容器instance 内执行 entrypoint: 执行的命令入口 &#xff0c; 只能有1个对象 args&#xff1a; 命名…

函数的创建和调用

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 提到函数&#xff0c;大家会想到数学函数吧&#xff0c;函数是数学最重要的一个模块&#xff0c;贯穿整个数学学习过程。在Python中&#xff0c;函数…

深入解析 YOLOv8 中的 `conv.py`(代码图文全解析-下)

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《yolov8》系列专栏&…

【linux软件基础知识】与调度相关的进程描述符

进程描述符 每个进程描述符都包括几个与调度相关的字段,如下代码所示: //include/asm-arm/thread_info.h /** low level task data that entry.S needs immediate access to.* __switch_to() assumes cpu_context follows immediately after cpu_domain.*/ struct thread_in…

vite为什么速度快

原因 vite快的原因是因为 vite在开发环境中是使用的 esbuild&#xff0c;esbuild 是 go 写的&#xff0c;go 编译型语言、多线程&#xff0c;nodejs 解释型语言、单线程&#xff0c;并且 vite 使用了原生 esm 导入的&#xff0c;所以快一点&#xff0c;当然&#xff0c;这也…

6.1Java方法

1、方法定义&#xff1a; 方法是一种语法结构&#xff0c;它可以把一段代码封装成一个功能&#xff0c;以便重复调用 方法的完整格式&#xff1a; 修饰符 返回类型 方法名(形参列表){ 方法体代码(需要执行的功能代码) return 返回值; } package com.define;public class …

【缓存】框架层常见问题和对策

缓存是为了加快读写速度&#xff0c;再了解redis这类框架层的缓存应用之前&#xff0c;我们不妨先思考下操作系统层面的缓存解决方案&#xff0c;这样有助于我们更深的理解缓存&#xff0c;哪些是系统层面的&#xff0c;哪些是服务层面。 以下是一些常见的缓存问题及其解决方案…

面向对象编程 (OOP):深入理解继承、多态和抽象

1. 简介 面向对象编程 (OOP) 是一种强大的编程范式&#xff0c;它通过将程序组织成对象的集合来简化软件设计和开发。与传统的程序设计方法相比&#xff0c;OOP 提供了一种更自然、更易于理解和维护的方式来构建复杂的软件系统。OOP 的核心概念包括&#xff1a;对象、类、继承、…

Java进阶学习笔记31——日期时间

Date&#xff1a; 代表的是日期和时间。 分配Date对象并初始化它以表示自标准基准时间&#xff08;称为纪元&#xff09;以来的指定毫秒数&#xff0c;即1970年1月1日00:00:00。 有参构造器。 package cn.ensource.d3_time;import java.util.Date;public class Test1Date {pu…

linux C/C++静态库制作

概念&#xff1a;程序在编译时会把库文件的二进制代码链接到目标程序中&#xff0c;这种方式称为静态链接。 如果多个程序中用到了同一静态库中的函数或类&#xff0c;就会存在多份拷贝。 特点&#xff1a; 静态库的链接是在编译时期完成的&#xff0c;执行的时候代码加载速度…

Java—异常处理

异常的结构图 异常知识点 异常分类&#xff1a; 按照在程序编译阶段是否被检查&#xff0c;异常分为编译时异常&#xff08;Checked Exception&#xff09;和运行时异常&#xff08;Unchecked Exception&#xff09;。编译时异常是指必须进行显式处理的异常&#xff0c;例如IOE…

【Linux】写一个日志类

文章目录 1. 源代码2. 函数功能概览3. 代码详细解释3.1 头文件和宏定义3.2 Log类定义3.3 打印日志的方法3.4 操作符重载和析构函数3.5 可变参数函数的原理 4. 测试用例 1. 源代码 下面代码定义了一个 Log 类&#xff0c;用于记录日志信息。这个类支持将日志信息输出到屏幕、单…

Java扩展机制:SPI与Spring.factories详解

一、SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 整体机制图如下: Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。 系统设计的各个抽象,往往有很多不…

戴尔科技:一盆冷水浇醒了AIPC

这年头&#xff0c;只要沾上英伟达的公司&#xff0c;不论美股还是大A,都跟着鸡犬升天几轮过&#xff0c;但昨晚英伟达蒸发1064亿美元&#xff0c; 跟着遭罪的也不少&#xff0c;有没有一夜惊魂梦醒的感觉&#xff1f; 今天我们来说说——戴尔科技。 昨晚戴尔科技大跌5.18%&a…

5G无线标准演进综述及新技术引入

摘 要 随着经济和社会的发展&#xff0c;5G业务越来越丰富多彩&#xff0c;1080P高清视频、裸眼3D、网联汽车、云手机等新业务、新终端对网络的要求也越来越高&#xff1b;另一方面&#xff0c;5G标准持续演进&#xff0c;在MIMO、载波聚合、移动性管理、uRLLC、切片、定位等方…