不得不扩展
从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-
网友评论