1. Java SPI 與 dubbo SPI

        共 14634字,需瀏覽 30分鐘

         ·

        2021-06-07 08:18

        點擊上方藍色字體,選擇“標星公眾號”

        優(yōu)質文章,第一時間送達

          作者 |  煢祇

        來源 |  urlify.cn/NzQBbq

        Java原生SPI

        面向接口編程+策略模式

        實現

        建立接口

        Robot

        public interface Robot {
            /**
             * 測試方法1
             */
            void sayHello();
        }
        多個實現類實現接口

        RobotA

        public class RobotA implements Robot {
            public RobotA() {
                System.out.println("Happy RobotA is loaded");
            }
            @Override
            public void sayHello() {
                System.out.println("i am a very very happy Robot ");
            }
            public void sayBye(){}
        }

        RobotB

        public class RobotB implements Robot {
            public RobotB() {
                System.out.println("SB RobotB is loaded");
            }
            @Override
            public void sayHello() {
                System.out.println("i am a da sha bi ");
            }
            public void sayBye(){}
        }
        配置實現類與接口

        META-INF/services目錄下建立一個以接口全限定名為名字的文件,里面的內容是實現類的全限定名

        原理

        通過ServiceLoader與配置文件中的全限定名加載所有實現類,根據迭代器獲取具體的某一個類

        我們通過對下面一段代碼的分析來說明

        ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class);
        serviceLoader.forEach(Robot::sayHello);

        load(Robot.class)這個方法的目的只是為了設置類加載器為線程上下文加載器,我們當然可以不這么做,直接調用load(Class service,ClassLoader loader)方法

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

        這個load方法其實也沒有做什么實質的事,僅僅是實例化了一個ServiceLoad對象返回罷了

        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();
        }
        public void reload() {
            //這里的provider是一個對于已實例化對象的緩存,為Map類型
                providers.clear();
                lookupIterator = new LazyIterator(service, loader);
            }

        沒有,這里僅僅只是檢驗了參數和權限這樣一些準備操作.然后實例化了一個LazyIterator

        這是LazyIterator的構造函數

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

        然后....,沒了,ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class);執(zhí)行完畢了,到這里,并沒有實例化我們所需要的Robot對象,而僅僅只是返回了一個ServiceLoader對象

        這時候如果我們去看serviceLoader的對象方法是這樣的

        有用的只有這三個方法,reload上面已經提到過,只是重新實例化一個對象而已.

        而另外兩個iterator()是個迭代器,foreach也只是用于迭代的語法糖罷了.如果我們debug的話,會發(fā)現foreach的核心依舊會變成iterator(),好了,接下來重點看iterator()

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

            };

        這個方法實際上是返回了一個Iterator對象.而通過這個Iterator,我們可以遍歷獲取我們所需要的Robot對象.

        我們來看其用于獲取對象的next方法

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

        這個方法是先在緩存里找,緩存里找不到,就需要用最開始的實例化的lookupIterator

        再來看看它的next方法

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

        這方法的核心是nextService,我們繼續(xù)看實現,這個方法比較長,我貼一部分核心

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

        hasNextService()判斷是否還可以繼續(xù)迭代,通過class.forName反射獲取實例,最后再加入到provider緩存中.于是基本邏輯就完成了.那nextName哪來的.是在hasNextService()中獲取的.

        依舊只有核心代碼

        //獲取文件
        String fullName = PREFIX + service.getName();
        if (loader == null)
            configs = ClassLoader.getSystemResources(fullName);
        else
            configs = loader.getResources(fullName);
        //解析文件配置
        while ((pending == null) || !pending.hasNext()) {
                        if (!configs.hasMoreElements()) {
                            return false;
                        }
                        pending = parse(service, configs.nextElement());
                    }
                    nextName = pending.next();

        根據前綴(即META-INF/services)和接口的全限定名去找到對應的配置文件.然后加載里面的配置,獲取具體實現類的名字.

        Dubbo增強SPI

        實現

        建立接口

        與原生SPI不同,dubbo需要加入@SPI注解

        Robot

        @SPI
        public interface Robot {
            /**
             * 測試方法1
             */
            void sayHello();
        }
        多個實現類實現接口

        RobotA

        public class RobotA implements Robot {
            public RobotA() {
                System.out.println("Happy RobotA is loaded");
            }
            @Override
            public void sayHello() {
                System.out.println("i am a very very happy Robot ");
            }
            public void sayBye(){}
        }

        RobotB

        public class RobotB implements Robot {
            public RobotB() {
                System.out.println("SB RobotB is loaded");
            }
            @Override
            public void sayHello() {
                System.out.println("i am a da sha bi ");
            }
            public void sayBye(){}
        }
        配置實現類與接口

        META-INF/dubbo目錄下建立一個以接口全限定名為名字的文件,里面的內容是自定義名字與類的全限定名的鍵值對,舉個例子

        robotA = cn.testlove.double_dubbo.inter.impl.RobotA
        robotB=cn.testlove.double_dubbo.inter.impl.RobotB

        原理

        我們通過對下列代碼的調用來進行分析

        ExtensionLoader<Robot> extensionLoader= ExtensionLoader.getExtensionLoader(Robot.class);
        Robot robotB = extensionLoader.getExtension("robotB");

        第一句代碼沒什么好說的,只是獲取一個RobotExtensionLoader對象并且緩存在Map中,下次如果是同樣的接口可以直接從map中獲取

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

        再來看第二句代碼

        //從緩存中找
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        //雙重檢查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }

        首先從緩存里找,找不到再創(chuàng)建一個新的對象。

        再看createExtension(name)方法

         Class<?> clazz = getExtensionClasses().get(name);

        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        initExtension(instance);
        return instance;

        注意對于Class<?> clazz = getExtensionClasses().get(name);這一句的理解,這一句是獲取配置文件中所有類的Class實例,而不是獲取所有擴展類的實例。

        接下來的流程其實也就簡單了從EXTENSION_INSTANCES緩存中獲取instance實例,如果沒有,就借助Class對象實例化一個,再放入緩存中

        接著用這個instance去實例化一個包裝類然后返回.自此,一個我們需要的對象產生了.

        最后我們看看getExtensionClasses()這個方法

        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;

        這里的classes就是用來存各個擴展類Class的Map緩存,如果不存在的話,會調用loadExtensionClasses();去加載,剩下的就是找到對應路徑下的配置文件,獲取全限定名了

        上文我在分析Dubbo SPI時,多次提到Map,緩存二詞,我們可以具體有以下這些.其實看名字就大概知道作用了

        private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
        private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>()    
        private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
        private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
        private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
        private volatile Class<?> cachedAdaptiveClass = null;
        private String cachedDefaultName;

        對比原生的java SPI,dubbo的無疑更靈活,可以按需去加載某個類,也可以很便捷的通過自定義的名字去獲取類.而且Dubbo還支持setter注入.這點以后再講.

        最后提一個問題,java原生的SPI只有在用iterator遍歷到的時候才會實例化對象,那能不能在遇到自己想要的實現對象時就停止遍歷,避免不必要的資源消耗呢?








        瀏覽 69
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 久草中文在线视频 | 国产精品久久久久夜色 | 韩国一区二区在线观看 | 国产九九九精品婷婷影视在线观看 | 国产熟妇毛多 久久久久一区 |