Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)
環(huán)境搭建
- Github上下載Dubbo最新發(fā)布版本,樓主下載版本為2.5.7。
- cd到源碼解壓目錄,maven編譯,命令為:
mvn clean install -Dmaven.test.skip
- 生成Intellij idea相關(guān)配置文件,命令為:
mvn idea:idea
- 雙擊運行生成的dubbo-parent.ipr文件
Java SPI
SPI是Service Provider Interfaces的簡稱,是Java中定義的一個很重要的規(guī)范,SPI使得應(yīng)用之間變得更靈活、程序間更解耦。
一般在應(yīng)用中會定義一個接口,具體的實現(xiàn)由對應(yīng)的實現(xiàn)類去完成,即服務(wù)提供者(Service Provider)。模塊與模塊之間基于接口編程,模塊之間不能對實現(xiàn)類進行硬編碼、不能在代碼里寫具體的實現(xiàn)類,否則就違反了“可插拔原則”,如果要替換一種實現(xiàn),就需要修改代碼。此時,SPI提供了一種服務(wù)發(fā)現(xiàn)機制,完美解決了這個問題。
SPI機制基本思路是通過JDK提供的
java.util.ServiceLoader類去主動發(fā)現(xiàn)服務(wù),不需要硬編碼具體的類。
當(dāng)服務(wù)接口有多個實現(xiàn)類(即服務(wù)提供者)時,在jar包的META-INF/services/目錄下創(chuàng)建一個以服務(wù)接口命名的文件,文件內(nèi)容是該服務(wù)接口的具體實現(xiàn)類的全類名,一行記錄是一個實現(xiàn)類的全類名。當(dāng)外部程序裝配這個模塊時,通過jar包的META-INF/services/目錄里的配置文件就可以找到具體的實現(xiàn)類名,從而進行實例化、完成模塊的注入。
Java SPI 示例
定義服務(wù)接口:
package jdkspi;
public interface WorkerService {
    void work();
}
該服務(wù)接口的兩個實現(xiàn)類如下:
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/,在目錄下新建文件。文件名為服務(wù)接口全名jdkspi.WorkerService,具體內(nèi)容如下
jdkspi.impl.WorkerServiceA      //服務(wù)接口實現(xiàn)類全名
jdkspi.impl.WorkerServiceB      //服務(wù)接口實現(xiàn)類全名
測試示例執(zhí)行入口:
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();
        }
    }
}
執(zhí)行測試示例后,結(jié)果如下:
work hard ......
work lazy ......
Process finished with exit code 0
ServiceLoader源碼分析
ServiceLoader是一個final類,不能被繼承,實現(xiàn)了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類型常量,不能設(shè)置不能更改。表面Java SPI配置文件默認放在META-INF/services/路徑下
- service: 定義服務(wù)接口類,final類型變量,一旦被賦值便不能修改,由load方法傳入
- loader: 類加載器,一旦被賦值便不能修改
- acc: 訪問控制上下文,一旦被賦值比昂不修改
- providers: 存儲服務(wù)提供者,也即具體實現(xiàn)類。存儲的順序為配置文件中實現(xiàn)類的排列先后順序
- lookupIterator: 迭代器,實現(xiàn)延遲加載的效果
ServiceLoader只有一個構(gòu)造器,且是內(nèi)部構(gòu)造器。不能再外部直接通過new命令創(chuàng)建實例對象。如下:
    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提供了三種靜態(tài)類方法來創(chuàng)建實例對象。如下:
    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): 利用當(dāng)前線程持有的ClassLoader創(chuàng)建實例
- load(Class<S> service, ClassLoader loader): 利用指定的ClassLoader創(chuàng)建實例
- loadInstalled(Class<S> service): 利用系統(tǒng)頂級ClassLoader創(chuàng)建實例
ServiceLoader提供iterator()方法用以生成迭代器。迭代器中方法內(nèi)部具體由lookupIterator實現(xiàn)。如下:
    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是一個內(nèi)部類,實現(xiàn)了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();
        }
    }
從上面源碼中,不難發(fā)現(xiàn):服務(wù)提供者的實例化過程是在具體調(diào)用時進行的,延遲加載。
Java SPI機制的ServiceLoader缺點:
- 每次獲取一個實現(xiàn)類都必須遍歷加載所有的實現(xiàn)類,即使是不想使用的實現(xiàn)類也加載了,造成了資源的浪費。
- 不能定向獲取對應(yīng)的實現(xiàn)類,必須iterator遍歷查找,比較慢
Dubbo拓展機制
Dubbo拓展機制應(yīng)用的就是Java SPI的思想。Java SPI配置文件中一條記錄是一個實現(xiàn)類全名,但Dubbo配置文件中存儲的是key-value鍵值對,value存儲的是實現(xiàn)類全名。示例如下:
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構(gòu)造器也是內(nèi)部構(gòu)造器,在外部不能直接通過new命令來創(chuàng)建對象實例:
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
同樣,ExtensionLoader提供靜態(tài)類方法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;
    }
調(diào)用getExtension方法可以根據(jù)name值獲取指定的拓展實現(xiàn)類,實例化后的拓展實現(xiàn)類以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機制,拓展實現(xiàn)類的實例化是在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);
        }
    }
方法實現(xiàn)大體流程為:
- name作為key值,獲取對應(yīng)的class。在getExtensionClasses中是有做同步處理的
- 根據(jù)得到的class創(chuàng)建實例
- 對實例化對象進行依賴注入
- 對依賴注入后的實例化對象進行包裝
依賴注入及包裝源碼如下:
    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機制的一個優(yōu)化與拓展。下一節(jié)將探討provider服務(wù)的發(fā)布過程
總結(jié)
以上是生活随笔為你收集整理的Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 1Z0-053 争议题目解析682
- 下一篇: whois 查询 API
