【Web】浅聊JDBC的SPI机制是怎么实现的——DriverManager

news2024/10/7 18:28:01

目录

前言

分析


前言

【Web】浅浅地聊JDBC java.sql.Driver的SPI后门-CSDN博客

上篇文章我们做到了知其然,知道了JDBC有SPI机制,并且可以利用其Driver后门

这篇文章希望可以做到知其所以然,对JDBC的SPI机制的来源做到心里有数

分析

先是回顾一下JDBC的代码

package com.spi;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Jdbc_Demo {
    public static void main(String[] args) throws Exception {
        // 注册驱动(可以删去)
//        Class.forName("java.sql.Driver");
        // 获取数据库连接对象
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/security","root", "root");
        //  定义sql语句
        String sql = "select * from users";
        // 获取执行sql的对象Statement
        Statement stmt = con.createStatement();
        // 执行sql
        ResultSet res = stmt.executeQuery(sql);
        // 处理对象
        while(res.next()) {
            System.out.println(res.getString("username"));
        }
        // 释放资源
        res.close();
        stmt.close();
        con.close();
    }
}

我们可以看到

 Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/security","root", "root");

因此JVM会尝试去加载DriverManager类,进而执行DriverManager的静态代码,调用类中的loadInitialDrivers方法(稍微精简了一下代码)

static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers"); // 也可以通过设置系统属性来加载驱动
                }
            });
        } //...
        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();
                    }
            }
        // ....
        String[] driversList = drivers.split(":");
        for (String aDriver : driversList) {
            try {
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } // ...
        }
}

 重点关注ServiceLoader.load(Driver.class)进行了类加载

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

 跟进ServiceLoader.load(service, cl);

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

service是Driver.class,loader是 Thread.currentThread().getContextClassLoader()

这里其实还有双亲委派机制的打破可以讲,但强行塞有点过于臃肿了,略去

 跟进ServiceLoader<>(service, loader);

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

跟进reload();

public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

最后得到的driversIterator就是LazyIterator

OK我们再看下上面代码的这段局部

 while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }

在这段代码中调用 driversIterator.next()之时,便是调用LazyIterator#next

 public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

跟进LazyIterator#nextService

 private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } 

这段代码的作用是根据存储的类名动态加载一个类(Class.forName)

要注意一点:将 initialize 参数设置为 false,可以实现延迟类的静态初始化,静态初始化块和静态变量的赋值是在类第一次被加载时执行的,如果将 initialize 参数设置为 false,则这些静态操作将被延迟到后续使用该类的过程中才被执行

类名是哪来的?张口就来吗?我知道你很急,但你先别急,下面就讲。

 String cn = nextName;

看到nextName直接就懂了哇,回头看driversIterator.hasNext(),即LazyIterator#hasNext

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

跟进LazyIterator#hasNextService

 private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

ServiceLoader<S>类有常量属性PREFIX = "META-INF/services/" 

service是传入的Driver.class,service.getName()获取Driver接口全类名,拼接得到SPI文件名,给后续读取得到实现类的类名,最后赋值给nextName,再交给LazyIterator#nextService去进行类的动态加载

while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;

上面这段话,白话来讲就是,JVM去META-INF/services/下去找Driver接口名的文件,把文件中的内容读出来,也就是我们所要加载的类名,并交由Class.forName来进行动态类的加载。

至此,SPI大成!

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

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

相关文章

开源玩具总动员-本博客的知识关系图

作为一个非全职编程爱好者&#xff0c;基本是把计算机周边当做高档大玩具来玩的&#xff0c;顺便带着有兴趣的学生搞一搞学习。这篇文章作为全站的一个导航篇&#xff0c;把本博客的主干要点汇聚一下。从小学开始一直与计算机结缘。通过各种业余时间&#xff0c;慢慢地把感兴趣…

一篇了解电容的使用

目录 一、电容理论基础 1.电容的本质 2.电容量的大小 &#xff08;1&#xff09;电容的单位 &#xff08;2&#xff09;电容量的决定式 3.电容的特点 4.电容的串并联 5.电容器的类型 6.电容实际的电路模型 二、电容器的选型 1.安装方式 2.电容值 3.电容的类型 4…

备战蓝桥杯————二分搜索(一)

引言 一、二分查找 基本概念 代码框架 二、二分查找 题目描述 解题思路及代码 结果展示 三、寻找左侧边界的二分搜索 使用背景 基本代码 引言 在计算机科学的世界里&#xff0c;二分查找算法无疑是一种经典且强大的工具。它以其高效的性能&#xff0c;在有序数据集中…

Java毕业设计 基于SpringBoot 众筹网

Java毕业设计 基于SpringBoot 众筹网 SpringBoot 众筹网 功能介绍 注册 邮箱验证码 登录 忘记密码 首页 图片轮播 关于我们 项目列表 发布项目 我的添加项目 提交审核 已在募捐 项目详情 项目介绍 项目进展 捐赠列表 评论 新闻列表 发布新闻 新闻详情 评论新闻 联系我们 提交…

Android开发工程师面试题,2024年Android开发陷入饱和

前言 马上快到金三银四都春招阶段了&#xff0c;在这本就是跳槽、找工作的年后黄金时间&#xff0c;大多数求职者都早早做好年后求职的准备&#xff0c;其中不乏有年前早早辞了工作准备年后跳槽的有经验的职场老人们&#xff0c;也有一批即将毕业的应届毕业生的职场新人们。 …

redis使用笔记

redis使用笔记 1、Redis简介1.1 含义1.2 功能1.3 特点 2. 常用的数据结构2.1 HASH 3 redis接口定义3.1 redisReply3.2 redisContext3.3 redisCommand 4 实践操作4.1 遇到问题4.1.1 Get哈希的时候返回error4.1.2 长度一直为0&#xff0c;str没法打印&#xff08;未解决&#xff…

合泰HT66F2390----定时器中断学习笔记

前言 无需多言 直接开始定时器中断 的学习 通过上次的PWM学习&#xff0c;上次用的是周期型TM定时器模块 这次使用标准型TM定时器模块&#xff08;STM&#xff09; 代码 #include <HT66F2390.h>void Timer0_Init(void){_stm0c0 0b00001000;_stm0c1 0b11000001;_stm…

Android岗面试,android内存优化面试题

前言 曾听过很多人说Android学习很简单&#xff0c;做个App就上手了&#xff0c;工作机会多&#xff0c;毕业后也比较容易找工作。这种观点可能是很多Android开发者最开始入行的原因之一。 在工作初期&#xff0c;工作主要是按照业务需求实现App页面的功能&#xff0c;按照设…

22万字大模型面经整理+答案

槽位对齐&#xff08;slot alignment&#xff09; 在text2sql任务中&#xff0c;槽位对齐&#xff08;slot alignment&#xff09;通常指的是将自然语言问题中的关键信息&#xff08;槽位&#xff09;与数据库中的列名或API调用中的参数进行匹配的过程。这个过程中&#xff0c…

PTA L2-001 紧急救援

作为一个城市的应急救援队伍的负责人&#xff0c;你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候&#xff0c;你的任务是带领你的…

leetcode 热题 100_最小覆盖子串

题解一&#xff1a; 双指针滑动窗口&#xff1a;暴力解法——用双指针来表示字符串s中的子串首尾&#xff0c;遍历所有子串并与字符串t判断是否符合条件。我们可以对遍历和判断的过程进行优化&#xff0c;首先是遍历&#xff0c;右指针先移动直到涵盖所以需要的字母&#xff0c…

大数据技术学习笔记(五)—— MapReduce(2)

目录 1 MapReduce 的数据流1.1 数据流走向1.2 InputFormat 数据输入1.2.1 FileInputFormat 切片源码、机制1.2.2 TextInputFormat 读数据源码、机制1.2.3 CombineTextInputFormat 切片机制 1.3 OutputFormat 数据输出1.3.1 OutputFormat 实现类1.3.2 自定义 OutputFormat 2 Map…

【Software Platform Bundle】

https://www.ni.com/zh-cn/support/downloads/software-products/download.software-platform-bundle.html

蓝桥杯倒计时 38 天

整数二分模板&#xff1a;数的范围 二分的本质不是单调性&#xff0c;而是二分出能满足某种性质使得将整数分成两半。 思考&#xff1a;模板题&#xff0c;模板记熟就能做 #include<iostream> using namespace std; int n,q; const int N 1e510; int a[N]; int main…

大海捞针:用代码聚类寻找恶意 PyPI 包(ASE 2023)

A Needle is an Outlier in a Haystack: Hunting Malicious PyPI Packages with Code Clustering Institute of Software, Chinese Academy of Sciences, Beijing, China;University of Chinese Academy of Sciences, Beijing, China Abstract 作为最流行的Python软件存储库&…

Android开发必须会的技能,记得把每一次面试当做经验积累

前言 本来已经在为去大厂工作摩拳擦掌的Android开发者们&#xff0c;今年显得格外艰难&#xff1a; 待就业数高达874万&#xff01;人才竞争加剧&#xff01;疫情让大多数公司的招聘需求缩减&#xff01;人才招聘要求愈来愈高&#xff01; 别说offer&#xff0c;现在出门零活…

Android开发基础面试题,Android保活黑科技的技术实现

前言 Android常用知识体系是什么鬼&#xff1f;所谓常用知识体系&#xff0c;就是指对项目中重复使用率较高的功能点进行梳理。注意哦&#xff0c;不是Android知识体系。 古语道&#xff1a;学而不思则罔&#xff0c;思而不学则殆。如果将做项目类比为“学”&#xff0c;那么…

Unity2023.1.19_DOTS_JobSystem

Unity2023.1.19_DOTS_JobSystem 上篇我们知道了DOTS是包含Entity Component System&#xff0c;Job System&#xff0c;Burst compiler三者的。接下来看下JobSystem的工作原理和具体实现。 简介&#xff1a; 官方介绍说&#xff1a;JobSystem允许您编写简单而安全的多线程代…

【模型训练】-图形验证码识别

针对网站中的图形验证码图片&#xff0c;进行反向的内容识别&#xff0c;支持数字和字母&#xff0c;不区分大小写。 ​​​​​​​​​​​​​​数据集地址 数据格式如下&#xff1a; 1、依赖导入 import os import torch import torch.nn as nn import torch.optim as o…