Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)

环境搭建

  1. Github上下载Dubbo最新发布版本,楼主下载版本为2.5.7。

cd到源码解压目录,maven编译,命令为:

mvn clean install -Dmaven.test.skip

生成Intellij idea相关配置文件,命令为:

mvn idea:idea
  1. 双击运行生成的dubbo-parent.ipr文件

Java SPI

SPI是Service Provider Interfaces的简称,是Java中定义的一个很重要的规范,SPI使得应用之间变得更灵活、程序间更解耦。

一般在应用中会定义一个接口,具体的实现由对应的实现类去完成,即服务提供者(Service Provider)。模块与模块之间基于接口编程,模块之间不能对实现类进行硬编码、不能在代码里写具体的实现类,否则就违反了“可插拔原则”,如果要替换一种实现,就需要修改代码。此时,SPI提供了一种服务发现机制,完美解决了这个问题。

SPI机制基本思路是通过JDK提供的java.util.ServiceLoader类去主动发现服务,不需要硬编码具体的类。

当服务接口有多个实现类(即服务提供者)时,在jar包的META-INF/services/目录下创建一个以服务接口命名的文件,文件内容是该服务接口的具体实现类的全类名,一行记录是一个实现类的全类名。当外部程序装配这个模块时,通过jar包的META-INF/services/目录里的配置文件就可以找到具体的实现类名,从而进行实例化、完成模块的注入。

Java SPI 示例

定义服务接口:

package jdkspi;

public interface WorkerService {
    void work();
}

该服务接口的两个实现类如下:

package jdkspi.impl;

import jdkspi.WorkerService;

public class WorkerServiceA implements WorkerService {

    public void work() {
        System.out.println("work hard ......");
    }
}
package jdkspi.impl;

import jdkspi.WorkerService;

public class WorkerServiceB implements WorkerService {

    public void work() {
        System.out.println("work lazy ......");
    }
}

在resources下新建目录META-INF/services/,在目录下新建文件。文件名为服务接口全名jdkspi.WorkerService,具体内容如下

jdkspi.impl.WorkerServiceA      //服务接口实现类全名
jdkspi.impl.WorkerServiceB      //服务接口实现类全名

测试示例执行入口:

package jdkspi;

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

public class Test {

    public static void main(String[] args) {
        ServiceLoader<WorkerService> serviceLoader = ServiceLoader.load(WorkerService.class);
        WorkerService service = null;
        Iterator<WorkerService> iterator = serviceLoader.iterator();

        while (iterator.hasNext()) {
            service = iterator.next();
            service.work();
        }
    }
}

执行测试示例后,结果如下:

work hard ......
work lazy ......

Process finished with exit code 0

ServiceLoader源码分析

ServiceLoader是一个final类,不能被继承,实现了Iterable接口,可以遍历,如下:

public final class ServiceLoader<S> implements Iterable<S>

ServiceLoader属性如下:

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
  • PREFIX: 定义了配置文件的路径,是一个final类型常量,不能设置不能更改。表面Java SPI配置文件默认放在META-INF/services/路径下
  • service: 定义服务接口类,final类型变量,一旦被赋值便不能修改,由load方法传入
  • loader: 类加载器,一旦被赋值便不能修改
  • acc: 访问控制上下文,一旦被赋值比昂不修改
  • providers: 存储服务提供者,也即具体实现类。存储的顺序为配置文件中实现类的排列先后顺序
  • lookupIterator: 迭代器,实现延迟加载的效果

ServiceLoader只有一个构造器,且是内部构造器。不能再外部直接通过new命令创建实例对象。如下:

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

ServiceLoader提供了三种静态类方法来创建实例对象。如下:

    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
        return new ServiceLoader<>(service, loader);
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }
  • load(Class<S> service): 利用当前线程持有的ClassLoader创建实例
  • load(Class<S> service, ClassLoader loader): 利用指定的ClassLoader创建实例
  • loadInstalled(Class<S> service): 利用系统顶级ClassLoader创建实例

ServiceLoader提供iterator()方法用以生成迭代器。迭代器中方法内部具体由lookupIterator实现。如下:

    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

lookupIterator是LazyIterator的对象实例,LazyIterator是一个内部类,实现了Iterator接口,源码如下:

    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

从上面源码中,不难发现:服务提供者的实例化过程是在具体调用时进行的,延迟加载。 Java SPI机制的ServiceLoader缺点:

  1. 每次获取一个实现类都必须遍历加载所有的实现类,即使是不想使用的实现类也加载了,造成了资源的浪费。
  2. 不能定向获取对应的实现类,必须iterator遍历查找,比较慢

Dubbo拓展机制

Dubbo拓展机制应用的就是Java SPI的思想。Java SPI配置文件中一条记录是一个实现类全名,但Dubbo配置文件中存储的是key-value键值对,value存储的是实现类全名。示例如下:

ls=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ListTelnetHandler
ps=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.PortTelnetHandler
cd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ChangeTelnetHandler
pwd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CurrentTelnetHandler
invoke=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler
trace=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.TraceTelnetHandler
count=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CountTelnetHandler

类似Java SPI机制的ServiceLoader,Dubbo中也有一个拓展加载器ExtensionLoader。ExtensionLoader中定义了配置文件的存储路径:

    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

ExtensionLoader构造器也是内部构造器,在外部不能直接通过new命令来创建对象实例:

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

同样,ExtensionLoader提供静态类方法getExtensionLoader来生成实例:

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

调用getExtension方法可以根据name值获取指定的拓展实现类,实例化后的拓展实现类以Holder类封装存储在cachedInstances中,cachedInstances是ConcurrentMap<String, Holder<Object>>变量。

    /**
     * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}.
     *
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

getExtension方法中采用了double-check机制,拓展实现类的实例化是在createExtension方法中完成的:

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                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 + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

方法实现大体流程为:

  1. name作为key值,获取对应的class。在getExtensionClasses中是有做同步处理的
  2. 根据得到的class创建实例
  3. 对实例化对象进行依赖注入
  4. 对依赖注入后的实例化对象进行包装

依赖注入及包装源码如下:

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

此即是Dubbo拓展机制的大体流程,跟Java SPI机制非常类似,可看作Java SPI机制的一个优化与拓展。下一节将探讨provider服务的发布过程

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏林德熙的博客

C# 复制列表

List<Fex> b = a.ToList(); 可以把列表a到列表b,对b进行删除、添加,不会对a造成元素改变。

541
来自专栏GreenLeaves

C# 特性(Attribute)之Flag特性

本文参考自C# 位域[flags],纯属读书笔记,加深记忆 [Flags]的微软解释是“指示可以将枚举作为位域(即一组标志)处理。”其实就是在编写枚举类型时,上...

1948
来自专栏芋道源码1024

Spring Webflux —— 源码阅读之 handler 包

查找给定请求的handler,如果找不到特定的请求,则返回一个空的Mono。这个方法被getHandler(org.springframework.web.se...

1355
来自专栏大内老A

深入理解C# 3.x的新特性(2):Extension Method[下篇]

四、Extension Method的本质 通过上面一节的介绍,我们知道了在C#中如何去定义一个Extension Method:它是定义在一个Static c...

1859
来自专栏个人随笔

C# 序列化与反序列化

对象持久化到文本文件,策略是:将对象的属性值打散,拆解,分别存储。 序列化:  保存对象的"全景图"  序列化是将对象转换为可保存或可传输的格式的过程  三种:...

3689
来自专栏大内老A

Enterprise Library深入解析与灵活应用(9):个人觉得比较严重的关于CachingCallHandler的Bug

微软EnterLib的Policy Injection Application Block(PIAB)是一个比较好用的轻量级的AOP框架,你可以通过创建自定义的...

1899
来自专栏Java与Android技术栈

用Kotlin的方式来处理网络异常

之前的文章 RxJava处理业务异常的几种方式 曾经介绍过 Retrofit 的异常可以有多种处理方式。

602
来自专栏我和未来有约会

(保存)C#基础概念二十五问

注:本文部份资料来自网络,如有侵权,请与我联系,我会在第一时间声明引用或将其删除!     当初学 C# 时是找个人大概问了一下数据类型和分支语句就开始做项目了...

1858
来自专栏青玉伏案

IOS开发之自定义Button(集成三种回调模式)

  前面在做东西的时候都用到了storyboard,在今天的代码中就纯手写代码自己用封装个Button。这个Button继承于UIView类,在封装的时候用上啦...

2138
来自专栏GuZhenYin

[干货来袭]C#7.0新特性(VS2017可用)

前言 微软昨天发布了新的VS 2017 ..随之而来的还有很多很多东西... .NET新版本 ASP.NET新版本...等等..太多..实在没消化.. 分享一下...

1929

扫码关注云+社区