前言
如果我问你,dubbo客户端启动的时候是如何连接服务器端的?这个过程比较复杂,今天我们一起学习起来~
本文分以下几个部分
1、springboot启动dubbo需要配置
2、初始化@Reference过程
3、小结
一、项目应用
1、引入jar包
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
2、项目配置
@EnableDubbo(scanBasePackages = "com.XXX")
EnableDubbo标签包含两个子标签
@EnableDubboConfig @DubboComponentScan
2.1、@EnableDubboConfig
@EnableDubboConfig标签的处理器是DubboConfigConfigurationRegistrar。处理器基本做了以下几个步骤
1、获取EnableDubboConfig类的注解
2、读EnableDubboConfig注解的字段multiple=true
3、DubboConfigConfiguration.Single.class
解析DubboConfigConfiguration.Single.class,并注入spring容器管理
解析DubboConfigConfiguration.Multiple.class,并注入spring容器管理
4、注入以下bean到spring容器中
ReferenceAnnotationBeanPostProcessor
DubboConfigAliasPostProcessor
DubboApplicationListenerRegistrar
DubboConfigDefaultPropertyValueBeanPostProcessor
DubboConfigEarlyRegistrationPostProcessor
DubboConfigEarlyInitializationPostProcessor
由于本文重点分析springboot启动dubbo客户端连接服务端过程。接下来我们重点关注ReferenceAnnotationBeanPostProcessor。该类继承了AbstractAnnotationBeanPostProcessor。而AbstractAnnotationBeanPostProcessor类会解析@Reference标签,
要想解析@Reference标签,一共分两步骤
①、初始化@Reference标签到spring容器
②、走dubbo中@Reference业务类。也就是DubboBootstrap类的业务逻辑。
三、初始化@Reference标签到spring容器
3.1、applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
在创建bean的时候调用栈会调到
AbstractAnnotationBeanPostProcessor#buildAnnotatedMetadata。
将字段和方法的标签包装成AnnotatedInjectionMetadata对象
private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
return new AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}
AbstractAnnotationBeanPostProcessor#findInjectionMetadata会会缓存类名称和metadata的对应关系
this.injectionMetadataCache.put(cacheKey, metadata);
继续缓存到InjectionMetadata里面的checkedElements字段
this.checkedElements = checkedElements;
3.2、populateBean(beanName, mbd, instanceWrapper);
populateBean负责填充Bean实例属性,最终在populateBean会调用到ReferenceAnnotationBeanPostProcessor#doGetInjectedBean。
1、构建referencedBeanName
例如ServiceBean:com.opay.online.push.provider.facade.DingTalkPushFacade
该名称会缓存到referencedBeanNameIdx字段中
2、构建referenceBeanName
例如:@Reference com.opay.online.push.provider.facade.DingTalkPushFacade
3、缓存referencedBeanName到referencedBeanNameIdx
4、构建ReferenceBean对象
5、缓存InjectedElement和ReferenceBean关系到injectedFieldReferenceBeanCache (针对字段)
6、referenceBean.get()
该方法涉及很多方面,我们重点关注本文的重点,就是如何对外发起连接的。
6.1、构建名称是registry的InterfaceCompatibleRegistryProtocol对象
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
注意dubbo spi文件的该对应关系:registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
6.2、构建QosProtocolWrapper对象
6.3、构建MockClusterWrapper对象
6.4、构建consumerUrl
6.5、构建MigrationInvoker对象
6.6、调用org.apache.dubbo.registry.client.migration.MigrationRuleListener#onRefer
6.7、zk注册,将该接口对应的providers,configurators,routers都按照节点的形式注册到zk当中去。调用栈和方法如下。
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
6.8、调用QosProtocolWrapper的refer方法
6.9、调用ProtocolFilterWrapper的refer方法
6.10、调用dubbo父类的refer方法
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}
6.11、dubboProtocol调用getClients(url)
client = Exchangers.connect(url, requestHandler);
6.12、调用HeaderExchanger#connect
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
6.13、调用Transporters#connect
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().connect(url, handler);
}
6.14、调用NettyTransporter的connect方法
return new NettyClient(url, handler);
仔细看nettyClient初始化会发现,该方法会直接让客户端和服务器端进行了一次连接
public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
// set default needReconnect true when channel is not connected
needReconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, true);
initExecutor(url);
try {
doOpen();
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
try {
// connect.
connect();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
}
} catch (RemotingException t) {
if (url.getParameter(Constants.CHECK_KEY, true)) {
close();
throw t;
} else {
logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
}
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
}
最后我打印了整个发送连接的调用栈,供大家参考阅读
四、小结
dubbo在springboot环境启动的过程中,用#Reference引用类对象要经过dubbo加载对服务器端进行一个长链接。本文仔细分析了dubbo在springboot启动的过程中是如何一步一步对外进行连接的。大概来说要分以下几步:
1、dubbo实现接口AbstractAnnotationBeanPostProcessor,在类加载过程中能够感知ReferenceAnnotationBeanPostProcessor,解析@DubboReference, @Reference注解,然后走doGetInjectedBean方法做referenceBean.get()。
2、referenceBean.get()做了很多事情,本文关注的是其启动客户端和服务器端连接过程。在创建代理invoker的时候经过Exchanger层-》Transporters层,最终发送了连接请求到远程服务提供方。