今日のほとんどの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) {
このコードのおかげで、アプリケーションは
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() {
ある時点で、私たちは社会にとってこのプログラムの重要性を十分に認識し、友人と共有することにしました。 友人はこのサービスを使用して、何かが足りないと判断しました。 別のファイルに出力できますか? ただし、このクールなコードをすべて書き直す必要があります。 必要ありません。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 {
起動時に、アプリケーションは、以前と同様に、画面で見つかったすべての音楽を表示します。 ただし、新しく作成した拡張機能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 {
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アプリケーションの作成サービスローダー