dubboシリーズ(一)SPI

53938 ワード

1.紹介
SPIはサービスプロバイダインターフェースと呼ばれ、サービス発見メカニズムである.SPIの本質は,インタフェース実装クラスのフルネームをファイルに配置し,サービスローダによってプロファイルを読み出し,実装クラスをロードすることである.これにより、実行時に実装クラスを動的にインタフェースに置き換えることができます.そのため、SPIメカニズムを通じてプログラムに拡張機能を提供することができます.DubboはSPIメカニズムですべてのコンポーネントをロードします.しかし、DubboはJavaオリジナルのSPIメカニズムを使用するのではなく、ニーズをよりよく満たすために強化されています.
2.Dubbo SPI例
DubboはJava SPIではなく、より強力なSPIメカニズムを再実現しました.Dubbo SPIの関連ロジックはExtensionLoaderクラスにカプセル化されており,ExtensionLoaderにより指定された実装クラスをロードすることができる.Dubbo SPIに必要なプロファイルは、META-INF/dubboパスの下に配置する必要があります.コードと構成内容は次のとおりです.  拡張点インタフェースを定義し、インタフェースに@SPIをマークする必要がある
@SPI
public interface Animal {
     
    public void speak();
}

2つの実装クラスに対応する
public class Bird implements Animal{
     
    @Override
    public void speak() {
     
        System.out.println("I'm a bird");
    }
}
public class Cat implements Animal{
     
    @Override
    public void speak() {
     
        System.out.println("I'm a cat");
    }
}

  プロファイル、mavenプロジェクトの場合はresources/META-INF/dubboの下に置く必要があります.ファイル名はAnimalインタフェースの完全なクラス名で、ファイル内容はkey=valueのフォーマットで、具体的には以下の通りです.
bird=com.hy.study.spi.Bird
cat=com.hy.study.spi.Cat

テストクラス:
public class SPIDemo {
     
    public static void main(String[] args) {
     
        ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
        Animal animal = extensionLoader.getExtension("bird");
        animal.speak();
    }
}

実行結果:I’m a bird 
3.Dubbo SPIソース分析
 上記のコードでは、まずExtensionLoaderのgetExtensionLoaderメソッドでExtensionLoaderインスタンスを取得し、次に拡張クラスオブジェクトをExtensionLoaderのgetExtensionメソッドで取得します.ここで、getExtensionLoaderメソッドは、拡張クラスに対応するExtensionLoaderをキャッシュから取得するために使用され、キャッシュがヒットしない場合、新しいインスタンスを作成します.この方法の論理は比較的簡単で,本章では解析を行わない.次に,拡張クラスオブジェクトの取得過程を,ExtensionLoaderのgetExtension手法を入口として詳細に分析する.
public T getExtension(String name) {
     
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
     
        //           
        return getDefaultExtension();
    }
    // Holder,    ,        
    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  
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

上のコードの論理は比較的簡単で、まずキャッシュをチェックし、キャッシュがヒットしないと拡張オブジェクトを作成します.次に、拡張オブジェクトを作成するプロセスを見てみましょう.
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, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //         
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
     
            //      Wrapper   
            for (Class<?> wrapperClass : wrapperClasses) {
     
                //     instance        Wrapper      ,        Wrapper   。
                //     Wrapper        ,    Wrapper         instance   
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
     
        throw new IllegalStateException("...");
    }
}

 createExtensionメソッドの論理はやや複雑で、次の手順が含まれています.
  1)getExtensionClassesで取得したすべての拡張クラス  2)反射による拡張オブジェクト  3)拡張オブジェクトへの依存  4)拡張オブジェクトを対応するWrapperオブジェクトにラップ
重要な部分が来て、すべての拡張クラスを取得します.拡張クラスを名前で取得する前に、まずプロファイルに基づいて拡張クラス名から拡張クラスへのマッピングリレーションシップテーブル(Map)を解析し、その後、拡張クラス名に基づいてマッピングリレーションシップテーブルから対応する拡張クラスを取り出す必要があります.関連プロセスのコード分析は次のとおりです.
private Map<String, Class<?>> 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;
}

  ここでもキャッシュをチェックし、キャッシュがヒットしない場合はsynchronizedでロックします.ロックをかけた後、キャッシュを再確認し、空を判定します.この時点でclassesがnullのままの場合、loadExtensionClassesによって拡張クラスがロードされます.次に、loadExtensionClassesメソッドの論理を分析します.
private Map<String, Class<?>> loadExtensionClasses() {
     
    //    SPI   ,    type        getExtensionLoader       
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
     
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
     
            //   SPI         
            String[] names = NAME_SEPARATOR.split(value);
            //    SPI         ,        
            if (names.length > 1) {
     
                throw new IllegalStateException("more than 1 default extension name on extension...");
            }

            //       ,   getDefaultExtension   
            if (names.length == 1) {
     
                cachedDefaultName = names[0];
            }
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    //              
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

 loadExtensionClassesメソッドでは、SPI注釈の解析と、loadDirectoryメソッドを呼び出して指定したフォルダプロファイルをロードする2つのことを行いました.SPI注釈解析プロセスは比較的簡単で,多くのことを言う必要はない.次に、loadDirectoryがどのようなことをしたかを見てみましょう.
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
     
    // fileName =       + type      
    String fileName = dir + type.getName();
    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("...");
    }
}

 loadDirectoryメソッドは、classLoaderを使用してすべてのリソースリンクを取得してから、loadResourceメソッドを使用してリソースをロードします.引き続き、loadResourceメソッドの実装を見てみましょう.
private void loadResource(Map<String, Class<?>> extensionClasses, 
	ClassLoader classLoader, java.net.URL resourceURL) {
     
    try {
     
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(resourceURL.openStream(), "utf-8"));
        try {
     
            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 = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
     
                            //    ,    loadClass         
                            loadClass(extensionClasses, resourceURL, 
                                      Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
     
                        IllegalStateException e = new IllegalStateException("Failed to load extension class...");
                    }
                }
            }
        } finally {
     
            reader.close();
        }
    } catch (Throwable t) {
     
        logger.error("Exception when load extension class...");
    }
}

 loadResourceメソッドは、プロファイルを読み取り解析し、クラスを反射してロードし、最後にloadClassメソッドを呼び出して他の操作を行うために使用されます.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("...");
    }

    //           Adaptive   
    if (clazz.isAnnotationPresent(Adaptive.class)) {
     
        if (cachedAdaptiveClass == null) {
     
            //    cachedAdaptiveClass  
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
     
            throw new IllegalStateException("...");
        }
        
    //    clazz     Wrapper   
    } else if (isWrapperClass(clazz)) {
     
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
     
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        //    clazz   cachedWrapperClasses    
        wrappers.add(clazz);
        
    //        ,   clazz          
    } else {
     
        //    clazz           ,    ,     
        clazz.getConstructor();
        if (name == null || name.length() == 0) {
     
            //    name   ,     Extension       name,           name
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
     
                throw new IllegalStateException("...");
            }
        }
        //    name
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
     
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
     
                //       Activate   ,    names            ,
                //    name   Activate          
                cachedActivates.put(names[0], activate);
            }
            for (String n : names) {
     
                if (!cachedNames.containsKey(clazz)) {
     
                    //    Class         
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
     
                    //       Class      
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
     
                    throw new IllegalStateException("...");
                }
            }
        }
    }
}

以上のように、loadClassメソッドでは、cachedAdaptiveClass、cachedWrapperClass、cachedNameなど、異なるキャッシュが動作します.それ以外に、この方法には他の論理はありません.ここで、キャッシュクラスのロードに関するプロセスの分析が完了します.全体の過程は特に複雑なところはありません.みんなは順番に分析すればいいです.分からないところは調整してもいいです.