SPIエンジンを使用して拡張機能を作成する

今日のほとんどのJava(だけでなく)アプリケーションのアーキテクチャは、コードに対するさまざまな種類の魔法の効果によって機能を拡張する可能性を提供します。 最近、何らかのファッショナブルなフレームワークまたはIoCコンテナを使用する場合にも可能になりました。 しかし、アプリケーションが長命であり、フレームワークを使用するように変換するには複雑すぎる場合はどうでしょうか?



私が作業した最後のアプリケーションでは、当時私には知られていないSPI バイクメカニズムが実装され、 META-INF / services / <qualified interface name>のテキストファイルを検索し、そこからこのインターフェイスを実装する必要なクラスの名前を取得し 、このクラスは拡張機能として使用されました。 インターネットを検索した結果、 Service Provider Interface(SPI)はプラグインコンポーネントをサポートするソフトウェアメカニズムであり、このメカニズムはJava Database Connectivity(JDBC)などJava Runtime Environment(JRE)でかなり長い間使用されていることがわかりました。

ps = Service.providers(java.sql.Driver.class); try { while (ps.hasNext()) { ps.next(); } } catch (Throwable t) { // Do nothing }
      
      







このコードのおかげで、アプリケーションはClass.forName(<driver class>)コンストラクトを必要としません動作しますが)、JDBCドライバーはDriverManagerクラスのメソッドに初めてアクセスするときに自動的にロードされます。



SPIメカニズムは、 Java Cryptography Extension(JCE)Java Naming and Directory Service(JNDI)Java API for XML Processing(JAXP)Java Business Integration(JBI) 、Java Sound、Java Image I / Oでも使用されます



どのように機能しますか?



全体のポイントは、ロジックをサービス(サービス)とプロバイダー(サービスプロバイダー)に分離することです。 プロバイダーへのリンクは、テキストファイル(UTF-8) META-INF / services / <qualified service class>の拡張jarに格納され、各行にはプロバイダークラスの完全な名前が格納されます。 空白行とコメント(#文字で始まる)は無視されます。 プロバイダーの制限:プロバイダーは、インターフェイスを実装するか、サービスクラスから継承し、既定のコンストラクター(引数のないパブリックコンストラクター)を持っている必要があります。



プロバイダのリストを取得するためのメインアプリケーションは、Java SE 6 APIの一部であるjava.util.ServiceLoaderユーティリティを使用できます。このユーティリティは、次の原則に従って動作します。





カスタムコードは特定のサービスの構成ローダーによって要求され、ローダーは必要に応じて構成からプロバイダーを読み込み、キャッシュに保存します。 キャッシュをクリアして設定をリロードすることもできます。



Java SEの以前のバージョンには同様のユーティリティsun.misc.Serviceがあり、同じ原理で動作しますが、 Sun Oracle独自のソフトウェアの一部であり、将来のJava SEリリースで削除される可能性があります。



使用例



たとえば、コンピューターで音楽を検索し、名前でソートされた結果を画面に表示するプログラムがあります。

 public class MusicFinder { public static List<String> getMusic() { //some code } } public class ReportRenderer { public void generateReport() { final List<String> music = findMusic(); for (String composition : music) { System.out.println(composition); } } public List<String> findMusic() { final List<String> music = MusicFinder.getMusic(); Collections.sort(music); return music; } public static ReportRenderer getInstance() { return new ReportRenderer(); } public static void main(final String[] args) { final ReportRenderer renderer = ReportRenderer.getInstance(); renderer.generateReport(); } }
      
      







ある時点で、私たちは社会にとってこのプログラムの重要性を十分に認識し、友人と共有することにしました。 友人はこのサービスを使用して、何かが足りないと判断しました。 別のファイルに出力できますか? ただし、このクールなコードをすべて書き直す必要があります。 必要ありません。SPIメカニズムを使用できます。



たとえば、スーパープログラムのプラグインを作成します。

 public class FileReportRenderer extends ReportRenderer { @Override public void generateReport() { final List<String> music = findMusic(); try { final FileWriter writer = new FileWriter("music.txt"); for (String composition : music) { writer.append(composition); writer.append("\n"); } writer.flush(); } catch (IOException e) { e.printStackTrace(); } } }
      
      







以下をMETA-INF / services / com.example.ReportRendererに追加します。

 com.example.FileReportRenderer




ソースプログラムを拡張可能にしましょう:

 public class ReportRenderer { //... public static ReportRenderer getInstance() { final Iterator<ReportRenderer> providers = ServiceLoader.load(ReportRenderer.class).iterator(); if (providers.hasNext()) { return providers.next(); } return new ReportRenderer(); } //... }
      
      







起動時に、アプリケーションは、以前と同様に、画面で見つかったすべての音楽を表示します。 ただし、新しく作成した拡張機能jarをクラスパスに配置すると、検索結果を含むmusic.txt ファイルが作成されます。



さあ、 MusicFinderで遊んでみましょう 。 拡張可能にしましょう。 これを行うには、クラスをインターフェイスに変更します。

 public interface MusicFinder { List<String> getMusic(); }
      
      







メインモジュールに実装を追加します。

 public class DummyMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From DummyMusicFinder..."); } }
      
      







ReportRendererの拡張機能サポート:

 public class ReportRenderer { //... public List<String> findMusic() { final List<String> music = new ArrayList<String>(); for (final MusicFinder finder : ServiceLoader.load(MusicFinder.class)) { music.addAll(finder.getMusic()); } Collections.sort(music); return music; } //... }
      
      







ReportRendererの場合と同様に、次を含むテキストファイルMETA-INF / services / com.example.MusicFinderを追加します。

 com.example.DummyMusicFinder




繰り返しますが、最初のプログラムの結果は変更されていません。 今拡張。 ここでは、 MusicFinderの 2つの実装を作成します。

 public class ExtendedMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From ExtendedMusicFinder..."); } } public class MyMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From MyMusicFinder..."); } }
      
      







META-INF / service / com.example.MusicFinder

 com.example.MyMusicFinder
 com.example.ExtendedMusicFinder




さて、それですべて、拡張機能をサポートするプログラムの準備ができました。 クラスパスに拡張機能が追加されました。

 DummyMusicFinderから...
 ExtendedMusicFinderから...
 MyMusicFinderから...




サンプルのソースはこちらにあります



おわりに



上記の例は完璧とはほど遠いものであり、私は世界で最もクールな音楽検索エンジンの著者であるふりをしていません。 また、このメカニズムの狂信的な使用も求めていません。どこでも適用できるわけではないため、IoCコンテナの使用はより美しいソリューションであると考えていますが、それでもこのアプローチはいくつかの場所で役立つかもしれません。 記事を読んでくれてありがとう。



文学



プラグイン

サービスプロバイダーインターフェイス

サービスプロバイダー

サービスプロバイダーインターフェイス:拡張可能なJavaアプリケーションの作成

サービスローダー



All Articles