DUBBO的SPI机制(一)


JAVA的SPI

SPI是什么

SPI全称service provider interface,其作用是寻找接口的实现类,将接口与实现类完全分离,是面向接口编程的最后一步

为什么要使用

面向的对象的设计里,推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。解决了接口与实现类的解耦。

使用场景

提供功能的扩展点,解耦接口与实现类,提高框架的透明化。常见的使用案例:日志的门面包slf4j、jdbc4.0以上的驱动包、spring boot的自动装配。

如何使用

在JDK中的SPI使用步骤如下:
1、在资源目录resource下的MEAT-INF/services中创建配置文件(以接口完整class名为配置文件名,文件内容为接口实现类完整class名且分行符分隔,文件使用UTF-8编码格式)
2、在JAVA代码中使用ServiceLoader.load获取对应接口的加载器对象,遍历其实现了迭代器的加载器对象获取对应的实例调用即可

原理

指定读取MEAT-INF/services目录下的所有文件获取实现类的完整class名,并(使用线程上下文加载器)加载实例化

存在问题

会将配置文件的中实现类全部实例化,不管是否会使用到,造成资源浪费,不能按需加载

DUBBO的SPI

DUBBO的SPI用法

SPI机制解决了实例的加载问题,DUBBO的SPI实现基于健值对的形式进行管理配置文件,为了显示懒加载或运行期选择调用,实现了一套更灵活的过滤选择方式,其使用体现在几个注解的用法中。

@SPI

所有DUBBO的SPI接口必须使用@SPI注解标记,@SPI注解的value值为默认的接口实现类的别名(在配置文件中配置的那些key)

第一步:
这里创建一个api模块,创建SPI接口

pom.xml

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo-common</artifactId>
            <version>2.6.5</version>
            <scope>provided</scope>
        </dependency>

接口

package priv.dev.service;

import com.alibaba.dubbo.common.extension.SPI;

@SPI("a")
public interface ProductService {

    String getProduct(String productId);

}

第二步:

package priv.dev.service.impl;

import priv.dev.service.ProductService;

public class ProductServiceAImpl implements ProductService {

    @Override
    public String getProduct(String productId) {
        System.out.println("A,调用了ProductService的A实现类");
        return productId;
    }

}

package priv.dev.service.impl;

import priv.dev.service.ProductService;

public class ProductServiceBImpl implements ProductService {

    @Override
    public String getProduct(String productId) {
        System.out.println("B,调用了ProductService的B实现类");
        return productId;
    }

}

package priv.dev.service.impl;

import priv.dev.service.ProductService;

public class ProductServiceCImpl implements ProductService {

    @Override
    public String getProduct(String productId) {
        System.out.println("C,调用了ProductService的C实现类");
        return productId;
    }

}

在资源目录下的META-INF/dubbo/创建配置文件(以接口完整class名为配置文件名,文件内容为key=value形式,value为接口实现类完整class名,key为别名,且分行符分隔,文件使用UTF-8编码格式)

这里创建一个spi模块,引入api模块和dubbo依赖,在该项目下创建配置文件。没有在配置文件中接口实现类不会被读取。

在这里插入图片描述第三步:
这里创建spi-test模块,引入spi模块和dubbo和junit依赖

测试getDefaultExtension方法获取@SPI标记的SPI接口默认实现类

package priv.dev.spi;

import com.alibaba.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;
import priv.dev.service.ProductService;

public class SpiTest {

    @Test
    public void testGetDefaultExtension(){
        ExtensionLoader<ProductService> loader = ExtensionLoader.getExtensionLoader(ProductService.class);

        //loader.getDefaultExtension:获取接口上标记@SPI注解的value值对应的实现类
        ProductService extension = loader.getDefaultExtension();
        extension.getProduct("xxxx");
    }

}

运行效果:
在这里插入图片描述

当去掉@SPI接口的value值再次测试getDefaultExtension
在这里插入图片描述
运行效果:
由于getDefaultExtension无法获取SPI接口的默认实现类进而调用其方法空指针异常,使用getDefaultExtension在@SPI的value必须指定正确的别名
在这里插入图片描述

测试getExtension方法获取指定别名的SPI接口实现类

在这里插入图片描述

@Active 条件激活

@Active条件激活,@Active注解表示的是需要满足的条件

在上述第二步的基础上另行分支案例

接口及实现类

package priv.dev.service;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface PaymentService {

    String payment(String productId);

}

package priv.dev.service.impl;

import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;

@Activate(group = "dev")
public class PaymentServiceAImpl implements PaymentService {

    @Override
    public String payment(String productId) {
        System.out.println("调用了PaymentService的A实现类");
        return productId;
    }

}

package priv.dev.service.impl;

import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;

@Activate(group = {"dev", "test"},order = 3)
public class PaymentServiceBImpl implements PaymentService {

    @Override
    public String payment(String productId) {
        System.out.println("调用了PaymentService的B实现类");
        return productId;
    }

}


package priv.dev.service.impl;

import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;

@Activate(group = "test",order = 2)
public class PaymentServiceCImpl implements PaymentService {

    @Override
    public String payment(String productId) {
        System.out.println("调用了PaymentService的C实现类");
        return productId;
    }

}

package priv.dev.service.impl;

import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;

@Activate(group = "test",value = "ddd",order = 1)
public class PaymentServiceDImpl implements PaymentService {

    @Override
    public String payment(String productId) {
        System.out.println("调用了PaymentService的D实现类");
        return productId;
    }

}

配置文件

a=priv.dev.service.impl.PaymentServiceAImpl
b=priv.dev.service.impl.PaymentServiceBImpl
c=priv.dev.service.impl.PaymentServiceCImpl
d=priv.dev.service.impl.PaymentServiceDImpl

测试案例

@Active的group属性,表示分组

    /**
     * 实际情况的group为dev
     * 满足匹配条件激活的实现类为PaymentService的A、B实现类
     */
    @Test
    public void testGetActiveExtension() {
        ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
        URL url = URL.valueOf("/");
        List<PaymentService> extensions = loader.getActivateExtension(url, "", "dev");
        extensions.forEach(extension -> extension.payment("xxxx"));
    }

@Active的order属性,表示实现类在getActiveExtension返回的实现类集合中的顺序。(dubbo内部使用责任链模式,按顺序调用一批SPI接口的实现类)

    /**
     * 实际情况的group为test
     * 满足匹配条件激活的实现类为PaymentService的B、C实现类
     * 且调用顺序为C-》B,由于C实现类的order小于B的order,排在前面
     */
    @Test
    public void testGetActiveExtension2() {
        ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
        URL url = URL.valueOf("/");
        List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
        for (PaymentService service : extensions) {
            service.payment("123");
        }
    }

@Active的value属性,表示需要在url参数中有key为该值才符合条件

    /**
     * 实际情况的group为test,且url参数中有key为ddd
     * 满足匹配条件激活的实现类为PaymentService的B、D、C实现类
     */
    @Test
    public void testGetActiveExtension3() {
        ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
        URL url = URL.valueOf("/");
        url = url.addParameter("ddd", "666");
        List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
        for (PaymentService service : extensions) {
            service.payment("123");
        }
    }

    /**
     * 实际情况的group为test,且url参数中有key为fff
     * 满足匹配条件激活的实现类为PaymentService的C、B实现类
     */
    @Test
    public void testGetActiveExtension4() {
        ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
        URL url = URL.valueOf("/");
        url = url.addParameter("fff", "666");
        List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
        for (PaymentService service : extensions) {
            service.payment("123");
        }
    }

getActiveExtension的动态选择实现类

    /**
     * 实际情况的group为test,且url参数中有key为fff
     * 当前匹配条件激活的实现类为PaymentService的C、B实现类
     * 但由于getActivateExtension方法中指定了key进行动态选择实现类
     * 在url上新增key同名的参数,值为a,-c 即为 (+)a:加入a实现 -c:去掉c实现 
     * 最终匹配条件激活的实现类为PaymentService的B、A实现类
     */
    @Test
    public void testGetActiveExtension5() {
        ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
        URL url = URL.valueOf("/");
        url = url.addParameter("fff", "666");
        url = url.addParameter("dynamic", "a,-c");
        List<PaymentService> extensions = loader.getActivateExtension(url, "dynamic", "test");
        for (PaymentService service : extensions) {
            service.payment("123");
        }
    }

@Adaptive 自适配

@Adaptive可以使用在SPI接口的实现类上或者在SPI接口方法上
新增一个SPI接口

package priv.dev.service;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface RecordService {
    
    String addRecord(String message, URL url);
    
}

package priv.dev.service.impl;

import com.alibaba.dubbo.common.URL;
import priv.dev.service.RecordService;

public class RecordServiceAImpl implements RecordService {

    @Override
    public String addRecord(String message, URL url) {
        System.out.println("调用了RecordService的A实现类");
        return message;
    }
    
}

package priv.dev.service.impl;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import priv.dev.service.RecordService;

@Adaptive
public class RecordServiceBImpl  implements RecordService {

    @Override
    public String addRecord(String message, URL url) {
        System.out.println("调用了RecordService的B实现类");
        return message;
    }

}

配置文件

a=priv.dev.service.impl.RecordServiceAImpl
b=priv.dev.service.impl.RecordServiceBImpl

@Adaptive在SPI接口实现类上

在RecordServiceBImpl类上新增@Adaptive注解

/**
 * 在RecordService的B实现类上有@Adaptive注解
 * 调用B实现类
 */
@Test
public void testAdaptive() {
    ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
    RecordService service = loader.getAdaptiveExtension();
    URL url = URL.valueOf("/");
    service.addRecord("xxxx", url);
}

@Adaptive在SPI接口方法上

注释掉实现类上的@Adaptive注解,在RecordServiceAImpl的方法上标记

@SPI
public interface RecordService {

    @Adaptive
    String addRecord(String message, URL url);

}
    /**
     *
     * 在接口方法上标记@Adaptive,且接口方法参数必须包含URL或者参数class类中存在get方法返回URL
     * 通过在url追加参数,key默认值使用 点 分隔接口的驼峰名,如RecordService的默认key为record.service
     * value为实现类的别名
     * 由于在A实现类的方法上标记@Adaptive,所以最后调用了A实现类
     */
    @Test
    public void testAdaptive2(){
        ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
        RecordService service = loader.getAdaptiveExtension();
        URL url = URL.valueOf("/?record.service=a");
        service.addRecord("xxxx", url);
    }

@Adaptive自定义vlaue

@SPI
public interface RecordService {

//    @Adaptive
    @Adaptive("rec")
    String addRecord(String message, URL url);

}

    /**
     * 在接口方法上标记@Adaptive,且接口方法参数必须包含URL或者参数class类中存在get方法返回URL
     * 通过在url追加参数,key为rec,value为b
     * 所以最后调用了B实现类
     */
    @Test
    public void testAdaptive3(){
        ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
        RecordService service = loader.getAdaptiveExtension();
        URL url = URL.valueOf("/?rec=b");
        service.addRecord("xxxx", url);
    }

DUBBO中动态编译的Class

当在SPI接口的方法中标记@Adaptive注解,在ExtensionLoader的createAdaptiveExtensionClassCode方法会返回拼接的class字符串,通过javassist动态编译生成class,class中静态代理调用SPI接口实现类

以ProxyFactory这个SPI接口为例

ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

打印createAdaptiveExtensionClassCode方法返回拼接的class字符串

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {

   public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
       if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
       if (arg0.getUrl() == null)
           throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
       com.alibaba.dubbo.common.URL url = arg0.getUrl();
       String extName = url.getParameter("proxy", "javassist");
       if (extName == null)
           throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
       com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
       return extension.getProxy(arg0);
   }

   public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0, boolean arg1) throws com.alibaba.dubbo.rpc.RpcException {
       if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
       if (arg0.getUrl() == null)
           throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
       com.alibaba.dubbo.common.URL url = arg0.getUrl();
       String extName = url.getParameter("proxy", "javassist");
       if (extName == null)
           throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
       com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
       return extension.getProxy(arg0, arg1);
   }

   public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
       if (arg2 == null) throw new IllegalArgumentException("url == null");
       com.alibaba.dubbo.common.URL url = arg2;
       String extName = url.getParameter("proxy", "javassist");
       if (extName == null)
           throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
       com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
       return extension.getInvoker(arg0, arg1, arg2);
   }

}

通过分析createAdaptiveExtensionClassCode方法和凭借的class字符串可得知,在调用被@Adaptive标记注解的方法的时候会从方法参数获取URL或者参数class类中存在get方法返回URL获得URL,并通过DUBBO的URL类的url.getParameter获取key为@Adaptive的value的值,且默认值为@SPI的value

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值