Dubbo核心源码剖析(SPI 暴露机制 服务发现机制)

Dubbo核心源码剖析

1. Dubbo SPI机制

在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

1.1 SPI的概述

1.1.1 SPI的主要作用

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
在这里插入图片描述
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

1.1.2 入门案例

在这里插入图片描述

首先,我们定义一个接口,名称为 Robot。

public interface Robot {
	void sayHello();
}

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

public class Bumblebee implements Robot {

	@Override
	public void sayHello() {
		System.out.println("Hello, I am Bumblebee.");
	}
}

public class OptimusPrime implements Robot {

	@Override
	public void sayHello() {
		System.out.println("Hello, I am Optimus Prime.");
	}
}

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名com.itheima.java.spi.Robot。文件内容为实现类的全限定的类名,如下:

com.itheima.java.spi.impl.Bumblebee
com.itheima.java.spi.impl.OptimusPrime

做好所需的准备工作,接下来编写代码进行测试。

import com.itheima.java.spi.Robot;
import org.junit.Test;

import java.util.Iterator;
import java.util.ServiceLoader;

public class JavaSPITest {

	@Test
	public void sayHello(){
		//创建一个ServiceLoader对象,服务加载器 ->Iterator
		ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);

		//获取实例集合
		Iterator<Robot> iterator = serviceLoader.iterator();

		/***
		 * 循环调用
		 * 	1)hasNextService()
		 * 	2)
		 */
		while (iterator.hasNext()){
			Robot robot = iterator.next();
			robot.sayHello();
		}
	}
}

在这里插入图片描述

1.1.3 总结

调用过程

  • 应用程序调用ServiceLoader.load方法,创建一个新的ServiceLoader,并实例化该类中的成员变量
  • 应用程序通过迭代器接口获取对象实例,ServiceLoader先判断成员变量providers对象中(LinkedHashMap<String,S>类型)是否有缓存实例对象,如果有缓存,直接返回. 如果没有缓存,执行类的装载,

优点
使用 Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用进程可以根据实际业务情况启用或替换具体组件。

缺点
不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
多个并发多线程使用 ServiceLoader 类的实例是不安全的。
加载不到实现类时抛出并不是真正原因的异常,错误很难定位

1.2 Dubbo中的SPI

1.2.1 概述

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类

1.2.2 入门案例

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。下面来演示 Dubbo SPI 的用法:
Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,与 Java SPI 实现类配置不同,DubboSPI 是通过键值对的方式进行配置,配置内容如下。

在这里插入图片描述

bumblebee = com.itheima.dubbo.spi.impl.Bumblebee
optimusPrime = com.itheima.dubbo.spi.impl.OptimusPrime

在使用Dubbo SPI 时,需要在接口上标注 @SPI 注解。

@SPI("bumblebee") //bumblebee表示默认加载该对象实例
public interface Robot {
	void sayHello();
}

通过 ExtensionLoader,我们可以加载指定的实现类,下面来演示 Dubbo SPI :

import com.itheima.dubbo.spi.Robot;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;

public class DubboSPITest {

	//测试dubbo spi机制
	@Test
	public void sayHello() throws Exception {
		//1、创建ExtentionLoader
		ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);

		//2、根据指定的名字获取对应的实例
		Robot robot = extensionLoader.getExtension("bumblebee");
		//Robot robot = extensionLoader.getDefaultExtension();
		robot.sayHello();
	}
}

在这里插入图片描述
Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。

1.2.3 源码分析

上一章简单演示了 Dubbo SPI 的使用方法,首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

/**
     * 获取指定名字的实例对象,默认传入"true"
     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
     * will be thrown.
     *  type = Robot
     */
    @SuppressWarnings("unchecked")
    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);
        //判断instance是否为空
        Object instance = holder.get();
        if (instance == null) {
            //加锁
            synchronized (holder) {
                //获取instance
                instance = holder.get();
                //判断是否为空
                if (instance == null) {
                    /***
                     * 创建实例
                     * 1. 分配内存空间
                     * 2. 初始化对象
                     * 3. 将对象指向刚分配的内存空间
                     *
                     * 如果发生指令重排,有可能顺序变成如下顺序
                     * 1. 分配内存空间
                     * 2. 将对象指向刚分配的内存空间
                     * 3. 初始化对象
                     */
                    instance = createExtension(name);
                    holder.set(instance);
	            }
            }
        }
        return (T) instance;
    }

holder

public class Holder<T> {

    //保持内存可见性和防止指令重排序
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

 /***
     * 创建指定name的Class
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    private T createExtension(String name) {
        //从Map中获取指定name的字节码对象Class
        //该对象如果是装饰对象(带有构造函数,并且入参为装饰对象接口),就将指定Class存入到wrapperClasses中(wrapperClasses是Set集合)
        Class<?> clazz = getExtensionClasses().get(name);   //解析META-INFO/dubbo下的文件
        if (clazz == null) {
		    throw findException(name);
	    }
        try {
            //==================实例完成创建==========================
            //从缓存中获取实例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //反射创建对象clazz.newInstance(),并存入到EXTENSION_INSTANCES中
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //=================依赖注入===========================
            //依赖注入对象(如果当前实例存在set方法,则实现依赖注入,此时set方法只支持1个参数)
            injectExtension(instance);

	        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
	        //检查是否具有装饰者类,如果有则调用装饰者类的构造方法,并返回实例对象
	        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
	            //循环所有装饰对象
		        for (Class<?> wrapperClass : wrapperClasses) {
		            //如果该对象存在装饰对象,则创建该对象的装饰对象,并重新赋值,所以有装饰对象的时候,获取的实例并非被装饰对象,而是装饰对象实例
		            //依赖注入
			        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);
        }
    }

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中
    以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。
    由于此类设计源码较多,这里简单的总结下ExtensionLoader整个执行逻辑:
 /***
     * 解析com.itheima.dubbo.spi.Robot中的name和类的全限定名
     * 将解析的name和Class存储到Map中
     * bumblebee = com.itheima.dubbo.spi.impl.Bumblebee
     * optimusPrime = com.itheima.dubbo.spi.impl.OptimusPrime
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {
        //拿到Map中的Class
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //解析META-INFO/dubbo下的文件
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses

 /***
     * synchronized in getExtensionClasses
     * @return
     */
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        //加载解析META-INF/dubbo/路径下的文件
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

loadDirectory

  /***
     * 加载解析META-INF/dubbo/下的文件
     * @param extensionClasses
     * @param dir
     * @param type
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    //加载解析
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

loadResource

  /***
     * 加载解析
     * @param extensionClasses
     * @param classLoader
     * @param resourceURL
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            //根据=号分割
                            int i = line.indexOf('=');
                            if (i > 0) {
                                //获得name
                                name = line.substring(0, i).trim();
                                //获得类名
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //创建对应的Class:Class.forName(line, true, classLoader)
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadClass

   private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
            //判断该对象是否存在装饰类
        } else if (isWrapperClass(clazz)) {
            //装饰对象的Class
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, name);
                }
            }
        }
    }
getExtension(String name) #根据key获取拓展对象 
-->createExtension(String name) #创建拓展实例 
-->getExtensionClasses #根据路径获取所有的拓展类 
-->loadExtensionClasses #加载拓展类 
-->cacheDefaultExtensionName #解析@SPI注解 
-->loadDirectory #方法加载指定文件夹配置文件 
-->loadResource #加载资源 
-->loadClass #加载类,并通过 loadClass 方法对类进行缓存 

1.3 SPI中的IOC和AOP

1.3.1 依赖注入

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:

 /***
     * 依赖注入
     * @param instance
     * @return
     */
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //获取所有方法
                for (Method method : instance.getClass().getMethods()) {
                    //判断方法是否为set方法
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        //获取第1个参数,只支持1个参数
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            //获取set方法的名字(去除了set,其实就是属性名字)
                            String property = getSetterProperty(method);
                            //获取参数实例 objectFactory:工厂对象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //调用set方法实现依赖注入
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。这两个类的类的代码不是很复杂,这里就不一一分析了。

/**
 * SpiExtensionFactory
 */
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}
 /***
     * 从Spring容器中获取实例
     * @param type object type.
     * @param name object name.
     * @param <T>
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {

        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }

        //容器中获取实例
        for (ApplicationContext context : CONTEXTS) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

        if (Object.class == type) {
            return null;
        }

        for (ApplicationContext context : CONTEXTS) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");

        return null;
    }

Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。

1.3.2 动态增强

在用Spring的时候,我们经常会用到AOP功能。在目标类的方法前后插入其他逻辑。比如通常使用Spring AOP来实现日志,监控和鉴权等功能。 Dubbo的扩展机制,是否也支持类似的功能呢?答案是yes。在Dubbo中,有一种特殊的类,被称为Wrapper类。通过装饰者模式,使用包装类包装原始的扩展点实例。在原始扩展点实现前后插入其他逻辑,实现AOP功能。
装饰者模式
装饰者模式:在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象上,从而实现动态拓展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
在这里插入图片描述
一般来说装饰者模式有下面几个参与者:
Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为
ConcreteComponent:定义具体对象,即被装饰者
Decorator:抽象装饰者,继承自Component,从外类来扩展ConcreteComponent。对于
ConcreteComponent来说,不需要知道Decorator的存在,Decorator是一个接口或抽象类
ConcreteDecorator:具体装饰者,用于扩展ConcreteComponent
注:装饰者和被装饰者对象有相同的超类型,因为装饰者和被装饰者必须是一样的类型,这里利用继承
是为了达到类型匹配,而不是利用继承获得行为

dubbo中的AOP
Dubbo AOP 是通过装饰者模式完成的,接下来通过一个简单的案例来学习dubbo中AOP的实现方式。
首先定义一个接口

import org.apache.dubbo.common.extension.SPI;

@SPI("iphone")
public interface Phone {

	void call();
}

定义接口的实现类,也就是被装饰者

//被增强类(被装饰类)
public class IphoneX implements Phone {

	@Override
	public void call() {
		System.out.println("iphone正在拨打电话");
	}
}

为了简单,这里省略了装饰者接口。仅仅定义一个装饰者,实现phone接口,内部配置增强逻辑方法

//装饰者(增强类)
public class MusicPhone implements Phone {

	private Phone phone;

	//构造函数
	public MusicPhone(Phone phone) {
		this.phone = phone;
	}

	@Override
	public void call() {
		System.out.println("播放彩铃");
		this.phone.call();
	}
}

添加拓展点配置文件META-INF/dubbo/com.itheima.dubbo.Phone,内容如下

iphone = com.itheima.dubbo.IphoneX
filter = com.itheima.dubbo.MusicPhone

配置测试方法

import org.apache.dubbo.common.extension.ExtensionLoader;

public class PhoneDemo {

	public static void main(String[] args) {
		ExtensionLoader<Phone> extensionLoader = ExtensionLoader.getExtensionLoader(Phone.class);
		//创建对应的实例
		Phone phone = extensionLoader.getExtension("iphone");
		phone.call();
	}
}

具体执行效果如下
在这里插入图片描述
先调用装饰者增强,再调用目标方法完成业务逻辑。
通过测试案例,可以看到在Dubbo SPI中具有增强AOP的功能,我们只需要关注dubbo源码中这样一行代码就够了

 //检查是否具有装饰者类,如果有则调用装饰者类的构造方法,并返回实例对象
	        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
	            //循环所有装饰对象
		        for (Class<?> wrapperClass : wrapperClasses) {
		            //如果该对象存在装饰对象,则创建该对象的装饰对象,并重新赋值,所以有装饰对象的时候,获取的实例并非被装饰对象,而是装饰对象实例
		            //依赖注入
			        instance = injectExtension(
			                //获取装饰对象的构造函数
			                (T) wrapperClass.getConstructor(type)
                            //创建实例
                            .newInstance(instance));
		        }
	        }

1.4 动态编译

1.4.1 SPI中的自适应

我们知道在 Dubbo 中,很多拓展都是通过 SPI 机制 进行加载的,比如 Protocol、Cluster、LoadBalance、ProxyFactory 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载,即根据参数动态加载实现类。如下所示:
在这里插入图片描述
这种在运行时,根据方法参数才动态决定使用具体的拓展,在dubbo中就叫做扩展点自适应实例。其实是一个扩展点的代理,将扩展的选择从Dubbo启动时,延迟到RPC调用时。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist

自适应拓展机制的实现逻辑是这样的

  1. 首先 Dubbo 会为拓展接口生成具有代理功能的代码;
  2. 通过 javassist 或 jdk 编译这段代码,得到 Class 类;
  3. 通过反射创建代理类;
  4. 在代理类中,通过URL对象的参数来确定到底调用哪个实现类;
1.4.2 javassist入门

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。为了方便更好的理解dubbo中的自适应,这里通过案例的形式来熟悉下Javassist的基本使用

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 *  Javassist是一个开源的分析、编辑和创建Java字节码的类库
 *  能动态改变类的结构,或者动态生成类
 */
public class CompilerByJavassist {

	public static void main(String[] args) throws Exception {

		// ClassPool:class对象容器
		ClassPool pool = ClassPool.getDefault();

		// 通过ClassPool生成一个User类  Class
		CtClass ctClass = pool.makeClass("com.itheima.javassist.domain.User");

		// 添加属性     -- private String username
		CtField enameField = new CtField(pool.getCtClass("java.lang.String"),
				"username", ctClass);
		//修饰符
		enameField.setModifiers(Modifier.PRIVATE);
		//作为属性添加到CtClass中
		ctClass.addField(enameField);

		// 添加属性    -- private int age
		CtField enoField = new CtField(pool.getCtClass("int"), "age", ctClass);
		enoField.setModifiers(Modifier.PRIVATE);
		ctClass.addField(enoField);

		//添加方法
		ctClass.addMethod(CtNewMethod.getter("getUsername", enameField));
		ctClass.addMethod(CtNewMethod.setter("setUsername", enameField));
		ctClass.addMethod(CtNewMethod.getter("getAge", enoField));
		ctClass.addMethod(CtNewMethod.setter("setAge", enoField));


		// 无参构造器
		CtConstructor constructor = new CtConstructor(null, ctClass);
		constructor.setBody("{}");
		ctClass.addConstructor(constructor);

		// 添加构造函数
		//ctClass.addConstructor(new CtConstructor(new CtClass[] {}, ctClass));

		CtConstructor ctConstructor = new CtConstructor(new CtClass[] {pool.get(String.class.getName()),CtClass.intType}, ctClass);
		ctConstructor.setBody("{\n this.username=$1; \n this.age=$2;\n}");
		ctClass.addConstructor(ctConstructor);

		// 添加自定义方法
		CtMethod ctMethod = new CtMethod(CtClass.voidType, "printUser",new CtClass[] {}, ctClass);
		// 为自定义方法设置修饰符
		ctMethod.setModifiers(Modifier.PUBLIC);
		// 为自定义方法设置函数体
		StringBuffer buffer2 = new StringBuffer();
		buffer2.append("{\nSystem.out.println(\"用户信息如下\");\n")
				.append("System.out.println(\"用户名=\"+username);\n")
				.append("System.out.println(\"年龄=\"+age);\n").append("}");
		ctMethod.setBody(buffer2.toString());
		ctClass.addMethod(ctMethod);

		//生成一个class
		Class<?> clazz = ctClass.toClass();

		Constructor cons2 = clazz.getDeclaredConstructor(String.class,Integer.TYPE);

		Object obj = cons2.newInstance("itheima",20);

		//反射 执行方法
		obj.getClass().getMethod("printUser", new Class[] {})
				.invoke(obj, new Object[] {});

		// 把生成的class文件写入文件
		byte[] byteArr = ctClass.toBytecode();
		FileOutputStream fos = new FileOutputStream(new File("D://User.class"));
		fos.write(byteArr);
		fos.close();
	}
}

通过以上代码,我们可以知道使用javassist可以方便的在运行时,按需动态的创建java对象,并执行内部方法。而这也是dubbo中动态编译的核心

1.4.3 源码分析

Adaptive注解
在开始之前,我们有必要先看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。

@Documented 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.TYPE, ElementType.METHOD}) 
public @interface Adaptive { 
String[] value() default {}; 
}

从上面的代码中可知,Adaptive 可注解在类或方法上。

  • 标注在类上:Dubbo 不会为该类生成代理类。
  • 标注在方法上:Dubbo 则会为该方法生成代理逻辑,表示当前方法需要根据 参数URL 调用对应的 扩展点实现

获取自适应拓展类
dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。 先来看下创建自适应扩展类的代码:

   /***
     * 创建代理对象
     * @return
     */
    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //创建代理
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

继续看createAdaptiveExtension方法

 @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            //依赖注入
            return injectExtension(
                    // 创建接口的代理实现类对应的Class
                    (T) getAdaptiveExtensionClass()
                    //实例化
                    .newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

继续看getAdaptiveExtensionClass方法

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        Class<?> clazz = cachedAdaptiveClass = createAdaptiveExtensionClass();
        return clazz;
    }

继续看createAdaptiveExtensionClass方法,绕了一大圈,终于来到了具体的实现了。看这个createAdaptiveExtensionClass方法,它首先会生成自适应类的Java源码,然后再将源码编译成Java的字节码,加载到JVM中。

private Class<?> createAdaptiveExtensionClass() {
        //创建接口指定的动态代理对象
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        //调用了JavassistCompiler
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

Compiler的代码,默认实现是javassist。

/**
 * Compiler. (SPI, Singleton, ThreadSafe)
 */
@SPI("javassist")
public interface Compiler {

    /**
     * Compile java source code.
     *
     * @param code        Java source code
     * @param classLoader classloader
     * @return Compiled class
     */
    Class<?> compile(String code, ClassLoader classLoader);

}

createAdaptiveExtensionClassCode()方法中使用一个StringBuilder来构建自适应类的Java源码。方法实现比较长,这里就不贴代码了。这种生成字节码的方式也挺有意思的,先生成Java源代码,然后编译,加载到jvm中。通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。因为xxx.java文件是Java通用的,也是我们最熟悉的。只是代码的可读性不强,需要一点一点构建xx.java的内容。

2. 服务暴露与发现

2.1 概述

dubbo是一个简单易用的RPC框架,通过简单的提供者,消费者配置就能完成无感的网络调用。那么在dubbo中是如何将提供者的服务暴露出去,消费者又是如何获取到提供者相关信息的呢?这就是本章我们要讨论的内容。

2.2 Spring中自定义Schema

在了解dubbo的服务注册和服务发现之前,我们首先需要掌握一个知识点:Spring中自定义Schema。
Dubbo 现在的设计是完全无侵入,也就是使用者只依赖于配置契约。在 Dubbo 中,可以使用 XML 配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring 会读取配置文件,生成注入相关Bean。那 Dubbo 如何实现自定义 XML 被 Spring 加载读取呢?
从 Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机制,用于定义和配置bean。

2.2.1 案例使用

学习和使用Spring XML Schema 扩展机制并不难,需要下面几个步骤:

  1. 创建配置属性的JavaBean对象
  2. 创建一个 XML Schema 文件,描述自定义的合法构建模块,也就是xsd文件。
  3. 自定义处理器类,并实现 NamespaceHandler 接口。
  4. 自定义解析器,实现 BeanDefinitionParser 接口(最关键的部分)。
  5. 编写Spring.handlers和Spring.schemas文件配置所有部件

定义JavaBean对象,在spring中此对象会根据配置自动创建

public class User {

	private String id;  
    private String name;  
    private Integer age;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User{" +
				"id='" + id + '\'' +
				", name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

在META-INF下定义 user.xsd 文件,使用xsd用于描述标签的规则

<?xml version="1.0" encoding="UTF-8"?>  
<xsd:schema   
    xmlns="http://www.itheima.com/schema/user"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"   
    xmlns:beans="http://www.springframework.org/schema/beans"  
    targetNamespace="http://www.itheima.com/schema/user"
    elementFormDefault="qualified"   
    attributeFormDefault="unqualified">  
    <xsd:import namespace="http://www.springframework.org/schema/beans" />  
    <xsd:element name="user">
        <xsd:complexType>  
            <xsd:complexContent>  
                <xsd:extension base="beans:identifiedType">  
                    <xsd:attribute name="name" type="xsd:string" />  
                    <xsd:attribute name="age" type="xsd:int" />  
                </xsd:extension>  
            </xsd:complexContent>  
        </xsd:complexType>  
    </xsd:element>  
</xsd:schema> 

Spring读取xml文件时,会根据标签的命名空间找到其对应的NamespaceHandler,我们在NamespaceHandler内会注册标签对应的解析器BeanDefinitionParser。

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class UserNamespaceHandler extends NamespaceHandlerSupport {
	public void init() {
		/***
		 *  user.xsd文件中 name="user"
		 *  解析user节点
		 */
		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
	}
}

BeanDefinitionParser是标签对应的解析器,Spring读取到对应标签时会使用该类进行解析;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends
		AbstractSingleBeanDefinitionParser {

	protected Class getBeanClass(Element element) {
		return User.class;
	}

	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		String name = element.getAttribute("name");
		String age = element.getAttribute("age");
		String id = element.getAttribute("id");

		if (StringUtils.hasText(id)) {
			bean.addPropertyValue("id", id);
		}
		if (StringUtils.hasText(name)) {
			bean.addPropertyValue("name", name);
		}
		if (StringUtils.hasText(age)) {
			bean.addPropertyValue("age", Integer.valueOf(age));
		}
	}
}

定义spring.handlers文件,内部保存命名空间与NamespaceHandler类的对应关系;必须放在
classpath下的META-INF文件夹中。

http\://www.itheima.com/schema/user=com.itheima.schema.UserNamespaceHandler

定义spring.schemas文件,内部保存命名空间对应的xsd文件位置;必须放在classpath下的META-INF文件夹中。

http\://www.itheima.com/schema/user.xsd=META-INF/user.xsd

代码准备好了之后,就可以在spring工程中进行使用和测试,定义spring配置文件,导入对应约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:util="http://www.springframework.org/schema/util" 
xmlns:task="http://www.springframework.org/schema/task" 
xmlns:aop="http://www.springframework.org/schema/aop" 
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:itheima="http://www.itheima.com/schema/user"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
		http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.itheima.com/schema/user http://www.itheima.com/schema/user.xsd">

    <!--    创建了一个对象,并交给SpringIOC容器管理-->
    <itheima:user id="user" name="zhangsan" age="12"></itheima:user>

</beans>

编写测试类,通过spring容器获取对象user

import com.itheima.schema.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SchemaDemo {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("/spring/applicationContext.xml");
		User user = (User)ctx.getBean("user");
		System.out.println(user);

		//for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
		//	System.out.println(beanDefinitionName);
		//}
	}
}

2.2.2 dubbo中的相关对象

Dubbo是运行在spring容器中,dubbo的配置文件也是通过spring的配置文件applicationContext.xml来加载,所以dubbo的自定义配置标签实现,其实同样依赖spring的xml schema机制
在这里插入图片描述

可以看出Dubbo所有的组件都是由 DubboBeanDefinitionParser 解析,并通过registerBeanDefinitionParser方法来注册到spring中最后解析对应的对象。这些对象中我们重点关注的有以下两个:
ServiceBean:服务提供者暴露服务的核心对象
ReferenceBean:服务消费者发现服务的核心对象
RegistryConfig:定义注册中心的核心配置对象

2.3 服务暴露机制

前面主要探讨了 Dubbo 中 schema 、 XML 的相关原理 , 这些内容对理解框架整体至关重要 , 在此基础上我们继续探讨服务是如何依靠前面的配置进行服务暴露

2.3.1 术语解释

在 Dubbo 的核心领域模型中:

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可 执行体,可向它发起 invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行 远程调用。
  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
    export:暴露远程服务
    refer:引用远程服务
  • proxyFactory:获取一个接口的代理类
    getInvoker:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象
    getProxy:针对client端,创建接口的代理对象,例如DemoService的接口。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等
2.3.2 流程机制

在详细探讨服务暴露细节之前 , 我们先看一下整体duubo的服务暴露原理
在这里插入图片描述
在整体上看,Dubbo 框架做服务暴露分为两大部分 , 第一步将持有的服务实例通过代理转换成Invoker, 第二步会把 Invoker 通过具体的协议 ( 比如 Dubbo ) 转换成 Exporter, 框架做了这层抽象也大大方便了功能扩展 。
服务提供方暴露服务的蓝色初始化链,时序图如下:
在这里插入图片描述

2.3.3 源码分析

(1) 导出入口
服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。方法代码如下:

  /***
     * 暴露服务入口,事件监听,只要Spring容器发生变化,就会调用该方法
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
    // 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            //执行导出(暴露出服务列表)
            export();
        }
    }

onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。在export 根据配置执行相应的动作。最终进入到doExportUrls导出服务方法

  /***
     * 暴露服务入口
     */
    public synchronized void export() {
        checkAndUpdateSubConfigs();

        if (!shouldExport()) {
            return;
        }

        if (shouldDelay()) {
            delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            //核心关键方法
            doExport();
        }
    }
 protected synchronized void doExport() {
        if (unexported) {
		    throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
	    }
	    if (exported) {
		    return;
	    }
        exported = true;

        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        //关键方法
        doExportUrls();
    }

核心

 private void doExportUrls() {
    	//加载配置文件中的所有注册中心,并且封装为dubbo内部的URL对象列表
        List<URL> registryURLs = loadRegistries(true);
        //循环所有协议配置,根据不同的协议,向注册中心中发起注册
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            //核心方法
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

关于多协议多注册中心导出服务首先是根据配置,以及其他一些信息组装 URL。前面说过,URL 是Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。

 //服务暴露
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        //协议名字,默认是dubbo
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

        //=======================数据组装 Start=========================
        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, PROVIDER_SIDE);

        appendRuntimeParameters(map);
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        if (CollectionUtils.isNotEmpty(methods)) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }


        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
        //=======================数据组装 End=========================

        // export service
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

        //Dubbo的SPI(ConfiguratorFactory:Dubbo SPI自适应加载)
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //本地暴露,将服务数据记录到本地JVM中
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
           //远程暴露,向注册中心发送数据
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
		                    url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
	                    }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        /***
                         * 远程导出核心代码->最终要执行本地接口的实现类(ref)方法调用
                         */
                        Invoker<?> invoker = proxyFactory.getInvoker(
                                ref,
                                (Class) interfaceClass,
                                registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //通过protocol导出Invoker
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    //将invoker转化为exporter  (参数:registry)   RegistryProtocol
                    //RegistryProtocol会创建监听->会添加注册
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
        this.urls.add(url);
    }

上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map中,最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。如下:

 String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //本地暴露,将服务数据记录到本地JVM中
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
           //远程暴露,向注册中心发送数据
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
		                    url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
	                    }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        /***
                         * 远程导出核心代码->最终要执行本地接口的实现类(ref)方法调用
                         */
                        Invoker<?> invoker = proxyFactory.getInvoker(
                                ref,
                                (Class) interfaceClass,
                                registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //通过protocol导出Invoker
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    //将invoker转化为exporter  (参数:registry)   RegistryProtocol
                    //RegistryProtocol会创建监听->会添加注册
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
        this.urls.add(url);
    }

上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:
scope = none,不导出服务
scope != remote,导出到本地
scope != local,导出到远程
不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中,探索Invoker 的创建过程。如下:

 /***
     * 创建Invoker
     * @param proxy
     * @param type
     * @param url
     * @param <T>
     * @return
     */
    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 为目标类创建 Wrapper
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        // 创建匿名 Invoker 类对象,并实现 doInvoke 方法
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                    // 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

如上,JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。

(2) 导出服务到本地
Invoke创建成功之后,接下来我们来看本地导出

 private void exportLocal(URL url) {
        URL local = URLBuilder.from(url)
                .setProtocol(LOCAL_PROTOCOL)  // 设置协议头为 injvm
                .setHost(LOCALHOST_VALUE)  
                .setPort(0)
                .build();

        //通过protocol将Invoker导出(导出到本地,直接将服务添加到exporters集合中)
        Exporter<?> exporter = protocol.export(
                //通过JavassistProxyFactory进行代理创建,获取Invoker
                proxyFactory.getInvoker(
                        // <dubbo:service   interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
                        // ref指xml配置中的ref="demoService"
                        ref,
                        //interfaceClass指接口全限定名
                        (Class) interfaceClass,
                        local)
        );
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
    }

exportLocal 方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。

 @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }

如上,InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就分析完了。

(3) 导出服务到远程
接下来,我们继续分析导出服务到远程的过程。导出服务到远程包含了服务导出与服务注册两个过程。先来分析服务导出逻辑。我们把目光移动到 RegistryProtocol 的 export 方法上。

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws 
RpcException { 
// 1. 导出服务 
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); 
// 2. 获取注册中心 URL 
URL registryUrl = getRegistryUrl(originInvoker); 
// 3. 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry 
final Registry registry = getRegistry(originInvoker); 
// 4. 获取已注册的服务提供者 URL,比如: 
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker); 
// 5. 获取 register 参数 
boolean register = registeredProviderUrl.getParameter("register", true); 
// 6. 向服务提供者与消费者注册表中注册服务提供者 
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, 
registeredProviderUrl); 
// 7. 根据 register 的值决定是否注册服务 
if (register) { 
// 向注册中心注册服务(dubbo协议, zookeeper) 
register(registryUrl, registeredProviderUrl); 
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); 
}
// 8. 获取订阅 URL,比如:final URL overrideSubscribeUrl = 
getSubscribedOverrideUrl(registeredProviderUrl); 
// 9. 创建监听器 
final OverrideListener overrideSubscribeListener = new 
OverrideListener(overrideSubscribeUrl, originInvoker); 
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); 
// 10. 向注册中心进行订阅 override 数据 
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); 
// 11. 创建并返回 DestroyableExporter 
return new DestroyableExporter<T>(exporter, originInvoker, 
overrideSubscribeUrl, registeredProviderUrl); 
} 


上面代码看起来比较复杂,主要做如下一些操作:

  1. 调用 doLocalExport 导出服务
  2. 向注册中心注册服务
  3. 向注册中心进行订阅 override 数据
  4. 创建并返回 DestroyableExporter
    下面先来分析 doLocalExport 方法的逻辑,如下:
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> 
        originInvoker) { 
String key = getCacheKey(originInvoker); 
// 1. 访问缓存 
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) 
bounds.get(key); 
if (exporter == null) { 
synchronized (bounds) { 
exporter = (ExporterChangeableWrapper<T>) bounds.get(key); 
if (exporter == null) { 
// 创建 Invoker 为委托类对象 
final Invoker<?> invokerDelegete = new InvokerDelegete<T> 
(originInvoker, getProviderUrl(originInvoker)); 
// 调用 protocol 的 export 方法导出服务 
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) 
protocol.export(invokerDelegete), originInvoker); 
// 写缓存 
bounds.put(key, exporter); 
} 
} 
}
return exporter; 
} 

接下来,我们把重点放在 Protocol 的 export 方法上。假设运行时协议为 dubbo,此处的 protocol 变
量会在运行时加载 DubboProtocol,并调用 DubboProtocol 的 export 方法。

   /***
     * 服务导出
     * @param invoker Service invoker
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        //获取客户端调用地址
        URL url = invoker.getUrl();

        // export service.服务名字 com.xxx.xxx.DemoService:280883,客户端调用该接口
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        //exporter存入到Map中
        exporterMap.put(key, exporter); //key:接口 (DemoService)

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }
        //开启服务(Netty服务)
        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }

(4) 开启Netty服务
如上,我们重点关注 DubboExporter 的创建以及 openServer 方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。下面分析 openServer 方法。

    private void openServer(URL url) {
        // find server. 获取服务地址 IP:端口
        // 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(IS_SERVER_KEY, true);
        if (isServer) {
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        //服务创建,并且将创建的服务存入到serverMap  createServer(url)
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

接下来分析服务器实例的创建过程。如下:

  /***
     * 创建服务
     * @param url
     * @return
     */
    private ExchangeServer createServer(URL url) {
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // enable heartbeat by default
                .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                // 添加编码解码器参数
                .addParameter(CODEC_KEY, DubboCodec.NAME)
                .build();
        String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);

        //Transporter 使用的是Netty服务
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        }

        ExchangeServer server;
        try {
            //服务绑定和创建
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
         // 获取 client 参数,可指定 netty,mina
        str = url.getParameter(CLIENT_KEY);
        if (str != null && str.length() > 0) {
            // 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            // 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,
            // 是否包含 client 所表示的 Transporter,若不包含,则抛出异常
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }

        return server;
    }

如上,createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的Transporter 拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。

 public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        //服务绑定,使用HeaderExchanger,同时会开启Netty服务
        return getExchanger(url).bind(url, handler);
    }

上面代码比较简单,就不多说了。下面看一下 HeaderExchanger 的 bind 方法


    @Override
    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        //绑定端口好,创建解码器
        // 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下: 
// 1. new HeaderExchangeHandler(handler) 
// 2. new DecodeHandler(new HeaderExchangeHandler(handler)) 
// 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))) 

        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

HeaderExchanger 的 bind 方法包含的逻辑比较多,但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下:

   /***
     * Netty创建服务
     * @param url
     * @param handlers
     * @return
     * @throws RemotingException
     */
    public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
        // 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
            handler = new ChannelHandlerDispatcher(handlers);
        }
        //采用Netty创建服务
        return getTransporter().bind(url, handler);
    }

如上,getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为TransporterAdaptive,也就是自适应拓展类。TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。调用 NettyTransporter.bind(URL, ChannelHandler) 方法。创建一个 NettyServer 实例。调用NettyServer.doOPen() 方法,服务器被开启,服务也被暴露出来了。

(5) 服务注册
本节内容以 Zookeeper 注册中心作为分析目标,其他类型注册中心大家可自行分析。下面从服务注册
的入口方法开始分析,我们把目光再次移到 RegistryProtocol 的 export 方法上。如下:

 @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        //获取客户端调用地址
        URL url = invoker.getUrl();

        // export service.服务名字 com.xxx.xxx.DemoService:280883,客户端调用该接口
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        //exporter存入到Map中
        exporterMap.put(key, exporter); //key:接口 (DemoService)

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }
        //开启服务(Netty服务)
        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }

RegistryProtocol 的 export 方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:


    /***
     * 服务导出
     * @param originInvoker
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        URL registryUrl = getRegistryUrl(originInvoker);
        // url to export locally
        URL providerUrl = getProviderUrl(originInvoker);

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
        //  the same service. Because the subscribed is cached key with the name of the service, it causes the
        //  subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        //添加监听  overrideSubscribeUrl:远程调用的服务地址
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

	    //导出服务
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

	    // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);

	    //获取已注册的服务提供者 URL,
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
        ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
                registryUrl, registeredProviderUrl);
        //to judge if we need to delay publish
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }

        //服务注册、服务订阅
        // Deprecated! Subscribe to override rules in 2.6.x or before.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

RegistryProtocol 的 export 方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:


    public void register(URL registryUrl, URL registeredProviderUrl) {
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registeredProviderUrl);
    }

register 方法包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。接下来分两节内容对这两步操作进行分析。
这里以 Zookeeper 注册中心为例进行分析。下面先来看一下 getRegistry 方法的源码,这个方法由AbstractRegistryFactory 实现。如下:

 @Override
    public Registry getRegistry(URL url) {
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
        // 访问缓存
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            //create registry by spi/ioc
            // 缓存未命中,创建 Registry 实例
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            // 写入缓存
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

如上,getRegistry 方法先访问缓存,缓存未命中则调用 createRegistry 创建 Registry。在此方法中就是通过 new ZookeeperRegistry(url, zookeeperTransporter) 实例化一个注册中心

   public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        // 获取组名,默认为 dubbo
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        // 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporter
        zkClient = zookeeperTransporter.connect(url);
        // 添加状态监听器
        zkClient.addStateListener(state -> {
            if (state == StateListener.RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        });
    }

在上面的代码代码中,我们重点关注 ZookeeperTransporter 的 connect 方法调用,这个方法用于创建Zookeeper 客户端。创建好 Zookeeper 客户端,意味着注册中心的创建过程就结束了。接下来,再来分析一下 Zookeeper 客户端的创建过程。

   @Override
    public ZookeeperClient connect(URL url) {
        ZookeeperClient zookeeperClient;
        List<String> addressList = getURLBackupAddress(url);
        // The field define the zookeeper server , including protocol, host, port, username, password
        if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
            logger.info("find valid zookeeper client from the cache for address: " + url);
            return zookeeperClient;
        }
        // avoid creating too many connections, so add lock
        synchronized (zookeeperClientMap) {
            if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
                logger.info("find valid zookeeper client from the cache for address: " + url);
                return zookeeperClient;
            }

            zookeeperClient = createZookeeperClient(toClientURL(url));
            logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
            writeToClientMap(addressList, zookeeperClient);
        }
        return zookeeperClient;
    }

createZookeeperClient

@Override
    public ZookeeperClient createZookeeperClient(URL url) {
        return new CuratorZookeeperClient(url);
    }

CuratorZookeeperClient

 public CuratorZookeeperClient(URL url) {
        super(url);
        try {
        // 创建 CuratorFramework 构造器
            int timeout = url.getParameter(TIMEOUT_KEY, 5000);
            CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
                    .connectString(url.getBackupAddress())
                    .retryPolicy(new RetryNTimes(1, 1000))
                    .connectionTimeoutMs(timeout);
            String authority = url.getAuthority();
            if (authority != null && authority.length() > 0) {
                builder = builder.authorization("digest", authority.getBytes());
            }
            // 构建 CuratorFramework 实例
            client = builder.build();
            client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
                @Override
                public void stateChanged(CuratorFramework client, ConnectionState state) {
                    if (state == ConnectionState.LOST) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
                    } else if (state == ConnectionState.CONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
                    } else if (state == ConnectionState.RECONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
                    }
                }
            });
            // 启动客户端
            client.start();
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

CuratorZookeeperClient 构造方法主要用于创建和启动 CuratorFramework 实例。至此Zookeeper客户端就已经启动了
下面我们将 Dubbo 的 demo 跑起来,然后通过 Zookeeper 可视化客户端 ZooInspector 查看节点数据。如下:

 @Override
    public void doRegister(URL url) {
        try {
        // 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下: 
        // /${group}/${serviceInterface}/providers/${url} 
        // 比如 
        //  /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......

            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);
        }
    }

如上,ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由toUrlPath 方法生成,该方法逻辑不难理解,就不分析了。接下来分析 create 方法,如下:

 @Override
    public void create(String path, boolean ephemeral) {
        if (!ephemeral) {
        // 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在
            if (checkExists(path)) {
                return;
            }
        }
        int i = path.lastIndexOf('/');
        if (i > 0) {
        // 递归创建上一级路径
            create(path.substring(0, i), false);
        }
        // 根据 ephemeral 的值创建临时或持久节点
        if (ephemeral) {
            createEphemeral(path);
        } else {
            createPersistent(path);
        }
    }

好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。

2.3.4 总结
  1. 在有注册中心,需要注册提供者地址的情况下,ServiceConfifig 解析出的 URL 格式为:
    registry:// registry-host/org.apache.dubbo.registry.RegistryService?
    export=URL.encode(“dubbo://service-host/{服务名}/{版本号}”)
  2. 基于 Dubbo SPI 的自适应机制,通过 URL registry:// 协议头识别,就调用RegistryProtocol#export() 方法
    将具体的服务类名,比如 DubboServiceRegistryImpl ,通过 ProxyFactory 包装成Invoker 实例
    调用 doLocalExport 方法,使用 DubboProtocol 将 Invoker 转化为 Exporter 实例,并打开 Netty 服务端监听客户请求
    创建 Registry 实例,连接 Zookeeper,并在服务节点下写入提供者的 URL 地址,注册服务
    向注册中心订阅 override 数据,并返回一个 Exporter 实例
  3. 根据 URL 格式中的 “dubbo://service-host/{服务名}/{版本号}” 中协议头 dubbo:// 识别,调用 DubboProtocol#export() 方法,开发服务端口
  4. RegistryProtocol#export() 返回的 Exporter 实例存放到 ServiceConfifig 的List exporters 中

2.4 服务发现

2.4.1 服务发现流程

在这里插入图片描述
在整体上看 , Dubbo 框架做服务消费也分为两大部分 , 第一步通过持有远程服务实例生成Invoker,这个 Invoker 在客户端是核心的远程代理对象 。 第二步会把 Invoker 通过动态代理转换成实现用户接口的动态代理引用 。
服务消费方引用服务的蓝色初始化链,时序图如下:
在这里插入图片描述

2.4.2 源码分析

(1) 引用入口
服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。

/***
     * 核心方法
     * @return
     */
    @Override
    public Object getObject() {
        return get();
    }
public synchronized T get() {
        checkAndUpdateSubConfigs();

        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            //核心方法
            init();
        }
        return ref;
    }

Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。

 private void init() {
        if (initialized) {
            return;
        }
        checkStubAndLocal(interfaceClass);
        checkMock(interfaceClass);
        //=================================数据组装 Start============================
        Map<String, String> map = new HashMap<String, String>();

        map.put(SIDE_KEY, CONSUMER_SIDE);

        appendRuntimeParameters(map);
        if (!isGeneric()) {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
            }
        }
        map.put(INTERFACE_KEY, interfaceName);
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        // remove 'default.' prefix for configs from ConsumerConfig
        // appendParameters(map, consumer, Constants.DEFAULT_KEY);
        appendParameters(map, consumer);
        appendParameters(map, this);
        Map<String, Object> attributes = null;
        if (CollectionUtils.isNotEmpty(methods)) {
            attributes = new HashMap<String, Object>();
            for (MethodConfig methodConfig : methods) {
                appendParameters(map, methodConfig, methodConfig.getName());
                String retryKey = methodConfig.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(methodConfig.getName() + ".retries", "0");
                    }
                }
                attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
            }
        }

        String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
        if (StringUtils.isEmpty(hostToRegistry)) {
            hostToRegistry = NetUtils.getLocalHost();
        } else if (isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        }
        map.put(REGISTER_IP_KEY, hostToRegistry);
        //=================================数据组装 End============================

        //创建代理
        ref = createProxy(map);

        String serviceKey = URL.buildKey(interfaceName, group, version);
        ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
        initialized = true;
    }

此方法代码很长,主要完成的配置加载,检查,以及创建引用的代理对象。这里要从 createProxy 开始看起。从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并 Invoker 实例。具体细节如下。

    /***
     * 创建代理
     * @param map
     * @return
     */
    @SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
    private T createProxy(Map<String, String> map) {
        if (shouldJvmRefer(map)) {
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {
            urls.clear(); // reference retry init will add url to urls, lead to OOM
            if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
                String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
                if (us != null && us.length > 0) {
                    for (String u : us) {
                        URL url = URL.valueOf(u);
                        if (StringUtils.isEmpty(url.getPath())) {
                            url = url.setPath(interfaceName);
                        }
                        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                            urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            } else { // assemble URL from register center's configuration
                // if protocols not injvm checkRegistry
                if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){
                    checkRegistry();
                    // 加载注册中心 url
                    List<URL> us = loadRegistries(false);
                    if (CollectionUtils.isNotEmpty(us)) {
                        for (URL u : us) {
                            URL monitorUrl = loadMonitor(u);
                            if (monitorUrl != null) {
                                map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                            }
                            // 添加 refer 参数到 url 中,并将 url 添加到 urls 中
                            urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        }
                    }
                    if (urls.isEmpty()) {
                        throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                    }
                }
            }

            //单个注册中心
            if (urls.size() == 1) {
                //从REF_PROTOCOL中获取接口对应的invoker
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
            // 多个注册中心或多个服务提供者,或者两者混合
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                // 获取所有的 Invoker
                for (URL url : urls) {
                // 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时 
                // 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
                    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
                    // 如果注册中心链接不为空,则将使用 AvailableCluster
                    URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
                    // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
                    // 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }
        }

        if (shouldCheck() && !invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        /**
         * @since 2.7.0
         * ServiceData Store
         */
        MetadataReportService metadataReportService = null;
        if ((metadataReportService = getMetadataReportService()) != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataReportService.publishConsumer(consumerURL);
        }
        // create service proxy
        //创建代理
        return (T) PROXY_FACTORY.getProxy(invoker);
    }

上面代码很多,不过逻辑比较清晰。
1、如果是本地调用,直接jvm 协议从内存中获取实例
2、如果只有一个注册中心,直接通过 Protocol 自适应拓展类构建 Invoker 实例接口
3、如果有多个注册中心,此时先根据 url 构建 Invoker。然后再通过 Cluster 合并多个 Invoker,最后调用 ProxyFactory 生成代理类

(2) 创建客户端
在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。Protocol 实现类有很多,这里分析DubboProtocol

 @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);
        //getClients->Netty的Client
        // create rpc invoker.
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

上面方法看起来比较简单,创建一个DubboInvoker。通过构造方法传入远程调用的client对象。默认情况下,Dubbo 使用 NettyClient 进行通信。接下来,我们简单看一下 getClients 方法的逻辑。

  private ExchangeClient[] getClients(URL url) {
        // whether to share connection
       // 是否共享连接
        boolean useShareConnect = false;
        // 获取连接数,默认为0,表示未配置
        int connections = url.getParameter(CONNECTIONS_KEY, 0);
        List<ReferenceCountExchangeClient> shareClients = null;
        // if not configured, connection is shared, otherwise, one connection for one service
        // 如果未配置 connections,则共享连接
        if (connections == 0) {
            useShareConnect = true;

            /**
             * The xml configuration should have a higher priority than properties.
             */
            String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
            connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
                    DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
            shareClients = getSharedClient(url, connections);
        }

        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (useShareConnect) {
            // 获取共享客户端
                clients[i] = shareClients.get(i);

            } else {
                //初始化客户端链接
                clients[i] = initClient(url);
            }
        }

        return clients;
    }

这里根据 connections 数量决定是获取共享客户端还是创建新的客户端实例,getSharedClient 方法中也会调用 initClient 方法,因此下面我们一起看一下这个方法

 private ExchangeClient initClient(URL url) {

        // client type setting.
        // 获取客户端类型,默认为 netty
        String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));

        url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
        // enable heartbeat by default
        url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT));

        // BIO is not allowed since it has severe performance issue.
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + "," +
                    " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }

        ExchangeClient client;
        try {
            // connection should be lazy
            // 获取 lazy 配置,并根据配置值决定创建的客户端类型
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
            // 创建懒加载 ExchangeClient 实例
                client = new LazyConnectExchangeClient(url, requestHandler);

            } else {
                // 创建普通 ExchangeClient 实例
                client = Exchangers.connect(url, requestHandler);
            }

        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
        }

        return client;
    }

initClient 方法首先获取用户配置的客户端类型,默认为 netty。下面我们分析一下 Exchangers 的connect 方法。

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        // 获取 Exchanger 实例,默认为 HeaderExchangeClient
        return getExchanger(url).connect(url, handler);
    }

如上,getExchanger 会通过 SPI 加载 HeaderExchangeClient 实例,这个方法比较简单.
接下来分析 HeaderExchangeClient 的实现

 @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // 这里包含了多个调用,分别如下: 
// 1. 创建 HeaderExchangeHandler 对象 
// 2. 创建 DecodeHandler 对象 
// 3. 通过 Transporters 构建 Client 实例 
// 4. 创建 HeaderExchangeClient 对象 

        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

这里的调用比较多,我们这里重点看一下 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 数量大于1,则创建一个 ChannelHandler 分发器
            handler = handlers[0];
        } else {
        // 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().connect(url, handler);
    }

如上,getTransporter 方法返回的是自适应拓展类,该类会在运行时根据客户端类型加载指定的Transporter 实现类。若用户未配置客户端类型,则默认加载 NettyTransporter,并调用该类的connect 方法。如下:

   @Override
    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }

(3) 注册
这里就已经创建好了NettyClient对象。关于 DubboProtocol 的 refer 方法就分析完了。接下来,继续分析 RegistryProtocol 的 refer 方法逻辑。

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 取 registry 参数值,并将其设置为协议头
        url = URLBuilder.from(url)
                .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
                .removeParameter(REGISTRY_KEY)
                .build();
        //获取注册中心信息
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        // 将 url 查询字符串转为 Map
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        // 获取 group 配置
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            // 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        // 调用 doRefer 继续执行服务引用逻辑
        return doRefer(cluster, registry, type, url);
    }

上面代码首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据group 配置决定 doRefer 第一个参数的类型。这里的重点是 doRefer 方法,如下:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 创建 RegistryDirectory 实例
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        // 设置注册中心和协议
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
        // 生成服务消费者链接
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
            directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        //订阅服务->调用DubboProtocol#protocolBindingRefer()方法
        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
       // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

如上,doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个Invoker

(4)创建代理对象
Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调
用。代理对象生成的入口方法为 ProxyFactory 的 getProxy,接下来进行分析。

 @Override
    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        return getProxy(invoker, false);
    }

    @Override
    public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
        Class<?>[] interfaces = null;
        // 获取接口列表
        String config = invoker.getUrl().getParameter(INTERFACES);
        if (config != null && config.length() > 0) {
        // 切分接口列表
            String[] types = COMMA_SPLIT_PATTERN.split(config);
            if (types != null && types.length > 0) {
            // 设置服务接口类和 EchoService.class 到 interfaces 中
                interfaces = new Class<?>[types.length + 2];
                interfaces[0] = invoker.getInterface();
                interfaces[1] = EchoService.class;
                for (int i = 0; i < types.length; i++) {
                    // TODO can we load successfully for a different classloader?.
                    // 加载接口类
                    interfaces[i + 2] = ReflectUtils.forName(types[i]);
                }
            }
        }
        if (interfaces == null) {
            interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
        }
        // 为 http 和 hessian 协议提供泛化调用支持,参考 pull request #1827
        if (!GenericService.class.isAssignableFrom(invoker.getInterface()) && generic) {
            int len = interfaces.length;
            Class<?>[] temp = interfaces;
            // 创建新的 interfaces 数组
            interfaces = new Class<?>[len + 1];
            System.arraycopy(temp, 0, interfaces, 0, len);
            // 设置 GenericService.class 到数组中
            interfaces[len] = com.alibaba.dubbo.rpc.service.GenericService.class;
        }
        // 调用重载方法
        return getProxy(invoker, interfaces);
    }

如上,上面大段代码都是用来获取 interfaces 数组的,我们继续往下看。getProxy(Invoker, Class<?>
[]) 这个方法是一个抽象方法,下面我们到 JavassistProxyFactory 类中看一下该方法的实现代码。

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

上面代码并不多,首先是通过 Proxy 的 getProxy 方法获取 Proxy 子类,然后创建InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例。InvokerInvocationHandler 实现 JDK 的 InvocationHandler 接口,具体的用途是拦截接口类调用。下面以 org.apache.dubbo.demo.DemoService 这个接口为例,来看一下该接口代理类代码大致是怎样的(忽略 EchoService 接口)。

package org.apache.dubbo.common.bytecode; 
public class proxy0 implements org.apache.dubbo.demo.DemoService { 
public static java.lang.reflect.Method[] methods; 
private java.lang.reflect.InvocationHandler handler; 
public proxy0() { 
}
public proxy0(java.lang.reflect.InvocationHandler arg0) { 
handler = $1; 
}
public java.lang.String sayHello(java.lang.String arg0) { 
Object[] args = new Object[1]; 
args[0] = ($w) $1; 
Object ret = handler.invoke(this, methods[0], args); 
return (java.lang.String) ret; 
} 
}

好了,到这里代理类生成逻辑就分析完了。整个过程比较复杂,大家需要耐心看一下。

2.4.3 总结
  1. 从注册中心发现引用服务:在有注册中心,通过注册中心发现提供者地址的情况下,
    ReferenceConfig 解析出的 URL 格式为: registry://registry- host:/org.apache.registry.RegistryService?refer=URL.encode(“conumer- host/com.foo.FooService?version=1.0.0”) 。

  2. 通过 URL 的registry://协议头识别,就会调用RegistryProtocol#refer()方法
    1 查询提供者 URL,如 dubbo://service-host/com.foo.FooService?version=1.0.0 ,来获取注册中心
    2 创建一个 RegistryDirectory 实例并设置注册中心和协议
    3 生成 conusmer 连接,在 consumer 目录下创建节点,向注册中心注册
    4 注册完毕后,订阅 providers,configurators,routers 等节点的数据
    5 通过 URL 的 dubbo:// 协议头识别,调用 DubboProtocol#refer() 方法,创建一个ExchangeClient 客户端并返回 DubboInvoker 实例

  3. 由于一个服务可能会部署在多台服务器上,这样就会在 providers 产生多个节点,这样也就会得到
    多个 DubboInvoker 实例,就需要 RegistryProtocol 调用 Cluster 将多个服务提供者节点伪装成
    一个节点,并返回一个 Invoker

  4. Invoker 创建完毕后,调用 ProxyFactory 为服务接口生成代理对象,返回提供者引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值