springboot启动dubbo客户端连接服务端过程

前言

如果我问你,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层,最终发送了连接请求到远程服务提供方。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值