dubbo源码分析2(jdk原生spi机制)

jdk中有一个spi的机制,可能很多人听都没听过,我以前也没有听说过,我擦(╯—﹏—)╯(┷━━━┷

因为一个接口可以有很多个不同的实现类嘛,而spi机制的作用就是使用配置文件可以动态的加载实现类;

而dubbo中对java原生的spi机制进行了扩充,后面我们会看到dubbo源码中spi机制无处不在;

现在我们先学习一下java原生的spi机制

1.java原生的spi

首先我们需要创建一个maven项目,什么依赖都不需要,能打印出hello world就行了

dubbo源码分析2(jdk原生spi机制)

然后我们新建一些文件,如下图所示

dubbo源码分析2(jdk原生spi机制)

一个接口,两个实现类:

java;gutter:true; package com.protagonist;</p> <p>public interface ISayName { void say(); }</p> <pre><code> ;gutter:true;
package com.protagonist;

public class SayEnglishName implements ISayName{
@Override
public void say() {
System.out.println("English:hello cool java boy");
}
}

java;gutter:true; package com.protagonist;</p> <p>public class SayChineseName implements ISayName { @Override public void say() { System.out.println("中文:哈喽,你好帅呀๑乛◡乛๑"); } }</p> <pre><code> 执行结果下图所示,可以看到正确的加载到了配置文件里面的所有实现类,然后分别调用它们的say方法; ![dubbo源码分析2(jdk原生spi机制)](https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/1368608-20211121141740914-1911867540.png) **2.java中spi机制的分析** spi全称是Service Provider Interface,这个是针对厂商或者插件的一种机制,用于一些服务提供给第三方实现或者扩展,更多的内容我就不去复制粘贴了,可以看看[这篇博客](https://blog.csdn.net/u012009613/article/details/52752625) ,说的比较通俗易懂,嘿嘿๑乛◡乛๑ 举个例子,jdk提供数据库驱动的接口, 然后不同的公司根据这些接口实现自己的产品,例如mysql,oracle驱动就是最经典的了; 下面我们可以简单的看一下mysql驱动加载的时候,首先导入依赖 ;gutter:true;
mysql
mysql-connector-java
8.0.11

一般我们jdbc的原始代码是这样的:

public static void test() {
        private String URL = "jdbc:mysql://localhost:3306/T_USER?useUnicode=true&characterEncoding=UTF-8";
        private String USER = "root";
        private String PASSWORD = "123456";

        Class.forName("com.mysql.jdbc.Driver");              //1 加载数据库驱动(现在的jdbc已经不需要显示的加载驱动了,这一行可以不要)
        Connection connection = DriverManager.getConnection(URL,USER,PASSWORD); //    2 获取链接connection
        PreparedStatement preparedStatement = connection.prepareStatement("insert into test (name, sex) values (?,?)"); // 3 通过statement对象执行sql
        preparedStatement.setString(1, "xx");
        preparedStatement.setString(2, "yy");
        Boolean result = preparedStatement.execute();      // 4 获取返回结果
    }

然后我们可以看看java.sql.DriverManager这个类,这个类就是我们使用jdbc时候,获取连接的:

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

由此看来只要是自己手动的使用jdbc或者持久层框架中封装了这句代码:DriverManager.getConnection(URL,USER,PASSWORD) ,去获取数据库连接,启动服务的时候,就会去遍历所有jar包下的META-INF/services目录,找到文件名称为java.sql.Driver的文件,取出其中所有实现类的全路径,然后去实例化就可以使用了;

那可能有的人就会又问了,为啥非要是META-INF/services目录呀,你猜的吧?(-_-メ)

其实我们可以跟着ServiceLoader.load方法一直往里面看看

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

到这里应该就知道为什么是加载META-INF/services/目录下了吧!

有兴趣的还可以看看ServiceLoader类的parse和parseLine方法,这里是详细的解析META-INF/serivces/下文件内容的,下图所示:

dubbo源码分析2(jdk原生spi机制)

在解析了文件中所有的实现类的全路径的时候,返回的是一个List

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

dubbo源码分析2(jdk原生spi机制)

3. 打破双亲委派机制

不知道大家有没有看到上图中Class.forName(cn, false, loader),这一行代码有个loader,这是一个类加载器(是在最开始load方法的时候就实例化的),大家知道这里为什么要有一个类加载器么?或者说这个类加载器有啥用?

dubbo源码分析2(jdk原生spi机制)

首先这里默认你已经熟悉了双亲委派机制了,双亲委派机制就是为了保证系统安全,jdk已经定义过的类,我们就不能再写一个相同类名的类了;

但是这里有个问题,如果有这么几个类,String类,Teacher类,Student类,, 其中String类肯定是要启动类加载器加载的吧,然后另外两个类是应用类加载器加载的,我们可以在Teacher类中使用String name = new Strign(“小王老师”), 那么我们可以在String类中使用引用Teacher类和Student吗?

我们刚刚说的spi就有这个问题,厂商实现的类为什么可以在jdk使用啊?jdk的类都是启动类加载器和扩展类加载器加载的,而厂商实现的类都是应用类加载器加载的。

dubbo源码分析2(jdk原生spi机制)

所以jdk给spi打破了这个双亲委托机制,可以把一个 ClassLoader置于一个线程的实例之中,使该 ClassLoader成为一个相对共享的实例.这样即使是启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了;

这里是真的很重要!

dubbo源码分析2(jdk原生spi机制)

如果上面说的你可能没有看懂,我也查了很多资料,在一个老哥的博客中有段话说的挺好的,只需要看我贴出来的这部分就可以了(类加载器是组合的哦,不是继承!)

java;gutter:true; 以JDBC加载驱动为例: 在JDBC4.0之后支持SPI方式加载java.sql.Driver的实现类。SPI实现方式为,通过ServiceLoader.load(Driver.class)方法,去各自实现Driver接口的lib的META-INF/services/java.sql.Driver文件里找到实现类的名字,通过Thread.currentThread().getContextClassLoader()类加载器加载实现类并返回实例。</p> <p>驱动加载的过程大致如上,那么是在什么地方打破了双亲委派模型呢? 先看下如果不用Thread.currentThread().getContextClassLoader()加载器加载,整个流程会怎么样。</p> <p>1.从META-INF/services/java.sql.Driver文件得到实现类名字DriverA 2.Class.forName("xx.xx.DriverA")来加载实现类 3.Class.forName()方法默认使用当前类的ClassLoader,JDBC是在DriverManager类里调用Driver的,当前类也就是DriverManager,它的加载器是BootstrapClassLoader。 4.用BootstrapClassLoader去加载非rt.jar包里的类xx.xx.DriverA,就会找不到 5.要加载xx.xx.DriverA需要用到AppClassLoader或其他自定义ClassLoader 6.最终矛盾出现在,要在BootstrapClassLoader加载的类里,调用AppClassLoader去加载实现类</p> <p>这样就出现了一个问题:如何在父加载器加载的类中,去调用子加载器去加载类? 1.jdk提供了两种方式,Thread.currentThread().getContextClassLoader()和ClassLoader.getSystemClassLoader()一般都指向AppClassLoader,他们能加载classpath中的类 2.SPI则用Thread.currentThread().getContextClassLoader()来加载实现类,实现在核心包里的基础类调用用户代码

4.总结

没想到一个java原生的spi不知不觉的写了这么多(´⊙ω⊙`),就很离谱!

其实总结一下,spi其实就是讲接口和实现类的定义放在了配置文件中,项目启动的时候,根据接口全名A,就会去找所有jar包中META-INF/services/目录下找到对应的A文件,在A文件中写着有所有对于A的实现类,通过io流的方式读取并解析成List

本篇博客还通过mysql驱动实际的看了一下spi的原理,然后而且还说了spi打破了类加载的双亲委派机制,以及jdk中是怎么打破的(在java历史上有好几次打破了双亲委派机制,有兴趣的可以自己了解一下)

现在说个问题,jdk原生的spi是会将文件中所有实现类都给加载实例化,但是有的时候我们只会使用到其中一个呀?全部加载了多浪费资源啊!这个问题会在dubbo中会解决,dubbo中有一套自己的spi机制,可以说是在jdk基础上优化了性能,而且还给扩展了一些新的功能,后续的再说( ̄▽ ̄)ノ

Original: https://www.cnblogs.com/wyq1995/p/15585700.html
Author: java小新人
Title: dubbo源码分析2(jdk原生spi机制)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/574200/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习。时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正。 第三版全。下回再见,后会有期! Original:…

    Java 2023年5月29日
    080
  • 1、什么是注解

    Annotation JDK5.0 始引入的新技术 0 An n otationxe 0 An n otation 的作用 1、不是程序本身, 可以对程序作出解释. ( 这一点和注…

    Java 2023年6月8日
    061
  • spring-security 配置简介

    1、Spring Security 简介 Spring Security 是一个能够基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在…

    Java 2023年6月6日
    067
  • shiro

    关于shiro:不依赖容器! 依赖 推荐插件:Ini(shiro.ini) Authentication(认证):用户身份识别,通常被称为用户”登录” Au…

    Java 2023年6月13日
    066
  • uwsgi+nginx代理Django无法访问静态资源的解决

    查看nginx启动用户,将静态资源赋权给改用户访问即可。如 静态资源目录:/data/django/static赋权:chmod 755 /data/django/static -…

    Java 2023年5月30日
    063
  • Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    &#x53C2;&#x8003;&#x4E86;Spring &#x5B98;&#x7F51;&#x6587;&#x6863…

    Java 2023年6月14日
    058
  • 半同步半异步线程池框架代码实现

    cpp;gutter:true; SyncTaskQueue.h</p> <h1>pragma once</h1> <h1>incl…

    Java 2023年5月30日
    069
  • 实现线程的两种方式

    实现Runnable接口如果当前类 不仅要继承其他类( 非Thread类), 还要实现多线程,那么 只能通过当前类实现 Runnable接口来 创建Thread类对象。 实现Run…

    Java 2023年6月9日
    074
  • 我选择了MySQL和SpringData JPA

    我是3y,一年 CRUD经验用十年的 markdown程序员👨🏻‍💻常年被誉为优质八股文选手 今天想跟大家聊聊数据库层面上的事,austin项目继续更新( 注:今天聊的数据库都特指…

    Java 2023年6月9日
    073
  • Typora+PicGo图片上传教程

    一.下载 PicGo下载地址 PicGo: 一个用于快速上传图片并获取图片 URL 链接的工具 安装 PicGo时,建议直接安装在C盘; 安装成功后,上传图片到 gitee图床试一…

    Java 2023年6月9日
    073
  • mybatis-plus报错解决Invalid bound statement (not found)错误

    mybatis-plus报错解决Invalid bound statement (not found)错误 org.apache.ibatis.binding.BindingExc…

    Java 2023年5月30日
    093
  • 10分钟搞定让你困惑的 Jenkins 环境变量

    前言 Jenkins, DevOps 技术栈的核心之一,CI/CD 离不开编写 Pipeline 脚本,上手 Jenkins ,简单查一下文档,你就应该不会被 agent,stag…

    Java 2023年6月5日
    061
  • 狂神说笔记——SpringBoot快速入门20

    SpringBoot快速入门 ; 1.什么是SpringBoot 回顾什么是Spring? Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Ro…

    Java 2023年5月30日
    0112
  • Mac VMWare NAT模式安装 CentOS 7-操作教程

    学习大数据离不开 Linux 系统,网络上大部分文章都是在 Windows 系统下使用 VMWare Workstation 安装 CentOS ,并使用 NAT 模式配置网络。本…

    Java 2023年6月16日
    078
  • 关键字、标识符和保留字

    关键字 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)特点:关键字中所字母都为小写 保留字 具体哪些保留字:goto 、const注意: 1、自己命名标识符时要避…

    Java 2023年6月7日
    062
  • Node.js(四)json

    html > 汽车管理系统 汽车管理系统 {{car.id}} {{car.name}} {{car.price}} {{car.speed}} {{car.color}} …

    Java 2023年6月15日
    061
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球