当使用Dubbo时候,使用@Reference
或者 ReferenceConfig.get
时候获取的一个目标接口,那么进行调用时候,接口并不是直接调用到了接口的实现(Impl)类。
public static void main(String[] args) {
ReferenceConfig reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-consumer"));
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
reference.setInterface(HelloService.class);
HelloService service = reference.get();
String message = service.hello("dubbo I am anla7856");
System.out.println(message);
}
当执行完reference.get()
后,返回的 HelloService
是什么样子呢?肯定不是Consumer 端产生的 HelloServiceImpl
,如果是这样,那过滤器岂不是没用了?
下面一步一步看博主分析。
主要就是在 ReferenceConfig
的 createProxy
方法,会产生一个代理对象。
在方法内部检查完参数之后,则会初始化invoker,如果有指定多个url,则会加入集群容错机制,这里先分析通过Protocol
而获取 的refer方法而获取具体invoker对象。
if (urls.size() == 1) {
// 当 url有多个
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
List> invokers = new ArrayList>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use RegistryAwareCluster only when register's CLUSTER is available
URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
而此时,url为 registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-consumer&dubbo=2.0.2&pid=6501&refer=application%3Ddubbo-consumer%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.anla.rpc.pureapi.service.HelloService%26lazy%3Dfalse%26methods%3Dhello%26pid%3D6501%26register.ip%3D192.168.1.107%26release%3D2.7.2%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1566912707974®istry=zookeeper&release=2.7.2×tamp=1566912927481
此时 REF_PROTOCOL
为 Protocol$Adaptive
,上一篇文章有讲默认的 Adaptive类原理,可以参考:Dubbo进阶(七)- Dubbo 中默认的 Adaptive类生成过程及例子。
所以此时加载的将是 RegistryProtocol
,并且执行它的 refer方法。
下面给出 Protocol$Adaptive
类的refer方法
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
上一小节发现,由于url 协议为 registry,所以当它调用 ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
将传入 registry
。
毫无疑问是返回一个 RegistryProtocol
,那么 RegistryProtocol是怎样的呢?
首先看看 ExtensionLoader
的 getExtension
方法,它是如何组装并返回这个类的呢?
public T getExtension(String name) {
// 检查参数
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 尝试从缓存中获取 实例
Holder
上面代码主要有以下逻辑:
createExtension(name)
创建一个对应实例在 createExtension
中,就是完成了具体实例的组装过程:
private T createExtension(String name) {
// 获取对应的扩展
Class> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 尝试看是否有缓存起来
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 注入相关extension注解
injectExtension(instance);
// 获取该SPI 里面所有的包装类
Set> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class> wrapperClass : wrapperClasses) {
// 由 SPI 文件中定义顺序,遍历
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
这段代码逻辑比较有意思,除了前面的检查参数、读取缓存外,就是使用到了SPI 文件中的包装类 cachedWrapperClasses
。
而Protocol的SPI 文件名为:org.apache.dubbo.rpc.Protocol
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
所以wrapperClasses
包装类集合顺序为ProtocolFilterWrapper
->ProtocolListenerWrapper
->QosProtocolWrapper
。
Dubbo 中 包装类的特性就是拥有一个 SPI 对应类型的 单参数构造方法。
这样设计的结构很明显是可以组装成为链式调用。
经过遍历组装后,得到的链式结构为:
ProtocolFilterWrapper
->ProtocolListenerWrapper
->QosProtocolWrapper
->RegistryProtocol
。
当然上面链式结构也不一定准确,但是`RgistryProtocol一定是在最后调用。
开始比较纠结的点是,既然按照顺序读取,为啥不用List或者其他具有顺序性特性的集合呢?
这样做没有意义,因为 classloader 加载的顺序也是未知的。应当确保 wrapper class 本身的实现与顺序无关。
即保证在 任何一个 Protocol
时候,都会先调用 多个Wrapper
类。
https://github.com/apache/dubbo/issues/4578
所以这个refer调用流程,则会按照如下流程调用:
ProtocolFilterWrapper
的 refer
方法,目的是初始化以及调用FilterQosProtocolWrapper
的 refer
方法,目的是初始化QoS
功能,默认是开启的ProtocolListenerWrapper
的 refer
方法,初始化 listener
功能RegistryProtocol
的 refer
方法,初始化相关注册信息,以及注册监听器所以划重点: classloader加载SPI 文件类中无需性,所以每个类之前都会调用相应的 Wrapper
类,最后才会调用相关SPI 类。
Protocol
的 SPI 文件中的这三个 Wrapper,当然有不同的功能,但是他们的 refer
方法则都会考虑 是否为registry
服务。
ProtocolFilterWrapper
的 refer
方法: @Override
public Invoker refer(Class type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}
QosProtocolWrapper
的 refer
方法: @Override
public Invoker refer(Class type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
startQosServer(url);
return protocol.refer(type, url);
}
return protocol.refer(type, url);
}
RegistryProtocol
的 refer
方法: @Override
public Invoker refer(Class type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, INVOKER_LISTENER_KEY)));
}
如果是,则会直接去先初始化 RegistryProtocol
,相关注册中心初始化的且看下回分解。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路: