JavaのSPIメカニズムを知っていますか?

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して3日目です。クリックしてイベントの詳細をご覧ください

インターフェイスのセットをカプセル化する場合、他のプロジェクトはインターフェイスを呼び出したいので、作成したパッケージをインポートするだけで済みますが、他のプロジェクトがインターフェイスを拡張したい場合は、インターフェイスが依存関係パッケージにカプセル化されているため、そうではありません。拡張が簡単な場合は、Javaが提供するSPIメカニズムに依存する必要があります。

1簡単な紹介

SPIのフルネームはサービスプロバイダーインターフェイス、サービスプロバイダーインターフェイスであり、それに最も近い概念はAPI、フルネームのアプリケーションプログラミングインターフェイス、アプリケーションプログラミングインターフェイスです。では、2つの主な違いは何ですか?

APIの呼び出し元は、プロバイダーの既存の実装にのみ依存でき、SPIはカスタマイズ可能なAPIです。呼び出し元は、APIによって提供されるデフォルトの実装を置き換えるために、実装をカスタマイズできます

SPIメカニズムは、特にフレームワークにとって非常に重要です。これを使用して、フレームワークの拡張を有効にし、コンポーネントを置き換えることができます。また、フレームワークのソースコードを読み取るときに、多数のSPIアプリケーションが表示されます。

SPIの役割は、これらの拡張APIのサービス実装を見つけることです。

2SPIケース

1つはSPIサービスプロバイダー用、もう1つはSPIサービス導入拡張テスト用のプロジェクトを作成します。この場合、最も単純なMaven子親プロジェクトをビルドでき、spiプロバイダーの依存関係がspiテストプロジェクトに導入されます。 。

image.png

spi-testは依存関係を追加します

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spi-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
复制代码

spi-providerで、インターフェースとデフォルトの実装クラスを提供します

image.png

次のファイルをリソースファイルに追加し、変更ファイルでインターフェイスのデフォルトの実装クラスを指定します

image.png

spi-testで実行可能なテストメソッドを構築し、それを直接実行して、デフォルトの実装を取得します

![image-20220406221727379](C:\ Users \ coder zhj \ AppData \ Roaming \ Typora \ typora-user-images \ image-20220406221727379.png)

次の図に示すように、このインターフェイスをspi-testで拡張できます。

image.png

現時点では、拡張機能は有効ではありません。リソースファイルの下にあるインターフェイスの拡張機能の実装クラスを指定する必要があります。この時点で、拡張機能の結果は、上記のテストメソッドを実行することで取得できます。 SPIメカニズムです。

image.png

3SPIの主成分分析

ServiceLoaderクラスには、SPIのコア原則が含まれています。最初に指定されたパスプレフィックスから、ファイルをこのパスに配置して有効にする必要がある理由を推測できます。

loadメソッドを使用して、ServiceLoaderオブジェクトを作成し、そのコンストラクターを入力してリロードすると、新しいLazyIteratorイテレーターが作成されます。LazyIteratorは内部クラスであり、META-INF / services /の下の構成ファイルをスキャンし、すべてのインターフェイスの名前。次に、完全修飾クラス名を使用して、リフレクションによるクラスのロードを実装します。

public final class ServiceLoader<S>
    implements Iterable<S>
{

    // 扫描路径前缀
    private static final String PREFIX = "META-INF/services/";
	// 被加载的类或者接口
    private final Class<S> service;
	// 用于定位、加载和实例化需要加载类的类加载器
    private final ClassLoader loader;
	// 上下文对象
    private final AccessControlContext acc;
	// 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
	// 懒查找迭代器
    private LazyIterator lookupIterator;

    // 重新加载
    public void reload() {
        // 清理缓存
        providers.clear();
        // 新的懒查找迭代器
        lookupIterator = new LazyIterator(service, loader);
    }
	// 构造方法
    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();
    }

    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }

    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        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
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}
复制代码

上記のソースコードから、ServiceLoaderに追加のロックメカニズムがないことを見つけるのは難しくありません。そのため、同時実行の問題が発生し、対応する実装クラスを取得するのに十分な柔軟性がなく、イテレータを使用して既知のインターフェースのすべての特定の実装クラスを取得するため、すべての実装クラスを毎回ロードしてインスタンス化する必要があります。拡張機能が他の拡張機能に依存している場合、自動挿入とアセンブリを実行できず、拡張機能を他のフレームワークと統合することは困難です。

多くのフレームワークがServiceLoaderのネイティブSPIメカニズムを直接使用しないのもこれらの理由からですが、このアイデアに基づいて拡張され、その機能がより強力になります。典型的なケースは、dubboのSPI、SpringBootのSPIです。

私の記事「SpringBootのspring.factoriesファイルを使用してモジュール間でBeanをインスタンス化する原則」は、SpringBootのSPIメカニズムに関するものです。興味のある学生はそれを読むことができます。

読んでいただきありがとうございます。参考になった場合は、高く評価してください。ありがとうございます。

おすすめ

転載: juejin.im/post/7083506719715754020