Dubbo良好的扩展性与两个方面是密不可分的,一是整 个框架中针对不同的场景,恰到好处地使用了各种设计模式,二就是本章要介绍的加载机制。基于DubboSPI加载机制,让整个框架的接口和具体实现完全解耦,从而奠定了整个框架良好可扩展性的基础。
dubbo的扩展核心类ExtensionLoader可谓框架的基石。可以看到源码中大量引入的ExtensionLoader相关的扩展机制,了解了扩展机制是阅读后续源码的大前提。
ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class);
扩展点特性
- 自动包装Wrapper
- 自动装配
- 自适应@Adaptive
- 自动激活@Activate
先放上一张ExtensionLoader的工作流程图
自动包装Wrapper
自动包装扩展点的 Wrapper 类。ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。
比如dubbo中常用的包装类
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
**filter:**过滤器调用链包装器;
**listener:**协议监听器包装器;
**qos:**在线运维服务包装器;
通过这些包装类,将listener织入到我们export和reffer的调用链路中,将 filter织入到我们invoke远程调用流程中,具体包装后的链路组装逻辑,可以在之后的文章中阅读源码查看。
public class ProtocolListenerWrapper implements Protocol {
private final Protocol protocol;
public ProtocolListenerWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
自动装配
除了在构造函数中传入其他扩展实例,我们还经常使用setter 方法设置属性值。如果某个扩展类是另外-一个扩展点类的成员属性,并且拥有setter方法,那么框架也会自动注入对应的扩展点实例。ExtensionLoader在执行扩展点初始化的时候,会自动通过setter方法注入对应的实现类。这里存在-一个问题,如果扩展类属性是一个接口,它有多种实现,那么具体注入哪一个呢?这就涉及第三个特性自适应。
@SPI
public interface CarMaker {
Car makeCar();
}
@SPI
public interface WheelMaker {
Wheel makeWheel();
}
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
public void setWheelMaker(WheelMaker wheelMaker) {//这里装配了扩展类
this.wheelMaker = wheelMaker;
}
public Car makeCar() {
// ...
Wheel wheel = wheelMaker.makeWheel();//具体使用扩展类哪个实现类的方法
// ...
return new RaceCar(wheel, ...);
}
}
自适应@Adaptive
在Dubbo SPI中,我们使用@Adaptive注解,可以动态地通过URL中的参数来确定要使用哪个具体的实现类。从而解决自动加载中的实例注入问题。
@SPI
public interface Transporter {
@Adaptive({"server", "transport"})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({"client", "transport"})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
对于 bind() 方法,Adaptive 实现先查找 server key,如果该 Key 没有值则找 transport key 值,来决定代理到哪个实际扩展点。
@SPI("dubbo")
public interface Protocol {
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
如果@Adaptive没指定key,默认取类名小写protocol作为key,如果URL中没有protocol参与,那么会取@SPI(“dubbo”)标注的默认key作为value值dubbo。
这种动态寻找实现类的方式比较灵活,但只能激活-一个具体的实现类,如果需要多个实现类同时被激活,如Filter可以同时有多个过滤器;或者根据不同的条件,同时激活多个实现类,如何实现?这就涉及最后一个特性一自动激活。
自动激活@Activate
对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker 等,可以同时加载多个实现,此时,可以用自动激活来简化配置,如:
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate // 无条件自动激活
public class XxxFilter implements Filter {
// ...
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate("cache") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
public class XxxFilter implements Filter {
// ...
}
public static void main(String[] args) {
ExtensionLoader<Filter> loader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("", "", 0);
// 在url中添加cache参数
url = url.addParameter("cache", "cache");
List<Filter> filters = loader.getActivateExtension(url, "cache");
System.out.println(filters);
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer"
public class XxxFilter implements Filter {
// ...
}
源码阅读
有了这些前置知识,接下来可以深入查看源码,了解下dubbo是怎么实现这样在运行时动态适应的扩展类的。实际上就是用javassist生成一个新的类,在方法里去做动态判断。
参考资料:
csdn: