Dubbo源码分析(六)服务引用的具体流程   前言 一、引用的是什么? 二、从哪里开始?

前言

在前面的章节中,我们已经完成Dubbo服务暴露的流程分析。今天我们一起来看Dubbo怎么引用这些服务的。

关于服务引用,Dubbo有两种方式。一种是基于注册中心进行服务引用,一种是服务直连进行引用。服务直连主要用于测试联调阶段,生产环境不推荐使用。它的配置也比较简单,在消费者端指定服务url即可。

<dubbo:reference id="infoUserService"
    interface="com.viewscenes.netsupervisor.service.InfoUserService"
    url="dubbo://192.168.139.129:20880"/>

本文将重点分析通过注册中心方式,服务引用的过程。开始之前,我们再回顾一下消费者端项目整体的XML文件配置。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!-- 消费者方应用信息,用于计算依赖关系 -->
    <dubbo:application name="dubbo_consumer"/>
    <!-- 用于配置连接注册中心相关信息 -->
    <dubbo:registry protocol="zookeeper" address="192.168.139.131:2181" client="zkclient" />
    <!-- 引用配置   用于创建一个远程接口服务代理 -->
    <dubbo:reference id="infoUserService"
        interface="com.viewscenes.netsupervisor.service.InfoUserService"/>
</beans>

一、引用的是什么?

在上述配置文件中,infoUserService只是Spring中的一个Bean。在代码中,我们通过getBean()来获取它,然后调用它的方法。

我们在RPC基本原理以及如何用Netty来实现RPC这篇文章中已经分析过,这里的bean其实是一个FactoryBean,通过它可以返回一个接口的代理对象,完成调用逻辑的处理。

在代码中,我们通过这样来获取这个Bean。

InfoUserService infoUserService = (InfoUserService) context.getBean("infoUserService");

这里的infoUserService就是一个代理对象,比如像proxy@2903这种。当然了,它必然会包含一个InvocationHandler,如下所示:

 
Dubbo源码分析(六)服务引用的具体流程
 
前言
一、引用的是什么?
二、从哪里开始?
infoUserService代理对象

服务引用就是其实通过动态代理给接口创建代理对象并返回。当我们调用接口方法时,则调用到InvocationHandler相关方法,处理相关请求。

二、从哪里开始?

在上面的配置文件中,dubbo:reference对应的是ReferenceBean处理类。那么Spring在实例化这个Bean的时候,就调用到里面方法。我们先看看它的类结构

package com.alibaba.dubbo.config.spring;

public class ReferenceBean<T> extends ReferenceConfig<T> implements 
        FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

    private transient ApplicationContext applicationContext;

    public ReferenceBean() {
        super();
    }
    public ReferenceBean(Reference reference) {
        super(reference);
    }
    //设置applicationContext
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    //返回代理对象
    public Object getObject() throws Exception {
        return get();
    }
    //返回代理对象接口类型
    public Class<?> getObjectType() {
        return getInterfaceClass();
    }
    //是否为单例
    @Parameter(excluded = true)
    public boolean isSingleton() {
        return true;
    }
    //Bean初始化方法
    public void afterPropertiesSet() throws Exception {
        if(isInit()){
            getObject();
        }
    }
}

首先,映入眼帘的是FactoryBean接口。有了它,则证明当前bean是一个工厂Bean,所以我们重点关注它的getObject方法,当我们的服务被注入到其他类中时,就会调用到此方法。

其次,是InitializingBean接口。它是Bean的初始化方法,当Bean完成实例化之后被调用。在上述代码中,当init()条件判断成立后调用getObject。我们可以在配置文件中以这种方式来激活它:init="true"

以上就是服务引用的两个不同时机。再专业点来讲,一个是懒汉式,一个是饿汉式。Dubbo默认是懒汉式引用,需要时才会调用。

上述代码中,getObject方法会调用到父类的init方法。这个方法内容比较多,前面的部分是各种配置检查、赋值,然后就是创建代理对象返回。

private void init() {
    //避免重复初始化
    if (initialized) {
        return;
    }   
    //省略相关配置检查、赋值等代码... 
    
    //创建代理
    ref = createProxy(map);
}

上述代码中,重点是createProxy方法。它负责创建Invoker实例和创建代理对象。

也许我们还记得,在服务暴露的时候,会分为本地暴露和远程暴露。在这里,服务引用也是这样。Dubbo首先判断服务引用是本地引用还是远程引用,默认是远程引用。然后判断是否为直连服务,根据协议调用refer方法创建invoker对象 。最后创建服务代理对象并返回。

private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    final boolean isJvmRefer;
    //获取配置判断是否为本地引用
    if (isInjvm() == null) {
        if (url != null && url.length() > 0) {
            isJvmRefer = false;
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            // by default, reference local service if there is
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        isJvmRefer = isInjvm().booleanValue();
    }
    //本地引用
    if (isJvmRefer) {
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    //远程引用
    } else {
        //url不为空则代表是服务直连
        if (url != null && url.length() > 0) {
            //切割多个url
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        url = url.setPath(interfaceName);
                    }
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))