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)

大家都在看

  • Java基础语法(真随笔,不完整)

    一、注释 (1)单行注释 // (2)多行注释 / / (3)文档注释 JavaDoc /* /(用来生成自己的API文档) 二、标识符 (1)都以A-Z、a-z、$、_ 开头 (…

    Java 2023年6月6日
    086
  • 使用URL快捷方式提高效率

    阅文时长 | 0.9分钟字数统计 | 1453.6字符主要内容 | 1、引言&背景 2、URL格式基本格式介绍 3、附录:Hotkey详细参数 4、拓展:收藏夹中的URL格…

    Java 2023年6月5日
    074
  • 【spring-boot】分页类使用

    pom.xml中 pageDTO.java UserService.java UserServiceImpl.java 实现类 UserController.java Origin…

    Java 2023年5月29日
    062
  • node-java的使用及源码分析

    上篇文章简单提了下node调用java的方法但也只属于基本提了下怎么输出helloworld的层度,这次将提供一些案例和源码分析让我们更好地了解如何使用node-java库。 前置…

    Java 2023年6月5日
    0105
  • THO医鸣会章程

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年5月29日
    058
  • 安装最新Nginx

    添加Nginx官方源 vi /etc/yum.repos.d/nginx.repo 添加如下内容 [nginx] name=nginx repo baseurl=http://ng…

    Java 2023年5月30日
    073
  • Linux概述以及Linux目录结构

    Linux的概述:Linux是基于Unix的Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。(Linux操作系统它是基于Un…

    Java 2023年6月6日
    0124
  • java并发体系

    posted @2022-02-17 11:37 雄狮_杜 阅读(11 ) 评论() 编辑 Original: https://www.cnblogs.com/duyinqiang…

    Java 2023年5月29日
    086
  • 【Spring学习】过滤器和拦截器

    1、认识过滤器(Filter) 1.1、过滤器的定义 过滤器是JavaWeb的三大组件之一,是实现Filter接口的Java类。 过滤器是实现对请求资源(jsp、servlet、h…

    Java 2023年6月5日
    081
  • Java开发学习(十一)—-基于注解开发bean作用范围与生命周期管理

    一、注解开发bean作用范围与生命周期管理 前面使用注解已经完成了bean的管理,接下来将通过配置实现的内容都换成对应的注解实现,包含两部分内容: bean&#x4F5C;…

    Java 2023年5月29日
    085
  • 哈工大信息安全概论期末复习

    防扒链接: 何以牵尘的博客_CSDN博客https://blog.csdn.net/m0_61753302 ;何以牵尘 – 博客园 (cnblogs.com)https…

    Java 2023年6月9日
    090
  • 好玩Python——PIL项目实训(二)

    1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sun Apr 12 22:03:26 2020 4 5 @…

    Java 2023年6月6日
    0112
  • 这样的阅读工具,人手一个不过分吧?

    无意间被同事看到,惊呼,你是怎么在网页上做标记的? 这其实是我个人浏览网页,每日浏览大量信息,从中标记重要信息必备的工具 个人痛点 每日要浏览很多网页或者查阅资料,浏览过后重新定位…

    Java 2023年6月7日
    086
  • 碎碎念六四

    09.01 写可,不写亦可。但,—— 毕竟是写了十八年,——或许会是自己一生坚持时间最长的事情吧!写到离开世界的那一天。 用代码驱动机器,用文字影响世界。 09.02 早上又迷糊睡…

    Java 2023年6月9日
    060
  • [Java编程思想] 第五章 初始化与清理

    5.1 用构建器确保初始化 可以想象为每个类都定义一个initialize()方法,与类同名,让编译器在初始化期间自动调用。确保在你能操作对象之前,它已经被恰当地初始化。 从概念上…

    Java 2023年6月5日
    0117
  • 如何保证消息的可靠性的投递

    如何保证消息的可靠性的投递 在本项目中,添加员工会发送入职邮件,利用RabbitMQ的队列发送入职邮件。这部分只是实现发送邮件的功能,RabbitMQ它有它的优点就是异步、解耦、流…

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