SPI
正式名は Service Provider Interface
で、JDK
組み込みダイナミックです。実装拡張ポイントのメカニズムを読み込むと、SPI
テクノロジを通じて、インターフェイスの実装クラスを自分で作成しなくても動的に取得できます。これは特別な技術ではなく、単なる設計コンセプトです。
Java SPI は、実際には、インターフェイスベースのプログラミング + 戦略モード + 設定ファイルの組み合わせによって実装される動的読み込みメカニズムです。
システム設計では、さまざまな抽象化に対してさまざまな実装ソリューションが存在することがよくありますが、オブジェクト指向設計では、一般に、モジュールをインターフェイスに基づいてプログラムすることが推奨されており、モジュール間で実装クラスをハードコーディングすべきではありません。特定の実装クラスがコード内で参照されている場合、プラグ可能性の原則に違反します。置き換えを実装するには、コードを変更する必要があります。プログラム内で動的に指定せずにモジュールのアセンブリを有効にするには、サービス検出メカニズムが必要です。
Java SPI は、特定のインターフェイスに関連するサービス実装を検索するメカニズムを提供します。モジュール設計では、IOC の考え方に似た仕組み、つまりコンポーネントの組み立て制御をプログラムの外部に移す仕組みが広く使われています。つまり、SPI の中心的な考え方はデカップリングです。
呼び出し元は、実際の使用ニーズに応じてフレームワークの実装戦略を有効化、拡張、または置き換えます
このメカニズムが使用されるいくつかのシナリオを次に示します
JDBC ドライバー、さまざまなデータベースのドライバー クラスのロード
Spring は、servlet3 での ServletContainerInitializer の実装など、多くの SPI を使用します。 0 仕様、自動型変換 SPI (コンバータ SPI、フォーマッタ SPI) など
Dubbo はフレームワーク拡張機能を実装するために SPI も広範囲に使用しますが、Java によって提供されるネイティブ SPI をカプセル化します。ユーザーは、Filter インターフェイスの実装を拡張できます。
Tomcat は、META-INF/services の下にロードする必要があるクラスをロードします。
SpringBoot プロジェクトの @SpringBootApplication アノテーションを追加すると、自動構成が開始され、スタートアップ構成によって META-INF/spring.factories の下の構成クラスがスキャンされます。
4.1 アプリケーションによる ServiceLoader.load メソッドの呼び出し
ServiceLoader.load メソッドでは、まず新しい ServiceLoader を作成し、クラス内のメンバー変数をインスタンス化します
private static final String PREFIX = "META-INF/services/"; 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() { providers.clear(); //清除此加载程序的提供程序缓存,以便重新加载所有提供程序。 lookupIterator = new LazyIterator(service, loader); } private class LazyIterator implements Iterator<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //找到配置文件 String fullName = PREFIX + service.getName(); //加载配置文件中的内容 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } //解析配置文件 pending = parse(service, configs.nextElement()); } //获取配置文件中内容 nextName = pending.next(); return true; } } /** * * 通过反射 实例化配置文件中的具体实现类 */ private S nextService() { 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"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
ステップ 1 次のクラスを作成します
public interface IService { /** * 获取价格 * @return */ String getPrice(); /** * 获取规格信息 * @return */ String getSpecifications(); }
public class GoodServiceImpl implements IService { @Override public String getPrice() { return "2000.00元"; } @Override public String getSpecifications() { return "200g/件"; } }
public class MedicalServiceImpl implements IService { @Override public String getPrice() { return "3022.12元"; } @Override public String getSpecifications() { return "30粒/盒"; } }
ステップ 2、src/main/resources/ の下に /META-INF/services ディレクトリを作成します。インターフェース org.example.IService.txt にちなんだ名前のファイルを追加します。中身は適用する実装クラスで、入れるべきデータは以下のとおりです
#org.example.GoodServiceImpl# #ステップ 3org.example.MedicalServiceImpl
、ServiceLoader を使用して、構成ファイルで指定された実装をロードします。 public class Main {
public static void main(String[] args) {
final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
serviceLoader.forEach(service -> {
System.out.println(service.getPrice() + "=" + service.getSpecifications());
});
}
}
生産量:
3022.12 元=30 カプセル/箱6: メリットとデメリット
6.2 欠点
以上がJavaのSPIメカニズムとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。