美文网首页
梳理Dubbo扩展的理解

梳理Dubbo扩展的理解

作者: 胖脸君 | 来源:发表于2018-03-21 16:50 被阅读0次

    不得不扩展

    从SPI说起

    ExtensionLoader

    验证自定义协议


    不得不扩展

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

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


    image

    如何让众多的实现,以统一的方式准确地接入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-

    相关文章

      网友评论

          本文标题:梳理Dubbo扩展的理解

          本文链接:https://www.haomeiwen.com/subject/sfngqftx.html