dubbo源码实践-SPI扩展

Posted alf_cee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dubbo源码实践-SPI扩展相关的知识,希望对你有一定的参考价值。

1 概述

SPI的官方文档说明:Dubbo SPI | Apache Dubbo

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

dubbo通过SPI完成的功能:

1)程序运行时切换实现类

2)支持用户扩展,我们可以定义自己的实现类,并且使用该类

3)依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。

4)支持AOP功能,支持类似spring的AOP功能,在dubbo中AOP功能的类要求以Wrapper名字结尾类。

功能举例

2.0 项目准备

使用官方例子,这里直接上代码了。maven工程,保证有以下4个文件。

1个接口Robot,2个实现类Bumblebee、OptimusPrime,1个配置文件org.example.test.exLoader.Robot。

1个接口Robot

package org.example.test.exLoader;
import org.apache.dubbo.common.extension.SPI;
/**
 * 机器人
 */
@SPI
public interface Robot 
    void sayHello();

2个实现类

package org.example.test.exLoader;
/**
 * 大黄蜂
 */
public class Bumblebee implements Robot
    @Override
    public void sayHello() 
        System.out.println("Hello, I am Bumblebee.");
    
package org.example.test.exLoader;
/**
 * 擎天柱
 */
public class OptimusPrime implements Robot
    @Override
    public void sayHello() 
        System.out.println("Hello, I am Optimus Prime.");
    

1个配置文件

文件:resources/META-INF/dubbo/org.example.test.exLoader.Robot。

等号前面的是名字(文件内唯一, 代码中靠名字来找到对应的类),等号后面的是实现类,文件名是接口的全名。

optimusPrime = org.example.test.exLoader.OptimusPrime
bumblebee = org.example.test.exLoader.Bumblebee

maven依赖

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.8</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

2.1 程序运行时切换实现类

package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;
/** 动态切换实现类 */
public class DubboSPIChangeImplTest 
    public static void main(String[] args) 
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        System.out.println(optimusPrime.getClass());
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        System.out.println(bumblebee.getClass());
        bumblebee.sayHello();
    

代码运行结果如下图,可以看到通过optimusPrime名字可以拿到对应类的实例。

2.2 支持用户扩展

dubbo框架中的SPI都定义在dubbo.jar的/META-INF/dubbo.internal文件夹下。我们来看一下LoadBalance的SPI。

可以看到LoadBalance SPI已经有5个实现了。我们扩展一个自己的。

1)定义一个LoadBalance实现类AlfLoadBalance

package org.example.test.exExtend;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;

import java.util.List;
/** 自定义实现类*/
public class AlfLoadBalance implements LoadBalance 
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException 
        return null;
    

2)创建一个配置文件

文件名:org.apache.dubbo.rpc.cluster.LoadBalance

alf=org.example.test.exExtend.AlfLoadBalance

3)编写测试代码

package org.example.test.exExtend;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.cluster.LoadBalance;
/** 动态切换实现类 */
public class DubboSPIExtendTest 
    public static void main(String[] args) 
        ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
        LoadBalance alf = extensionLoader.getExtension("alf");
        System.out.println(alf.getClass().getSimpleName());
        LoadBalance random = extensionLoader.getExtension("random");
        System.out.println(random.getClass().getSimpleName());
    

运行结果:AlfLoadBalance类我们自己定义,RandomLoadBalance类dubbo自带的。

 读到这里,可能有个疑问,dubbo想调用我的实现,如果通过类似LoadBalance alf = extensionLoader.getExtension("alf") 的方法,岂不需要我去修改dubbo源码才能实现?这肯定是不现实的。dubbo通过@Adaptive("loadbalance")自适应SPI+URL参数来解决这个问题的,另起一篇单说吧。

2.3 依赖注入

依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。

如果字段不想不注入,可以在set方法上使用DisableInject注解。

被依赖注入的属性类的方法必须有@Adaptive注解,即这个类是一个自适应SPI。

本例中OptimusPrime类新添加了属性weapon也是一个扩展。所以该属性会被自动注入到OptimusPrime类的对象中。

例子总体文件图

OptimusPrime类

package org.example.test.exLoader;
/**
 * 擎天柱
 */
public class OptimusPrime implements Robot
    private Weapon weapon;
    @Override
    public void sayHello() 
        System.out.println("Hello, I am Optimus Prime.");
    
    public Weapon getWeapon() 
        return weapon;
    

    public void setWeapon(Weapon weapon) 
        this.weapon = weapon;
    

Weapon类是接口(dubbo中的扩展),注意@Adaptive("weaponKey")必须有,否则程序会报错。

package org.example.test.exLoader;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

@SPI("aWeapon")
public interface Weapon 
    @Adaptive("weaponKey")
    void open(URL url);

WeaponA、WeaponB是两个武器的实现类

public class AWeapon implements Weapon
    @Override
    public void open(URL url) 
        System.out.println("AWeapon");
    

public class BWeapon implements Weapon
    @Override
    public void open(URL url) 
        System.out.println("BWeapon");
    

org.example.test.exLoader.Weapon 文件

aWeapon = org.example.test.exLoader.AWeapon
bWeapon = org.example.test.exLoader.BWeapon

测试类

package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;

/** 动态切换实现类 */
public class DubboSPI_DITest 
    public static void main(String[] args) 
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        System.out.println(optimusPrime.getClass());
        Weapon weapon = ((OptimusPrime)optimusPrime).getWeapon();
        System.out.println(weapon.getClass());
    

运行结果:Weapon$Adaptive表示是自适应SPI。

依赖注入主要的代码逻辑在ExtensionLoader类的createExtension方法中,感兴趣的同学可以调试以下。

2.4 支持AOP功能

支持类似spring的AOP功能,在dubbo中AOP功能的类要求以Wrapper名字结尾类。

例子总体文件图

Robot是接口,OptimusPrime类是Robot接口的实现类,这两个类上面介绍了,没有修改。Robotwrapper1是一个AOP功能的类,也实现了Robot接口。org.example.test.exLoader.Robot文件是扩展的配置文件。DubboSPI_WrapperTest类是运行类。

 Robotwrapper1类

package org.example.test.exLoader;
/** Robot的wapper */
public class RobotWapper1 implements Robot
    private Robot robot;
    public RobotWapper1(Robot robot)
        this.robot = robot;
    
    @Override
    public void sayHello() 
        System.out.println("Before do something!!!");
        this.robot.sayHello();
        System.out.println("After do something!!!");
    

org.example.test.exLoader.Robot文件 

Wrapper前面的名字gaga可以不写,直接写org.example.test.exLoader.RobotWapper1也行。

DubboSPI_WrapperTest类

package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;
/** 动态切换实现类 */
public class DubboSPI_WrapperTest 
    public static void main(String[] args) 
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        System.out.println(optimusPrime.getClass().getSimpleName());
    

运行结果:

3 关于配置文件加载路径

请参考LoadingStrategy这个接口的实现类。dubbo会加载3个路径:

ServicesLoadingStrategy类: 加载"META-INF/services/"

DubboLoadingStrategy类: 加载“META-INF/dubbo/”

DubboInternalLoadingStrategy类:加载“META-INF/dubbo/internal/”

4 源码位置

ExtensionLoader类。

5 参考资源

Dubbo SPI | Apache Dubbo

03 Dubbo SPI 精析,接口实现两极反转(上)_哔哩哔哩_bilibili

03 Dubbo SPI 精析,接口实现两极反转(上) - 打工人技术合集

以上是关于dubbo源码实践-SPI扩展的主要内容,如果未能解决你的问题,请参考以下文章

dubbo源码实践-SPI扩展-自适应扩展机制

Dubbo底层源码分析之SPI扩展点

dubbo源码实践-Exchange 信息交换层例子

dubbo源码实践-Exchange 信息交换层例子

dubbo源码解析-spi

dubbo源码分析之基于SPI的强大扩展