我是靠谱客的博主 激动小熊猫,最近开发中收集的这篇文章主要介绍spi 动态加载、卸载_dubbo的spi机制分析和实战案例,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章来源:https://dwz.cn/b0UieE77作者:Java知音

java里面提供了一种内置的服务提供和发现机制,可以通过配置让一个程序在运行的时候动态加载该类的具体实现。这样子我们可以在调用某个相应接口的时候,同时达到调用某些具体类的实现功能。

具体的代码案例如下所示:

首先定义一个接口和两个接口的实现类

接口

/** * @author idea * @date 2019/5/16 */public interface PersonAction { void say();}

接口实现类

62de04be4dd6aa5ae5419fe159c51d8d.png

然后我们需要在META-INF/services的文件夹底下配置一份文件:

(ps:这里的配置文件命名方式为类所在包名+类名)

1a5338ea2564be7394266beafaa61031.png

这份文件里面加入以下的配置信息:

(ps:文件里面输入的内容是表示类所在的地址全称,因为java的spi进行类加载的时候需要知道类所在的路径)

com.sise.dubbo.spi.SpiMainTestcom.sise.dubbo.spi.SpiSubTest

接着是编写测试类代码

dbf4b3d95502f50e3bc87fde4fd4b85d.png

当我们执行代码之后,会发现控制台输出了相应的内容:

this is java spithis is a SpiMainTestthis is a SpiSubTest

其实jdk自带的spi功能的实现原理分为了以下几步

1.首先通过java.util.ServiceLoader来加载META-INF/services/文件夹底下的类信息

2.在运行期间需要引用相关类的时候,对加载到内存的类进行搜索和分析,进行实例化调用。

为什么是META-INF/services该文件夹呢?

在ServiceLoader类里面,我们可以通过阅读源码看到它在加载配置的时候会指定默认的加载位置META-INF/services文件夹。

ServiceLoader会将该文件底下的配置类信息全部加载存储到内存中,然后在接口进行实例化的时候提供相应的实现类进行对象的实例化功能。这一点和ioc的思想有点类似,通过一个可插拔式的方式来对类的实例化进行控制。

8acae38c15ff5b2c950d5b10bc9863b3.png
45cb43c5d646ad600c260e5c4fdd1657.png

在了解了java的spi功能之后,我们不妨再来看看dubbo的spi扩展机制。

先用一些实际的案例来进行实战的演练,然后再进行原理性的分析:

基于dubbo的spi实现自定义负载均衡算法

dubbo里面提供了一个可扩展的LoadBalance类专门供开发者们进行扩展:

7f352b53be5744fe1c0c1641bca5adb4.png

这个类的头部加入了 @SPI 的注解标识,申明了该类是可以进行自定义拓展的。

在了解了loadBalance之后,我们需要在客户端加入自定义的负载均衡器代码,实现loadBalance接口

c0a7c9c7f5c7e1283faeaeb93fe4676a.png

这是最为基本的一种自定义负载均衡策略(永远只能请求一台机器)这种方式过于简陋,那么我们来对应用场景进行一些拓展吧。

假设说现在有个需求,由于某些特定的业务常景所需,要求consumer端在9-18点之间只能请求A机器(或者说更多机器),在18-23点之间请求B机器(或者说更多机器),其余时间可以任意请求,那么这个场景下,dubbo自带的负载均衡策略

ConsistentHashLoadBalance, RandomLoadBalance, RoundRobinLoadBalance, LeastActiveLoadBalance

均不支持,负载均衡该如何实现呢?

这个时候我们只能通过spi机制来自定义一套负载均衡策略进行实现了:

1d0692f343230e474fb5750b804aa280.png

然后在META-INF/dubbo文件夹底下配置一份纯文本的配置文件,文件命名为:

com.alibaba.dubbo.rpc.cluster.LoadBalance 

(ps:不同版本的dubbo,LoadBalance的包名可能不同)

bc0a7d756efed97e09d3b18001d5b292.png

在这份文件里面写入这么一行内容(有点key,value的味道)

mylb=com.sise.dubbo.config.loadBalanceSpi.MyLoadBalance

在consumer端的配置文件中写入以下内容,这里的loadbalance需要和配置文件里的mylb一致。

 

然后我们可以启动多台provider,用consumer去调用这些服务进行测试,通过调整机器的时间点,控制台就会打印出不同的属性信息

请求B机器执行自定义的负载均衡算法zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=consumer&check=false&dubbo=2.5.3&interface=com.sise.dubbo.api.UserRpcService&loadbalance=mylb&methods=findByUsername,findAll,printStr&pid=12460&printStr.async=true&service.filter=MyFilter&side=consumer&timestamp=1558143174084&weight=1600请求A机器执行自定义的负载均衡算法zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=consumer&check=false&dubbo=2.5.3&interface=com.sise.dubbo.api.UserRpcService&loadbalance=mylb&methods=findByUsername,findAll,printStr&pid=12460&printStr.async=true&service.filter=MyFilter&side=consumer&timestamp=1558143174084&weight=1600

通过上述的这种思路,我们借助dubbo的spi机制来加载满足自己特殊业务的负载均衡器,使得该框架的灵活性更高,扩展性更强。

自定义的dubbo过滤器

基于spi的扩展机制,dubbo里面还提供了对于filter类型的自定义拓展。开发者可以自定义一套filter来进行对于请求的功能拦截和校验,这个有点类似于springmvc里面的filter过滤器,通过特定的过滤器拦截数据之后,可以结合特殊的业务场景来做一些控制性的功能。

如何建立自己的filter过滤器?

首先我们需要在provider模块那定义一个filter类:

4b9da1f62ee2bef8ed24ec6288c5c587.png

然后在META-INF/dubbo文件夹底下去创建相应的配置文件:(这个项目里面我还加入了其他的spi配置,不过对于过滤器配置没有影响)

14d92df6ee3f92fe90002bca29bc5ac9.png

配置里面需要加入下边的内容:

MyFilter=com.sise.dubbo.config.filterSpi.MyFilter

对于过滤器的xml配置只需要在相应的provider的xml中加入

 

如果只是想对某个服务进行过滤操作的话,可以这么配置:

  

通常我们可以基于自定义的filter来实现一些服务调度的权限校验,调度次数统计等功能,但是注意在拦截请求的时候对于性能方面的把控,有时候也可以加入一些特殊ip的拦截校验功能,主要还是需要结合特殊的业务场景来实现。

dubbo本身的可扩展性极强,阿里巴巴团队在官方文档上边给出了十多种常用的spi扩展配置方式,这里主要只展示了两种常见的spi扩展,剩余的可以自行前往官网去查看文档讲解。

dubbo的spi加载原理

拿dubbo的spi来说,它在运行的时候会通过一个叫做ExtensionLoader的加载器来进行dubbo的扩展点加载。

我们可以进入ExtensionLoader这个类里面先进行初步的阅览:

bb988cb5862a4f26e29510a368a6536b.png

这里面包含写明了dubbo在使用spi机制加载配置文件的基本目录,这里的internal目录我个人理解为dubbo内置服务的配置地址。

核心的加载逻辑图如下所示:

2678de8b2adc76562a3d33125db419b5.png

通过getExtension函数来加载类:

e4ec66d009ae193c6788d73f96de3303.png

这里面有用到了加锁双重判断,主要是初始化加载之后,这些扩展类信息会被放入到一个ConcurrentMap> cachedInstances 里面。

进入createExtension函数里面,我们会看到以下内容:

377779375f51b9eac9fdc85471bf843a.png

这段代码的核心操作在于getExtensionClasses函数,再进入该函数中阅读源码:

会发现又是一次双重判断加锁的加载

07bbfe0a995756598ca63f810509858e.png

这里面的loadExtensionClasses函数是加载扩展配置类信息的作用,进去之后进行源码阅读会发现:

6bb789e2ae92bdc2cc7b1406aabecb95.png

loadFile函数对dubbo配置里面的 META-INF/services/META-INF/dubbo/META-INF/dubbo/internal/目录都进行了类的加载。这一点相比于jdk自带的spi加载所支持的目录要多。

再点进去loadFile源码里面,核心的类加载功能就会展示出来了:

8fa366b2b188cdef5d7b63b77900e7fb.png
29079cd74868b926eed384850b016911.png
98504689cc2e6808b0f8b29ca4dc8024.png

这段代码由于比较冗长,因为dubbo在进行实际加载的过程中需要考虑很多的因素,主要目的就是实现加载指定目录底下的拓展类并且将其存入一个map中缓存起来。

这段代码我进行了稍微的改写之后,变成了一个比较简单的util类,简化学习和理解的难度

34291236636ba5631ba3368edf2a947f.png
bcc298d22ef6c31803c791db1e61b3c1.png

相关的待加载服务代码:

56f174d1e0e472d55166c5e466f541c0.png

然后根据代码里面的指定目录进行配置文件的放置:

34e45989cd635b0fb2606a014645fdc9.png

配置文件也是按照dubbo的spi配置文件的格式来书写:

UserService=com.sise.dubbo.spi.spidemo.UserServiceImpl

运行程序之后,便可加载到相应的类并进行执行:

b9de77f5f1d976c1c69f266b9ced815e.png

spi技术在java中应用场景比较广泛,通常在开发的时候为了实现接口自动寻找实现类的功能,可以通过spi来进行实现,将接口的实现类转移到一份配置文件中来进行控制。jdk自带的spi通常会一次性就将所有类进行实例化比较耗时,而dubbo在加载类的时候直接通过名称来定位具体的类,按实际需要加载,同时支持加载的路径也更加多,相比于传统jdk的spi加载要效率更高。

最后

以上就是激动小熊猫为你收集整理的spi 动态加载、卸载_dubbo的spi机制分析和实战案例的全部内容,希望文章能够帮你解决spi 动态加载、卸载_dubbo的spi机制分析和实战案例所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部