• Dubbo源码分析之SPI


    背景

    在Dubbo源码学习过程中,首先就遇到了spi机制,看了一下别人写的介绍,感觉还是基础的,而且它在jdbc中也有应用。但我却感觉很陌生。总之Java基础需要不断加强,下面总结一下spi。

    一、别人已经做了不错的总结了,参考文章
    二、使用demo
    1.项目java/main/resources/META-INF/services目录下新建文本文件,文件名为com.alibaba.dubbo.cache.CacheFactory;
    2.刚才文件中的文本内容为
            com.alibaba.dubbo.cache.support.jcache.JCacheFactory
            com.alibaba.dubbo.cache.support.lru.LruCacheFactory
    3.测试类
    public class SpiTest {
        public static void main(String[] args){
            ServiceLoader<CacheFactory> load = ServiceLoader.load(CacheFactory.class);   //ServiceLoader在java.util包中
            Iterator<CacheFactory> iterator = load.iterator();
            while(iterator.hasNext()){
                CacheFactory next = iterator.next();
                System.out.println(next);
            }
        }
    }
    
    4.运行效果如下
    com.alibaba.dubbo.cache.support.jcache.JCacheFactory@3f99bd52
    com.alibaba.dubbo.cache.support.lru.LruCacheFactory@7adf9f5f
    
    Process finished with exit code 0
    
    三、spi源码分析

    这么一个简单的例子初看也不知道是干嘛的。感觉就是编写一个接口和几个实现类,然后再根据规则定义个配置文件,最后程序能够根据接口和配置文件,得到所有实现类的实例对象,底层涉及到反射技术。但是具体怎么用?以及Dubbo中spi扩展机制又是什么?这些需要一点点理解消化。先不扯那么远,先来把这个小例子的源码看看,毕竟千里之行始于当下。

    <1>ServiceLoader.load(CacheFactory.class)

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

    继续

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

    继续

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

    有必要看看ServiceLoad的成员方法了,如下

    public final class ServiceLoader<S>
        implements Iterable<S>
    {
    
        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;
    ...
    }
    

    继续

        public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    

    继续,来看看这个LazyIterator类

    小总结:到这里ServiceLoader.load(CacheFactory.class)源码涉及就这多么,关键就是初始化了LazyIterator类(ServiceLoad的私有内部类)的lookupIterator对象,这个对象看样子好像会比较有用。

    <2>load.iterator()

        public Iterator<S> iterator() {
            return new Iterator<S>() {               //返回了一个迭代器A
    
                Iterator<Map.Entry<String,S>> knownProviders
                    = providers.entrySet().iterator();     //注意,这个providers是类初始化new的一个空的Map,什么时候真正赋值呢?这里需要注意一下!
    
                public boolean hasNext() {
                    if (knownProviders.hasNext())
                        return true;
                    return lookupIterator.hasNext();   //迭代器A的hasNext()方法真正调用的是前面分析的lookupIterator对象的hasNext方法
                }
    
                public S next() {
                    if (knownProviders.hasNext())
                        return knownProviders.next().getValue();
                    return lookupIterator.next();      ////迭代器A的next()方法真正调用的是前面分析的lookupIterator对象的next方法
                }
    
                public void remove() {
                    throw new UnsupportedOperationException();  
                }
    
            };
        }
    

    小总结:Serviceload返回的迭代器,实际是对前面分析中关键对象lookupIterator的封装。所以关键点就是这个私有内部类的实现了。下面继续对它分析。

    <3>LazyIterator私有内部类源码

     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());    //Class类中的cast(Object obj),强转
                    providers.put(cn, p);                   //这里解答了前面提到的providers真正初始话的问题。
                    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();
            }
    
        }
    

    小总结:底层果然是反射,先分析到这

    四、JDBC中应用
    String url = "jdbc:mysql:///consult?serverTimezone=UTC";
    String user = "root";
    String password = "root";
    
    Class.forName("com.mysql.jdbc.Driver");   //新的不需要,但有也能运行
    Connection connection = DriverManager.getConnection(url, user, password);
    

    新版JDBC不再需要Class.forName()来显式加载jdbc驱动,底层就是利用spi技术提前加载好了。下面是我源码总结图

    小总结:这篇文章仅仅小小总结了一下spi,希望以后继续更新更多spi的实际应用分析。回到dubbo学习主线吧(20200309)

  • 相关阅读:
    ResourceBundle读取utf-8格式properties 中文乱码
    jquery checkbox选中
    扩展RBAC用户角色权限设计方案<转>
    Java调用doNet webService方法
    Mybatis批量更新<转>
    Json转list,两种包,两种方式
    win8.1 64位安装oracle10g客户端心得
    关于JXL读写以及修改EXCEL文件<转>
    Oracle主表列表上显示从表字段拼成的字符串
    ExtJS获取Grid的行数
  • 原文地址:https://www.cnblogs.com/leeethan/p/12449008.html
Copyright © 2020-2023  润新知