Service Provider Framework ? Interface ?


「Item 1.ジェネレータではなく静的メソッドを考慮する」を学習する過程で、Service Provider Frameworkについて個別に説明します.

Service Provider Framework


Service Provider Patternを実装するフレームワークをService Provider Frameworkと呼びます.
簡単に言えば、フレームワークは、クライアントがクライアントに所望のサービスを提供するインプリメンテーションの役割を制御し、クライアントをインプリメンテーションから分離する.
典型的な例はJDBCである.
ただし,Javaは学習や実装を必要とせず,Java SE 6からサービスプロバイダFrameworkを提供する.これはService Provider InterfaceというAPIです.
最も重要なのは、SPIを使用することは、拡張性のあるアプリケーションを作成するためです.
SPIを使用して拡張性のあるアプリケーションを作成する方法を見てみましょう.

Java SE SPIによるアプリケーションの拡張


拡張可能なアプリケーションとは、既存のコードベースを変更せずに拡張できるアプリケーションです.
つまり、新しいプラグインやモジュールを使用して、いくつかの機能をさらに拡張することができます.開発者、ソフトウェアベンダー、およびお客様は、Java Archive(JAR)ファイルをアプリケーションのクラスパスまたはアプリケーションの特定の拡張ディレクトリに追加することで、新しい機能またはAPIを追加できます.
拡張可能なサービスを使用してアプリケーションを作成する方法を見てみましょう.これらのサービスは、既存のアプリケーションに変更する必要のないサービス実施形態を提供することができます.
ここで提供される拡張可能なアプリケーションの例はword processorであり、ユーザーが新しい辞書を追加したり、スペルチェックを追加したりすることができます.この例では、word processorは、他の開発者、さらにはお客様自身の機能を実装するために、辞書とスペルチェック機能を拡張することができます.

Terms and definitions


拡張可能なアプリケーションを理解するために、いくつかの重要な言葉と定義があります.
  • Service
    -特定のアプリケーションの機能および機能にアクセスするためのインタフェースまたはクラス.
  • クライアントの観点から、アクセス機能に使用できるエントリーポイントです.
  • サービスは、機能のインタフェースを定義し、インプリメンテーションプログラムに戻る方法を定義することができる.Word Processorの例では、辞書サービスは単語の定義を定義し、辞書を返す方法を定義することができます.ただし、内部機能は定義されません.逆に、それは機能を実装するService Providerに依存する.
  • Service Provider Interface(SPI)
    -サービス定義の共通インタフェースと抽象クラス.SPIは、アプリケーションが使用できるクラスまたはメソッドを定義します.
  • Service Provider
    −SPIの実施形態.拡張性のあるアプリケーションでは、開発者、ベンダー、およびお客様が既存のアプリケーションを変更せずにサービスプロバイダを追加できます.//追加画像
  • 単語定義自体が理解されていない場合は、例を用いて再編成します.

    Dictionary Example Requirement


    ワードプロセッサとエディタでdictionaryサービスを設計する方法を考えてみましょう.
    1つの方法は、サービスをDictionaryServiceというクラスとして定義し、Dictionaryというサービスプロバイダインタフェースを定義することである.
    DictionaryServiceでは、モノトーンDictionaryオブジェクトを提供します.このオブジェクトは、Dictionaryプロバイダから単語の定義を取得できます.Dictionary Service Clientは、サービスインスタンスを使用してDictionary Service Providerをナビゲートし、インスタンスとして使用します.
    word processor開発者は、基礎的で汎用的なdictionary既存商品であっても、法律用語または技術用語を含む専門的な辞書を提供することを要求しています.お客様は、アプリケーションの新しい辞書を購入または作成し、アプリケーションに追加できます.
    上記のように要求を定義できます.

    DictionaryServiceDemo Project Structure


    DictionaryServiceDemo Sampleでは、Dictionaryサービスを実装する方法、Dictionary Service Providerを作成する方法、およびサービスをテストするための簡単なDictionary Service Clientを作成する方法を示します.

    Oracle Tutorial
    DictionaryServiceDemo.zip
    「」を参照してください.

    例について


    定義
  • サービスプロバイダインタフェース
  • package dictionary.spi;
    public interface Dictionary {
    	public String getDefinition(String word);
    }
    私たちのプロジェクトはSPI Dictionary.javaを定義します.1つのメソッドのみが含まれます.コードを表示することで、SPIとは何かをよりよく知ることができます.私たちが提供するオブジェクトインタフェースはSPIと呼ばれています.
  • サービスプロバイダに提供するためのサービスを定義する実装
    package dictionary;
    
    import dictionary.spi.Dictionary;
    import java.util.Iterator;
    import java.util.ServiceConfigurationError;
    import java.util.ServiceLoader;
    
    public class DictionaryService {
    
        private static DictionaryService service;
        private ServiceLoader<Dictionary> loader;
    
        private DictionaryService() {
            loader = ServiceLoader.load(Dictionary.class);
        }
    
        public static synchronized DictionaryService getInstance() {
            if (service == null) {
                service = new DictionaryService();
            }
            return service;
        }
    
    
        public String getDefinition(String word) {
            String definition = null;
    
            try {
                Iterator<Dictionary> dictionaries = loader.iterator();
                while (definition == null && dictionaries.hasNext()) {
                    Dictionary d = dictionaries.next();
                    definition = d.getDefinition(word);
                }
            } catch (ServiceConfigurationError serviceError) {
                definition = null;
                serviceError.printStackTrace();
    
            }
            return definition;
        }
    }
    DictionaryServiceはSingletonで、プログラムにはインスタンスが1つしか残っていません.これも、Dictionary ServiceProviderを使用するユーザーの切り込みポイントです.
    注意が必要なのはJava SEのクラスServiceLoaderです.getDifintionを呼び出すと、SPI(Interface Dictionary)が実装されているパーティションプロバイダで検索する単語を探します.
    dictionaryサービスは、ServiecLaoder.loadメソッドを使用してtarget classを検索します.デフォルトでは、loadメソッドはアプリケーションクラスパスに移動します.
    定義
  • サービスプロバイダ
  • サービスプロバイダを定義するには、SPIというDictionaryインタフェースを実装する必要があります.最もシンプルなSample GenericDictionary.Javaのコードを見てみましょう.
    package dictionary;
    
    import dictionary.spi.Dictionary;
    import java.util.SortedMap;
    import java.util.TreeMap;
    
    public class GeneralDictionary implements Dictionary {
    
        private SortedMap<String, String> map;
        
        public GeneralDictionary() {
            map = new TreeMap<String, String>();
            map.put(
                "book",
                "a set of written or printed pages, usually bound with " +
                    "a protective cover");
            map.put(
                "editor",
                "a person who edits");
        }
    
        @Override
        public String getDefinition(String word) {
            return map.get(word);
        }
    }
    前述したように、サービスプロバイダに登録して、他のことを確認する必要があります.登録はとても簡単です.
    サービスプロバイダJARファイルのMETA-INF/servicesディレクトリにプロファイルを作成する必要があります.設定ファイルの名前は、サービスプロバイダインタフェースの完全に限定されたclass nameでなければなりません.(以下に示す)

    また、プロバイダプロファイルは、自分で実装したSerivceプロバイダに完全に限定されたclass nameを1行に指定する必要があります.
    in GeneralDictionary
    dictionary.GeneralDictionary
    in ExtendedDictionary
    dictionary.ExtenedDictionary
  • サービスおよびサービスプロバイダを使用してクライアント
  • を作成する.
    package dictionary;
    
    import dictionary.DictionaryService;
    
    public class DictionaryDemo {
    
      public static void main(String[] args) {
    
        DictionaryService dictionary = DictionaryService.getInstance();
        System.out.println(DictionaryDemo.lookup(dictionary, "book"));
        System.out.println(DictionaryDemo.lookup(dictionary, "editor"));
        System.out.println(DictionaryDemo.lookup(dictionary, "xml"));
        System.out.println(DictionaryDemo.lookup(dictionary, "REST"));
      }
    
      public static String lookup(DictionaryService dictionary, String word) {
        String outputString = word + ": ";
        String definition = dictionary.getDefinition(word);
        if (definition == null) {
          return outputString + "Cannot find definition for this word.";
        } else {
          return outputString + definition;
        }
      }
    }
    したがって、DicitoryServiceクラスのインタフェースのみが使用されるため、新しい辞書を追加しても変更は起こりません.
    コード自体が簡単すぎるので、ハイライトになる部分だけ言及しました.完全なコードをダウンロードして実行すると、SPIの意図と原理を簡単に理解できます.
    最後に、JAVA SE 6がサポートするサービスプロバイダフレームワークjava.util.ServiceLoaderを見てみましょう.

    java.util.ServiceLoader


    ServiceLoaderは、サービスプロバイダの検索、ロード、および使用を支援します.
    ブラウズ・サービス・プロバイダの場所は、アプリケーションのクラス・パスまたはランタイム環境の拡張ディレクトリです.
    プロバイダのAPIをロードして使用します.
    classpathまたはランタイム拡張ディレクトリに新しいプロバイダを追加すると、ServiceLaoderは動的に検索できます.
    ServiceLoaderクラスはfinal(継承不可)であり、ロードアルゴリズムを上書きできないことを意味します.たとえば、他の場所でのナビゲーションサービスに変更することはできません.
    必要に応じてプロバイダをインスタンス化できます.1つのサービス・ローダでは、ロードされたプロバイダ・キャッシュがアイドル状態になります.各ローダの反復メソッド呼び出しは、キャッシュ内のすべての要素を順次返します.もちろん、reloadメソッドを使用してcacheをクリアすることができます.

    ServiceLoader APIの限界


    ServiceLoader APIは有用ですが、限界もあります.たとえば、ServiceLoaderクラスから継承することはできません.だから私たちはその動作を修正することはできません.
    もちろん、ServiceLoader classは、実行時に新しいプロバイダをアプリケーションに提供することはできません.
    public ServiceLoader APIは、Java SE 6に使用することができる.loaderサービスがJDK 1.3の初期に存在しても、このAPIはプライベートであり、Javaランタイムコード内部でのみ使用できます.

    サマリ


    Javaプロバイダフレームワークの簡単な例.util.ServiceLoader.
    Reference
    https://docs.oracle.com/javase/tutorial/ext/basics/spi.html#introduction
    https://dzone.com/articles/java-service-loader-vs-spring-factories-loader