Apache Dubbo系列:增强SPI

Stella981
• 阅读 813

Apache Dubbo系列:增强SPI

Dubbo良好的扩展性与两个方面是密不可分的,一是Dubbo整体架构中,在合适的场景中巧妙的使用了设计模式,二是使用Dubbo SPI机制,使Dubbo的接口与实现完全解耦。

在本次分享中,您可以了解如下知识点

  • Java SPI机制

  • Java SPI机制的缺点

  • Dubbo SPI配置规范

  • Dubbo SPI的分类与缓存

  • Dubbo SPI的特点

  • Dubbo SPI源码分析

  • Dubbo对IOC的支持

推荐阅读:Apache Dubbo系列:ZooKeeper注册中心

刘莅,公众号:OutOfMemoryErrorApache Dubbo系列:ZooKeeper注册中心

Java SPI机制

SPI,全称Service Provider Interface,起初是提供给厂商做定制化插件开发的。Java SPI使用策略模式,一个接口多种实现,我们只提供接口,具体实现并不在程序中直接确定,而是写在配置文件里。具体使用步骤如下:

1、定义一个接口和对应的方法。

package com.wb.service;

2、编写该接口的实现类,可以有多种实现。

package com.wb.service.impl;

3、在META-INF/services目录下新建一个文件,文件名是接口的全路径,如com.xxx.OrderService。

4、文件的具体内容是接口的实现类的全路径名称,如果接口有多个实现类,则换行编写。

com.wb.service.impl.OrderServiceImpl01

5、在业务代码中使用java.util.ServiceLoader加载具体的实现类。

import com.wb.service.OrderService;

Java SPI机制的缺点

Dubbo为什么不使用Java提供的SPI机制,而使用自己的SP?相对于Java SPI,Dubbo SPI做了一定的优化和改进。

1、Java SPI会一次性加载所有的扩展类,如果扩展类没有被使用到也会加载,很耗时。

2、如果扩展类加载失败,则连扩展的名称都获取不到,排查错误困难。

3、增加了对IOC和AOP的支持,一个扩展类可以通过setter方法注入到其他扩展类中。

Dubbo SPI机制

Dubbo SPI配置规范

Dubbo SPI和Java SPI类似,需要在META-INF/dubbo目录下放置相应的SPI配置文件,文件名是接口的全路径名,文件内容为key=扩展点实现类的全路径名,如果有多个实现类,则用换行符分割。其中,key是Dubbo SPI注解中传入的参数。另外,Dubbo SPI兼容了Java SPI,Dubbo在启动时会扫描META-INF/services/,META-INF/dubbo/、META-INF/dubbo/internal/三个路径下的SPI配置。详情可查看org.apache.dubbo.remoting.Transporter接口的实现类org.apache.dubbo.remoting.transport.netty4.NettyTransporter的配置,此处就不一一展开。

**Dubbo SPI的分类与缓存
**

Dubbo SPI根据缓存类型可以分为以下两类:

  • Class缓存

    Dubbo SPI获取扩展类配置时,先从缓存中获取,如果缓存中没有,则加载配置文件,根据配置把Class缓存起来,但不会全部初始化。

  • 实例缓存

    基于性能考虑,Dubbo SPI不仅会缓存Class,也会缓存Class的实例对象。每次获取时先从缓存中获取,如果缓存中没有,则重新加载并缓存。这也是Dubbo SPI比Java SPI性能高的原因之一,按需加载。

也可以根据扩展类的种类分为以下类型:

  • 普通扩展类

    最基础的扩展类,在SPI中配置的扩展类。

  • 包装扩展类(Wrapper类)

    如果该类的构造方法里有普通扩展类作为参数,Dubbo会判定为扩展点 Wrapper 类。

  • 自适应扩展类(Adaptive类)

    这样的扩展接口有多种实现,具体使用哪个类不写死在配置中,而是通过URL参数动态确定(此处不展开解释)。

**Dubbo SPI的特点
**

  • 自动包装特性

    如果该类的构造方法里有普通扩展类作为参数,Dubbo会判定为扩展点 Wrapper 类。

  • 自动加载特性

    如果该扩展类的成员变量有其他扩展类作为属性,并且拥有setter方法,那么Dubbo也会注入对应的扩展点实例(类似于Spring的依赖注入)。

  • 自适应

    在Dubbo中使用@Adaptive注解,我们可以在URL中动态的传入参数来确定具体使用哪个实现类。

**Dubbo SPI源码分析
**

与Java SPI代码类似,Dubbo官网中有这么一段示例

public class DubboSPITest {

所以我们阅读源码的切入点是org.apache.dubbo.common.extension.ExtensionLoader#getExtension方法。

public T getExtension(String name) {

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建扩展对象的过程是怎样的。

org.apache.dubbo.common.extension.ExtensionLoader#createExtension

private T createExtension(String name) {

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

  • 通过 getExtensionClasses 获取所有的拓展类

  • 通过反射创建拓展对象

  • 向拓展对象中注入依赖

  • 将拓展对象包裹在相应的 Wrapper 对象中

以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。接下来,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。

org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses

private Map<String, Class<?>> getExtensionClasses() {

这段代码逻辑也很简单,下面分析 loadExtensionClasses 方法的逻辑

org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses

private Map<String, Class<?>> loadExtensionClasses() {

loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,

loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,

loadResource方法用于读取配置文件,最后通过loadClass方法对结果进行缓存,loadClass方法代码如下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {

Dubbo对IOC的支持

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中

org.apache.dubbo.common.extension.ExtensionLoader#injectExtension

private T injectExtension(T instance) {

好了,这是我读Dubbo官网所理解的知识点,今天的分享就到这里,下期再见。

扫描二维码,加作者微信,更多精彩

Apache Dubbo系列:增强SPI

本文分享自微信公众号 - OutOfMemoryError(backend_technology)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这