プライベートな連絡先の最初の情報
送料メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI は Service Provider Interface の略で、フレームワークの機能を拡張するサービス プロバイダーまたは開発者に特別に提供されるインターフェイスとして理解できます。 SPI は、サービス インターフェイスを特定のサービス実装から分離し、サービス呼び出し元とサービス実装者を分離し、プログラムのスケーラビリティと保守性を向上させることができます。サービス実装を変更または置換する場合、呼び出し元を変更する必要はありません。 Spring フレームワーク、データベース読み込みドライバー、ログ インターフェイス、Dubbo 拡張機能の実装など、多くのフレームワークは Java の SPI メカニズムを使用します。この RPC フレームワークの SPI 部分を実装するには、Dubbo の SPI メカニズムを参照してください。
たとえば、クライアントがサーバーと通信するとき、メッセージをシリアル化する必要があります。 Hessian、Kryo、ProtoStuff など、シリアル化中に使用できるシリアル化アルゴリズムが多数あります。システムの要件は、メッセージ内のシリアル化アルゴリズムの名前に基づいて、該当するシリアル化アルゴリズムに対応するクラス内のメソッドを呼び出し、シリアル化および逆シリアル化を実行することです。また、拡張を容易にするために、SPI を使用する必要があります。デカップリング用。
SPI は次のように使用されます。
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName はシリアル化アルゴリズムの名前であり、対応するクラスはこの名前に従ってロードされる必要があります。
- private final Class<?> type;
-
- private ExtensionLoader(Class<?> type) {
- this.type = type;
- }
-
- // 每个SPI接口都有自身的ExtensionLoader
- public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type should not be null.");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type must be an interface.");
- }
- if (type.getAnnotation(SPI.class) == null) {
- throw new IllegalArgumentException("Extension type must be annotated by @SPI");
- }
- // firstly get from cache, if not hit, create one
- ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- if (extensionLoader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
- extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- }
- return extensionLoader;
- }
各 SPI インターフェイスには独自の ExtensionLoader があり、getExtensionLoader を呼び出すと、最初に一連の正当なチェック操作が実行され、次にインターフェイスの ExtensionLoader を取得できない場合は、ローカル キャッシュ CHM から取得しようとします。取得したら、Loader オブジェクトを作成します。
その後、getExtension を通じてインスタンスが取得され、インスタンスもローカルにキャッシュされます。キャッシュにない場合は、インスタンスを再度作成します。
- private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-
- public T getExtension(String name) {
- if (StringUtil.isBlank(name)) {
- throw new IllegalArgumentException("Extension name should not be null or empty.");
- }
- // firstly get from cache, if not hit, create one
- // 缓存holder
- Holder<Object> holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- // create a singleton if no instance exists
- // holder为空,双重检查锁创建示例
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
クラスの Class オブジェクトを取得した後、リフレクションを通じてこのオブジェクトを作成できます。
- // 缓存
- private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
-
- private T createExtension(String name) {
- // load all extension classes of type T from file and get specific one by name
- // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw new RuntimeException("No such extension of name " + name);
- }
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- try {
- // 缓存中不存在,则创建实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- return instance;
- }
重要なのは、Class オブジェクトを取得するプロセス、つまり getExtensionCalsses メソッドです。
- // 该SPI接口所有实现类的标识与其Class对象的缓存
- private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
- private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
-
- private Map<String, Class<?>> getExtensionClasses() {
- // get the loaded extension class from the cache
- // 根据Interface实现类的类名获取对应类的缓存
- Map<String, Class<?>> classes = cachedClasses.get();
- // double check
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = new HashMap<>();
- // load all extensions from our extensions directory
- loadDirectory(classes);
- // 将Map集合存储在Holder中进行缓存
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private void loadDirectory(Map<String, Class<?>> extensionClasses) {
- // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
- String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
- try {
- Enumeration<URL> urls;
- // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
- ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
- // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
- urls = classLoader.getResources(fileName);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- URL resourceUrl = urls.nextElement();
- // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
- loadResource(extensionClasses, classLoader, resourceUrl);
- }
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
- String line;
- // read every line
- // #是注释,截取注释之前的部分
- while ((line = reader.readLine()) != null) {
- // get index of comment
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- // string after # is comment so we ignore it
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- final int ei = line.indexOf('=');
- // 标识与类名
- String name = line.substring(0, ei).trim();
- String clazzName = line.substring(ei + 1).trim();
- // our SPI use key-value pair so both of them must not be empty
- if (name.length() > 0 && clazzName.length() > 0) {
- // 加载类
- Class<?> clazz = classLoader.loadClass(clazzName);
- // 在map中保存
- extensionClasses.put(name, clazz);
- }
- } catch (ClassNotFoundException e) {
- log.error(e.getMessage());
- }
- }
-
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
- kyro=github.javaguide.serialize.kyro.KryoSerializer
- protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
- hessian=github.javaguide.serialize.hessian.HessianSerializer
レイヤーごとのメソッド呼び出しは、META-INF/extensions/ パスの下にある対応する SPI 設定ファイルをロードして Class オブジェクトをロードし、インスタンスを取得するプロセスを実現します。重要な部分についてはコメントを参照してください。
META-INF/extensions/ の下のファイル名はコードと一致している必要があることに注意してください。コード内で指定されるファイル名は、SPI インターフェイス クラスの完全なクラス名です。ファイル内のコンテンツは、コードとの一貫性を保つために (実装クラス識別子 = 実装クラスの完全なクラス名) に従って書き込む必要もあります。また、プログラムはファイルを正しく解析し、クラス ローダーを使用して次のことを行うことができます。対応するクラスをロードします。最後に、<実装クラス識別子、実装クラスクラスオブジェクト>に従ってキャッシュします。
3 つのキャッシュ (クラス識別子、クラス オブジェクト)、(クラス オブジェクト、オブジェクト インスタンス)、および (クラス識別子、オブジェクト インスタンス) が存在するため、識別子を直接渡して、対応するクラスのインスタンスを取得できます。また、RPC フレームワークのパフォーマンスも最適化されます。