梳理Dubbo扩展的理解

2 篇文章 0 订阅
2 篇文章 0 订阅

不得不扩展

从SPI说起

ExtensionLoader

验证自定义协议


不得不扩展

Dubbo分为很多逻辑层,对于各个层的接口,Dubbo都提供了很多种的实现,
对于需要满足很多使用个性需求的框架来说,单单是多提供几个实现是不够的。
重要的是需要在框架设计层面有一个好的解决方案,能让框架能应对不断扩张的需求。
这样才能在不改动最原始逻辑的基础上,不断丰富框架的内容。

Dubbo应对这种需求,实现了内核+插件的方式,草图如下:

如何让众多的实现,以统一的方式准确地接入Dubbo框架中?这是重点需要解决的问题。

如果需要灵活,硬编码的方式肯定是不行的,
从编码的角度来说,如果能提供配置,让框架解析配置,这是比较好的方法。(如果是我,我会这样想)

从SPI说起

Service Provider Interface,是一种设计方式,而不是某种具体的API,可以理解为:通过接口寻找实现类。
一般说来,面向接口编程,接口实现方,可以提供多种实现,从而在调用方,屏蔽实现的细节。
SPI设计方式,在此基础上做了一定的扩展,同样,服务调用方不在意接口的实现细,并且接口由调用方制定,实现是在另外独立的包中。
有点抽象,我们可以用生活中很常见的事情来模拟下这种方案:

某天,幼儿园的老师布置了一个作业:小朋友明天都带一条小鱼来幼儿园,用玻璃瓶装好,我们明天一起观察小鱼,
然后评选出最漂亮的小鱼拍照放贴教室墙上。
消息传达到家长那里,第二天,有人带小鲫鱼,有人带小金鱼,有人带小丑鱼,咦,蛋蛋,你带的是条鱼干吗?!

玩笑结束 :)
首先,老师布置的作业,我们可以理解为一个接口,这个接口是由需要使用接口实现的人发布的,它的目的就是多样化,因为作业(接口)的实现方,是各位小朋友,大家对这个接口的实现肯定不是一样的。这样在上课的时候(程序运行期间),大家都把自己的实现带到教室,由老师(调用方)来使用,至于使用哪个,是需要看当时具体的情况。

从以上的例子可以看出,接口在逻辑上,和接口的使用者更接近,而不是实现者

Java6提供了一种SPI实现,并且提供一个ServiceLoader类来加载对象。
它约定接口的实现者实现接口后,在自己所在项目的目录(不一定和调用者在一起项目内)META-INF\services\下,创建一个接口全类名为名字的文件,将实现类的全类名写进去。
ServiceLoader的例子网上有很多,此处不再提了。

ExtensionLoader

SPI为Dubbo需要解决的扩展问题提供了很好的思路。但是JDK原生的SPI实现,不适合拿来用。

  • 一次性会加载出接口的所有实现,这样的性能不会高;

  • 没有办法做到对内部的扩展对象进行级联初始化;

  • 默认实现不是单例的;

Dubbo自己做了一套基于SPI机制的加载和缓存扩展的实现,ExtensionLoader
它着重解决如下的几个方面的问题:

  • 对于某个扩展接口,需要可以加载出所有的实现,保证每个实现都是单例的;
  • 需要能在运行时决定需要使用哪个扩展;

为了适配各种实现,Dubbo对每个扩展接口,都对应生成一个唯一的Adaptive对象,这个对象是个适配器,也可以成为代理,它
会根据每个扩展功能的实际配置,决定需要用哪个实现。
并且对加载出来的扩展Adaptive对象、Class对象、扩展对象和扩展名的对应关系都是存储在static容器中
在加载的时候甚至还做了双重判空和必要的同步。

ExtensionLoader默认在如下三个目录加载扩展:
private static final String SERVICES_DIRECTORY = “META-INF/services/”;
private static final String DUBBO_DIRECTORY = “META-INF/dubbo/”;
private static final String DUBBO_INTERNAL_DIRECTORY = “META-INF/dubbo/internal/”;

每个实现都会有一个对应的key,例如:

default=cn.irving.extension.DefaultExtension
sw=cn.irving.extension.SWExtension
adaptive=cn.irving.extension.AdaptiveExtension

如果某个实现上添加了@Adaptive注解,就说明是个适配类,配置类本身不提供服务,它会去ExtensionLoader中加载实现对象,并且调用相同的方法并返回。
如果没有任何实现由这个注解,就需要在接口的方法上添加并且提供URL作为参数,从中提取需要适配的方法,临时构造一个适配器类,这个适配器类,功能和上面的一致。

扩展实现通过扩展适配器被内核使用,这样外部的实现也能融合到框架内部。

验证自定义协议

可以弄个例子,简单地试一下,做一个简单的dubbo例子,测试一下dubbo是否可以接纳我们自己定义的Protocol:
1、创建接口:

public interface DemoService {
    String sayHello(String name);
}

2、创建provider实现:

public class DemoServiceImpl implements DemoService {
    public String sayHello(String name) {
        return "Hello world, "+name;
    }
}

3、配置dubbo的provider-xml和consumer-xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="demo-provider-zk"/>
    <dubbo:registry address="zookeeper://localhost:2181" check="false"/>
    <dubbo:protocol name="test" port="20880"/>
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
</beans>

注意,此处用到的协议名为:test

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="demo-consumer" logger="log4j"/>
    <dubbo:registry address="zookeeper://localhost:2181" check="false"/>
    <dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" check="false"/>
</beans>

4、定义服务启动类和消费者类:

public class App 
{
    public static void main( String[] args )throws Exception
    {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{
                "spring-config-zk-provider.xml"
        });
        context.start();
        System.in.read();
    }
}
public class Consumer {
    public static void main(String[] args) throws IOException, InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{
               "spring-config-zk-consumer.xml"
        });
        context.start();
        DemoService demoService = (DemoService)context.getBean("demoService");
        System.in.read();
        for(int i = 0 ; i < 3 ; i++) {
            System.out.println(demoService.sayHello("SUN-中文")+i);
        }
    }
}

此时如果启动服务,将会得到如下的报错:

Exception in thread "main" java.lang.IllegalStateException: No such extension com.alibaba.dubbo.rpc.Protocol by name test

找不到Protocol实现:(

接下来我们自己定义一个Protocol扩展,实现Protocol接口:

public class TestProtocol extends DubboProtocol {
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        System.out.println("THIS IS TEST EXPORTER");
        return super.export(invoker);
    }
}

这个协议比较简单,全部都用DubboProtocol的实现,为了证明它用到了这个实现,在export服务的时候打印一句话。

然后将我们的扩展配置在/META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol中:

test=cn.irving.extension.TestProtocol

然后再运行服务端和客户端,将得到如下的结果:
服务端:

Connected to the target VM, address: '127.0.0.1:58434', transport: 'socket'
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
THIS IS TEST EXPORTER

客户端:

Hello world, SUN-中文0
Hello world, SUN-中文1
Hello world, SUN-中文2

-EOF-

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值