jdk和dubbo的SPI机制

前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。对于一个高度集成化的、成熟、稳健的系统来讲,永远不是封闭、固守的,它需要向外提供一定的可扩展的能力,外部的实现类或者jar包都可以调用它。在面向对象的开发领域中,接口是对系统功能的高度抽象,因为SPI可谓是"应运而生",本篇博客就开始走进SPI,探究java自身的SPI和Dubbo的SPI到底是什么原理

目录

一:SPI是什么

二:jdk的SPI

三:dubbo的SPI

四:总结

正文

一:SPI是什么?

spi全称英文是service provider Interface,翻译成中文也就是服务提供接口,在jdk 1.6开始,就已经提供了SPI.它的使用比较简单。即在项目的类路径下提供一个META/services/xx文件,配置一个文件,文件名为接口的全路径的名称,内容为具体的实现类全路径名。jdk将会使用ServiceLoader.load()方法去解析和加载接口和其中的实现类,按需执行不同的方法。

举个简单的例子:在jdbc中,jdk提供了driver(数据库)接口,但是不同的厂商实现起来的方式不同,比如mysql、oracle、sqlLite等厂商底层的实现逻辑都是不同的,因此在对数据库驱动driver实现方式上,可以采用SPI机制。比如在mysql-contactor.jar包中会在META/services路径下,这里相当于扩展了java.sql.Driver接口,jdk会在META/services路径下扫描该文件,然后加载mysql的diver实现类com.mysql.cj.jdbc.Driver,就相当于扩展了Driver的接口能力,按需加载mysql的实现类。oracle的连接jar包会有oracle的配置文件,这样不同的数据库根据自身的不同逻辑按需扩展了Driver的能力,这就是SPI的最大好处。

java.sql.Driver的内容:

二:java的SPI机制

从jdk1.6开始,java就提供了spi机制的支持,接下来我们就从一个例子来说明jdk的spi是如何实现的?

2.1:设计一个接口

public interface Animal {
    void sound();
}

2.2:有两个实现类:

public class Cat implements Animal {
    public void sound() {
        System.out.println("小猫在叫");
    }
}
public class Dog implements Animal {
    public void sound() {
        System.out.println("小狗在叫");
    }
}

2.3:配置META-INF类

2.4:读取配置

    public static void main(String[] args) {
        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
        final Iterator<Animal> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Animal next =  iterator.next();
            next.sound();
        }
    }
}

2.5:原理

在上面的例子中:定义了一个接口Animal,然后有两个实现类:Cat和Dog,在META-INF的文件目录下,两个接口都进行了相关的配置,接口实现类模块要同时加载两个类,具体的调用逻辑在客户端的ServiceLoader中来通过迭代器遍历来调用具体的配置实现类,那么代码具体的原理是什么呢?跟着我一起走进源码来分析一下jdk:

在ServiceLoader的load方法中首先会获取上下文类加载器,然后构造一个ServiceLoader,在ServiceLoader中有一个懒加载器,懒加载器会通过BufferedReader来从META-INF/services路径下读取对应的接口名的全路径名文件,也就是我们配置的文件,然后通过文件的类解析器读取文件中的内容,再通过类加载器加载类的全路径

仔细分析下java的spi具有以下缺点:

①一次性加载除了所有的实现类,假如我只需要其中一个,其它的并不需要这就形成了一定的资源消耗浪费

②不具有IOC的功能,假如我有一个实现类,如何将它注入到我的容器中呢,类之间依赖关系如何完成呢?

三:dubbo的SPI

dubbo在原有的spi基础上主要有以下的改变,①配置文件采用键值对配置的方式,使用起来更加灵活和简单  增强了原本SPI的功能,使得SPI具备ioc和aop的功能,这在原本的java中spi是不支持的。dubbo的spi是通过ExtensionLoader来解析的,通过ExtensionLoader来加载指定的实现类,配置文件的路径在META-INF/dubbo路径下,我们通过一个例子来了解dubbo的SPI运行机制:

3.1:dubbo的负载均衡机制其中就采用了spi机制,选择哪个负载均衡策略是通过@SPI注解来实现的:

利用ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(name)来获取具体的LoadBalance的实现类,其中name是对应配置文件(见下文)中的键;

其中用了@SPI来指定了dubbo的负载均衡策略为随机(random),我们再来了解一下@SPI注解和@Adaptive是如何工作的?

3.2:在dubbo的META_INF.dubbo.internal路径下存在一个文件:

com.alibaba.dubbo.rpc.cluster.LoadBalance文件,文件内容是这样的:

可以看出dubbo的spi配置是采用键值对的方式,键值对最大的好处就是可以以键来获取值,取值比较简单和方便。这点和java的spi配置方式是不同的,java的spi只有全路径名;

3.3:@SPI和@Adaptive注解的作用是什么?

Dubbo通过注解@Adaptive作为标记实现了一个适配器类,dubbo将会为这个类动态生成代理对象;ExtensionLoader中获取默认实现类或者通过实现类名称(由@SPI注解指定的名称)来获取实现类

为什么会出现@Adaptive这个注解呢?主要原因是因为dubbo的加载扩展了是从配置文件加载的,是很动态的,但是实现类却要固定写死或者灵活实现,所以就得区分开。用@Adaptive就是表示由框架自己生成,不需要人为实现.

在dubbo加载SPI时会动态创建SPI Adaptive实现ExtensionLoader。从URL获取密钥,该密钥将通过@Adaptive由接口方法定义的注释提供

3.4:dubbo的spi读取配置和实现类原理

3.4.1:从解析加载配置类的源码开始分析

ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE)

上面的源码比较简单,首先是根据传入的接口从缓存(一个以class为键,ExtensionLoader为值的concurrentHashMap)中获取,如果拿不到就放入到缓存中;逻辑比较简单,这里就不做详细分析了。接下来主要是分析:ExtensionLoader.getExtension(name)

我们来看下具体的createExtension方法的源码:

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

①通过 getExtensionClasses 获取所有的拓展类,也就是所有META-INF下的配置文件中的键值对名

②通过反射创建拓展对象

③向拓展对象中注入依赖

④将拓展对象包裹在相应的 Wrapper 对象中,后面需要从wrapper中取

来具体看一下dubbo是如何解析配置文件的:

上面可以看出三个路径,这和我们刚才上面看到的路径是一致的,dubbo就是读取该路径下的的文件 

加载配置文件下的文件内容,也就是上面的com.alibaba.dubbo.rpc.cluster.LoadBalance文件

3.5:dubbo的IOC机制

dubbo的IOC是通过setter方法来实现注入的,通过遍历对象实例的所有方法,找到其setter方法在进行截取,从objectFactory中获取扩展类再进行反射执行。这样的话,就算实现实例中有依赖的扩展实例,都可以注入完成,是dubbo的IOC体现。ojectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。

四:总结

本篇博客简单分别介绍了 Java SPI 与 Dubbo SPI 用法,java的spi举了个简单的例子来进行了说明。并仔细分析了jdk的spi不足,dubbo是如何面对jdk的不足之处,然后自己定制开发出一套更加合理和更好的dubbo自我实现。以及详细分析了 Dubbo SPI 的加载拓展类的过程和源码的分析。其中可以看出来dubbo中对于缓存和反射的利用是相当之多的.SPI是软件设计中高扩展性的一个体现,通过spi机制可以灵活地实现厂商的规范订制和不同企业的具体规范自己实现.高度扩展了原程序,使得我们设计出来的程序更加具有扩展力。

 

原文地址:https://www.cnblogs.com/wyq178/p/12171881.html

时间: 2024-10-29 11:52:41

jdk和dubbo的SPI机制的相关文章

Dubbo的SPI机制与JDK机制的不同及原理分析

从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简单的来说,可以把它分为四个角色.服务提供方(Provider).服务消费方(Consumer).注册中心和监控中心.通过注册中心对服务进行注册和订阅,通过监控中心对服务进行监控. 核心功能 Remoting:远程通讯,提供对多种NIO框架抽象封装,包括"同步转异步"和"请求-响应

Dubbo的SPI机制(1)

插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,在dubbo的jar内部的/META-INF/dubbo/internal下会有许多以接口名命名的文件,如图: 这些文件正是用于扩展用途的,我们称之为SPI描述文件.每个文件就代表一种扩展,文件打开后,在文件中会以行为单位配置该扩展接口的具体实现类,每个扩展可以有多个实现,以key-value的形式进行配置.例如打开/META-IN

Dubbo的SPI机制(6)——AOP

在 ExtensionLoader 类的loadFile方法中有下图的这段代码: 类如现在这个ExtensionLoader中的type 是Protocol.class,也就是SPI接口的实现类中XxxProtocol类中有这样的构造函数 public XxxProtocol ( Protocol  object) ,这个构造函数显然说明XxxProtocol有包装或代理这个object的意思.所以当发现了这样特点的实现类后,就会把它缓存到wrappers这个变量中,最终缓存在Extension

dubbo源码分析01:SPI机制

一.什么是SPI SPI全称为Service Provider Interface,是一种服务发现机制,其本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件.这样可以在运行时,动态为该接口替换实现类. JDK提供了默认的SPI实现,但是Dubbo并未使用JDK提供的SPI,而是自己封装了一套.我们先来通过Dubbo官网给的两个例子简单了解下JDK和Dubbo的SPI是如何使用的. 1.1.JDK SPI示例 首先定义一个接口以及它的两个实现类 1 public interfac

Dubbo中SPI扩展机制解析

dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name,可以通过name找到具体的实现. 每个扩展点都有一个@Adaptive实例,用来注入到依赖这个扩展点的某些类中,运行时通过url参数去动态判断最终选择哪个Extension实例用. dubbo的SPI扩展机制增加了对扩展点自动装配(类似IOC)和自动包装(类似AOP)的支持. 标注了@Activat

聊聊Dubbo - Dubbo可扩展机制实战

摘要: 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架.今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性. 1. Dubbo的扩展机制 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架.今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性. 如同罗马不是一天建成的,任何系统都一定是从小系统不断发展成为大系统的,想要从一开始就把系统设计的足够完善是不可能的,相反的,我们应该关注当下的需求,然后再不断地对系统进行迭代.在代码层面,要求我们适当的对

JAVA SPI机制分析

简介 SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中.在java.util.ServiceLoader的文档里有比较详细的介绍.简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块.xml解析模块.jdbc模块等方案.面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码.一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码.为

分布式的几件小事(五)dubbo的spi思想是什么

1.什么是SPI机制 SPI 全称为 Service Provider Interface,是一种服务发现机制. SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以在运行时,动态为接口替换实现类. 正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能.SPI 机制在第三方框架中也有所应用,比如jdbc. java定义了一套jdbc的接口,但是java是没有提供jdbc的实现类. 但是实际上项目跑的时候,要使用jdbc接口的哪些实

JDK源码系列(一) ------ 深入理解SPI机制

什么是SPI机制 最近我建了另一个文章分类,用于扩展JDK中一些重要但不常用的功能. SPI,全名Service Provider Interface,是一种服务发现机制.它可以看成是一种针对接口实现类的解耦方案.我们只需要采用配置文件方式配置好接口的实现类,就可以利用SPI机制去加载到它们了,当我们需要修改实现类时,改改配置文件就可以了,而不需要去改代码. 当然,有的同学可能会问,spring也可以做接口实现类的解耦,是不是SPI就没用了呢?虽然两者都可以达到相同的目的,但是不一定所有应用都可