ホームページ  >  記事  >  Java  >  JavaのSPIメカニズムとは何ですか

JavaのSPIメカニズムとは何ですか

王林
王林転載
2023-05-17 23:40:553212ブラウズ

1: SPI メカニズムの概要

SPI 正式名は Service Provider Interface で、JDK 組み込みダイナミックです。実装拡張ポイントのメカニズムを読み込むと、SPI テクノロジを通じて、インターフェイスの実装クラスを自分で作成しなくても動的に取得できます。これは特別な技術ではなく、単なる設計コンセプトです。

2: SPI の原理

JavaのSPIメカニズムとは何ですか

Java SPI は、実際には、インターフェイスベースのプログラミング + 戦略モード + 設定ファイルの組み合わせによって実装される動的読み込みメカニズムです。

システム設計では、さまざまな抽象化に対してさまざまな実装ソリューションが存在することがよくありますが、オブジェクト指向設計では、一般に、モジュールをインターフェイスに基づいてプログラムすることが推奨されており、モジュール間で実装クラスをハードコーディングすべきではありません。特定の実装クラスがコード内で参照されている場合、プラグ可能性の原則に違反します。置き換えを実装するには、コードを変更する必要があります。プログラム内で動的に指定せずにモジュールのアセンブリを有効にするには、サービス検出メカニズムが必要です。

Java SPI は、特定のインターフェイスに関連するサービス実装を検索するメカニズムを提供します。モジュール設計では、IOC の考え方に似た仕組み、つまりコンポーネントの組み立て制御をプログラムの外部に移す仕組みが広く使われています。つまり、SPI の中心的な考え方はデカップリングです。

3: 使用シナリオ

呼び出し元は、実際の使用ニーズに応じてフレームワークの実装戦略を有効化、拡張、または置き換えます

このメカニズムが使用されるいくつかのシナリオを次に示します

  • 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: ソース コードのデモ

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
        }

5: 実践的な戦闘

ステップ 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

org.example.MedicalServiceImpl

# #ステップ 3

、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());
        });
    }
}
生産量:

2000.00 元=200g/個
3022.12 元=30 カプセル/箱


6: メリットとデメリット

6.1 利点

デカップリングは、サードパーティ サービス モジュールのアセンブリ制御ロジックを、呼び出し元のビジネス コードから結合するのではなく分離します。アプリケーションは、実際のビジネスに応じてフレームワークの拡張やフレームワークの置き換えを可能にします。条件、コンポーネント。サードパーティのサービス モジュールがインターフェイスを実装するためのインターフェイス jar パッケージを提供する方法と比較して、SPI 方法では、ソース フレームワークがインターフェイス実装クラスのパスを意識する必要がなくなります。

6.2 欠点

    ServiceLoader は遅延読み込みと見なすことができますが、基本的にはトラバーサルを通じてのみ取得できます。つまり、インターフェイスのすべての実装クラスがロードされ、インスタンス化されます。一部の実装クラスがロードされてインスタンス化されているにもかかわらず、それらを使用する必要がない場合、リソースが無駄になります。特定の実装クラスを取得する方法は非常に限られています。イテレータの形式でのみ取得でき、特定のパラメータに基づいて対応する実装クラスを取得することはできません。
  • インスタンスServiceLoader クラスを使用する複数の同時マルチスレッドは安全ではありません

以上がJavaのSPIメカニズムとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。