赞
踩
本文以围绕以下问题探讨:
以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><
当使用Dubbo时候,使用@Reference
或者 ReferenceConfig.get
时候获取的一个目标接口,那么进行调用时候,接口并不是直接调用到了接口的实现(Impl)类。
public static void main(String[] args) {
ReferenceConfig<HelloService> 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<Invoker<?>> invokers = new ArrayList<Invoker<?>>(); 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<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 没有则加锁创建 instance = createExtension(name); // 重新创建回去 holder.set(instance); } } } return (T) instance; }
上面代码主要有以下逻辑:
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<Class<?>> 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 <T> Invoker<T> refer(Class<T> 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 <T> Invoker<T> refer(Class<T> 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 <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, INVOKER_LISTENER_KEY)));
}
如果是,则会直接去先初始化 RegistryProtocol
,相关注册中心初始化的且看下回分解。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。