dubbo コア ソース コード - SPI および IOC、AOP

ダボコアのソースコード分析

ダボには、学ぶ価値のある多くのデザインがあります。理解すべきいくつかのポイント:

  • SPI メカニズム
  • 適応拡張ポイント
  • Ioc と Aop
  • Dubbo と Spring の統合方法

ダボコアのSPI

ダボのソースコードには、以下の 3 種類のコードがあちこちにあり、アダプティブ拡張ポイント、指定された名前の拡張ポイント、アクティベーション拡張ポイントです。

ExtensionLoader.getExtensionLoader(XXX.class).getAdaptiveExtension();
ExtensionLoader.getExtensionLoader(XXX.class).getExtension(name);
ExtensionLoader.getExtensionLoader(XXX.class).getActivateExtension(url,key);

この拡張ポイントは、実際には Dubbo の SPI メカニズムです。SPIに関しては、まだご存じないかと思いますが、Springbootが自動で組み立てるSpringFactoiesLoaderもSPIの仕組みです。

Java SPI 拡張ポイントの実装

SPI の正式名称は Service Provider Interface で、もともとは JDK に組み込まれたサービス検出メカニズムで、主にサービスの拡張と実装に使用されます。SPI メカニズムは、データベース接続などの多くのシナリオで使用されます. JDK は java.sql.Driver インターフェイスを提供します. このドライバー クラスは JDK では実装されていませんが、Oracle や Mysql などのさまざまなデータベース ベンダーによって実装されています. ドライバー パッケージは、このインターフェイスを実装すると、JDK は SPI メカニズムを使用してクラスパスから対応するドライバーを見つけ、指定されたデータベースへの接続を取得します。このプラグイン拡張機能の読み込み方法も、特定のプロトコル協定に従います. たとえば、すべての拡張ポイントは resources/META-INF/services ディレクトリに配置する必要があり, SPI メカニズムはデフォルトでこのパスのプロパティ ファイルをスキャンして読み込みを完了します. .

ここに栗があります:

  • 共通の Maven プロジェクト ドライバーを作成し、インターフェイスを定義します。このインターフェイスは仕様であり、実装されていません. 実装はサードパーティの製造元によって提供されます.
public interface Driver {
    
    
    String connect();
}
  • 別の一般的な Maven プロジェクト Mysql-Driver を作成し、Driver の Maven 依存関係を追加します
<dependency>
    <groupId>com.scmpe.book.spi</groupId>
    <artifactId>Driver</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

  • MysqlDriver を作成し、サード パーティの拡張機能の実装を表す Driver インターフェイスを実装します。
public class MysqlDriver implements Driver {
    
    
    @Override
    public String connect() {
    
    
        return "连接Mysql 数据库";
    }
}
  • resources/META-INF/services ディレクトリに Driver インターフェイスのフル パスにちなんで名付けられたファイル com.scmpe.book.spi.Driver を作成し、その中に Driver 実装クラスを入力します。

    com.scmpe.book.spi.MysqlDriver
    
  • テスト クラスを作成し、ServiceLoader でロードする

@Test
public void connectTest() {
    
    
    ExtensionLoader<Driver> extensionLoader = ExtensionLoader.getExtensionLoder(Driver.class);
    Driver driver = extensionLoader.getExtension("mysqlDriver");
    System.out.println(driver.connect());
}

Dubbo SPI 拡張ポイントのソース コード

先ほど、Dubbo での SPI の使用方法をExtensionLoader.getExtensionLoader.getExtension() 示し. 次に、この方法に基づいて、Dubbo ソース コードで SPI を実装する方法を分析します。

このコードは 2 つの部分に分かれています。最初にを介してインスタンスExtensionLoader.getExtensionLoader を取得しExtensionLoader 、次にgetExtension()メソッドを介して指定された名前の拡張ポイントを取得します。最初の部分を分析してみましょう。

ExtensionLoder.getExtensionLoader

このメソッドは、ExtensionLoader インスタンスを返すために使用されます。主なロジックは次のとおりです。

  1. まず、キャッシュから拡張クラスに対応する ExtensionLoader を取得します。
  2. キャッシュが見つからない場合は、新しいインスタンスを作成し、キャッシュ用に EXTENSION_LOADERS コレクションに保存します。
  3. ExtensionLoader 構築メソッドでは、後で使用する objectFactory が初期化されるため、ここでは無視します。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    
    
    // 省略部分代码
    ExtensionLoder<T> loder = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loder == null) {
    
    
        EXTENSION_LOADERS.putIfAbsent(type,new ExtensionLoader<T>(type));
        loder = (ExtensinoLoder<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
//构造方法
private ExtensionLoader(Class<?> type) {
    
    
    this.type = type;
    ObjectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

getExtension()

このメソッドは、指定された名前に従って、対応する拡張ポイントを取得し、それを返すために使用されます。前の例で、名前が mysqlDriver の場合、返される実装クラスは MysqlDriver になります。

  • name はパラメーターの判断に使用され、name="true" の場合、デフォルトの拡張機能の実装が返されます。

  • Holder オブジェクトを作成し、ユーザーが拡張ポイントのインスタンスをキャッシュします。

  • 拡張ポイントがキャッシュに存在しない場合は、createExtension(name) を介して作成します。

public T getExtension(String name) {
    
    
    if(StringUtils.isEpty(name)) {
    
    
        throw new IllegalArgumentException("Extension name = null");
    }
    if ("true".equals(name)) {
    
    
        return getDefaultExtension();
    }
    // 创建或者返回一个Holder对象,用于缓存实例
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    if (instance == null) {
    
     //如果缓存不存在,则创建一个实例
 		synchronized (holder) {
    
    
            instance = holder.get();
			if (instance == null) {
    
    
                instance = createExtension(name);
                holder.set(instance);
            }            
        }       
       
    }
    return (T)instance;
}

上記のコードの意味は、最初にキャッシュをチェックし、キャッシュが見つからない場合は拡張オブジェクトを作成することです。createExtension() が、指定されたパスの下にある名前に対応する拡張ポイントの実装を見つけ、インスタンス化後に戻ることを推測することは難しくありません。

  • getExtensionClasses().get(name) を介して拡張クラスを取得します
  • リフレクションによってインスタンス化された後、EXTENSION_INSTANCES コレクションにキャッシュされます。
  • injectExtension は依存性注入を実装します
  • 拡張クラス オブジェクトを Wrapper でラップします。
private T createExtension(String name) {
    
    
    Class<?> clazz = getExtensionClasses().get(name);
    if (clzz == null) {
    
    
        throw findException(name);
    }
    try {
    
    
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
    
    
            EXTENSION_INSTANCES.putIfAbsent(clazz,clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //依赖注入
        injectExtension(instance);
        //通过Wrapper包装
        Set<Class<?>> WrapperClasses = cachedWrapperClasses;
        if (CollectUtils.isNotEmpty(wrapperClasses)) {
    
    
        	for(Class<?> wrapperClass : wrapperClasses) {
    
    
                instance = injectExtension((T) WrapperClass.getConstructor(type).newInstance(instance))
            }
        }
        initExtension(instance);
        return instance;
    } catch(Throwable t) {
    
    
        throw new IllegalStateException()
    }
}
  • 読み込まれた拡張クラスをキャッシュから取得する
  • キャッシュにヒットしない場合は、loadExtensionClasses を呼び出して拡張クラスをロードします。
private Map<String,Class<?>> getExtensionClasses() {
    
    
    Map<String,Class<?>> classes = cachedClasses.get();
    if (classes == null) {
    
    
        synchronized (cachedClasses) {
    
    
            classes = cachedClasses.get();
            if (classes == null) {
    
    
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

Dubbo のコード実装ルーチンは基本的に同じです. まずキャッシュにアクセスし、次にキャッシュ ミス後に loadExtensionClasses を介して 2 つの拡張クラスをロードします. このメソッドは主に 2 つのことを行います.

  • cacheDefaultExtensionName メソッドを介して現在の拡張インターフェイスのデフォルトの拡張オブジェクトを返し、キャッシュします。
  • 指定されたファイル ディレクトリ内の構成ファイルは、loadDirectory メソッドによって読み込まれます。
private Map<String,Class<?>> loadExtensionclasses() {
    
    
	cacheDefaultExtensionName();//获得当前type接口默认的扩展类
	Map<String,Class<?>> extensionclasses = new HashMap<>();
    //解析指定路径下的文件
    loadDirectory(extensionclasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"),true);
    loadDirectory(extensionclasses,DUBBO_DIRECTORY,type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY,type.getName().replace("org.apache","com.alibaba"));
    loadDirectory(extensionclasses,SERVICES_DIRECTORY,type.getName());
    loadDirectory(extensionclasses,SERVICES_DIRECTORY,type.getName().replace("org.apache","com.alibaba"));
    return extensionClasses;
}

loadDirectory メソッドのロジックは比較的単純で、渡された型のフル パス名に従って、指定されたディレクトリから対応するファイルを検索し、コンテンツを解析した後、extensionClasses コレクションに読み込んで保存します。

cacheDefaultExtensionName メソッドも比較的単純ですが、ビジネスと一定の関係があります。

  • 指定された拡張インターフェースの @SPI アノテーションを取得する
  • @SPI アノテーションで名前を取得し、cachedDefaultName 属性に保存します。
private void cacheDefaultExtensionName() {
    
    
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation == null) {
    
    
        return;
    }
    // 得到注解中的value值
    String value = defaultAnnotation.value();
    if (value = value.tirm().lenth > 0) {
    
    
        String[] names = NAME_SEPARATOR.spilt(value);
        if (names.length > 1) {
    
    
            throw new IllegalStateException()
        }
        if (names.lenth == 1) {
    
    
            cachedDefault = names[0];
        }
    }
}

Dubbo の org.apache.dubbo.rpc.Protocol を例にとると、@SPI アノテーションに dubbo のデフォルト値があります。これは、プロトコル タイプが明示的に指定されていない場合、デフォルトで Dubbo プロトコルが使用されて公開されることを意味します。サービス。

@SPI("dubbo")
public interface Protocol {
    
    
    //...
}

指定した名前の拡張クラスをダボにロードする処理です。

適応拡張ポイント

アダプティブ拡張ポイントは、アダプター拡張ポイントとして理解できます。簡単に言えば、コンテキストに応じて拡張クラスを動的に構成することができます。

ExtensionLoder.getExtensionLoader(class).getAdaptiveExtension();

アダプティブ拡張ポイントは @Adaptive アノテーションを介して宣言されます。これは 2 つの方法で使用できます。

  • 現在のクラスが適応拡張クラスであることを示す Adaptive アノテーションがクラスに定義されています。

    @Adaptive
    public class AdativeCompiler implents Compiler {
          
          
        // 省略
    }
    

    AdaptiveCompiler クラスは適応拡張クラスであり、AdaptiveCompiler クラスのインスタンスは ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension(); によって返すことができます。

  • @Adaptive アノテーションはメソッド レベルで定義され、動的バイトコードは動的プロキシを介してアダプティブ マッチング用に生成されます。、。

public interface Protocol {
    
    
    int getDefaultPort();
    
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type,URL url) throws RpcException;
}

Protocol 拡張クラスの 2 つのメソッドは @Adaptive アノテーションを宣言します。これは、これがアダプティブ メソッドであることを意味します。Dubbo ソース コードには、次のコード行を使用して適応拡張ポイントを取得する場所が多数あります。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

次に、プロトコルベースの適応拡張ポイント メソッド ExtensionLoader.getExtensinoLoader(Protocol.class).getAdaptiveExtensino() のソース コード実装を分析します。

ソース コードから判断すると、getAdaptiveExtension メソッドは非常に単純で、次の 2 つのことだけを行います。

  • キャッシュから適応拡張ポイント インスタンスを取得します。
  • キャッシュ ミスがある場合は、createAdaptiveExtension によって適応拡張ポイントが作成されます。
public T getAdaptiveExtension() {
    
    
    Object instance = this.cachedAdaptiveInstance.get();
    if (instance == null) {
    
    
        if (this.createAdaptiveInstanceError != null) {
    
    
            throw new IllegalStateException();
        }
        // 创建自适应扩展点实例,并放置到缓存中
        synchronized(this.cachedAdativeInstance) {
    
    
            instance = this.cachedAdaptiveInstance.get();
            if (instance == null) {
    
    
                try {
    
    
                    instance = this.createAdaptiveExtension();
                    this.cachedAdaptiveInstance.set(instance);
                }
                }catch(Throwable var5) {
    
    
                this.createAdaptiveInstanceError = var5;
                throw new IllegalstateException("Failed to create adaptive instance:+var5.toString(), var5);

            }
            
        }
    }
    return instance;
}

アダプティブ拡張ポイントの以前の分析によると、基本的に createAdaptiveExtension メソッドの実装メカニズムを推測できます。

  • getAdaptiveExtensionClasses 適応拡張クラスのインスタンスを取得します
  • injectExtension は依存性注入を完了します
private T createAdaptiveExtension( {
    
    
    try {
    
    
    return this.injectExtension(this.getAdaptiveExtensionClass().newInstance());}catch (Exception var2){
    
    
    throw new IllegalStateException("can't create adaptive extension " + this.type +", cause:" +var2.getMessage(), var2);
    }
}

InjectExtension は後で分析します。まず getAdaptiveExtensinoClass を見てください。

  • getExtensionClasses メソッドを介して現在の Chunru 型のすべての拡張ポイントを読み込み、コレクションにキャッシュします
  • cachedAdaptiveClass が空の場合は、createAdaptiveExtensionCalss を呼び出して作成します
private Class<?> getAdaptiveExtensionClass() {
    
    
    this.getExtensionClasses();
    return this.cachedAdaptiveClass != null ? this.cachedAdaptiveClass : (this.cachedAdaptiveClass = this.createAdaptiveExtensionClass());
}

getExtensionClasses メソッドについては前に説明しましたが、動的バイトコードの生成とロードを含む createAdaptiveExtensionClass メソッドを直接見てください。

  • コードは動的に接合されたクラスです。
  • Compiler を介して動的コンパイルを実行します。
private Class<?> createAdaptiveExtensionClass() {
    
    
    String code = (new AdaptiveClassCodeGenerator(this.type,this.cachedDefaultName)).generate();
    ClassLoader classLoader = findClassLoader();
    Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();
    return compiler.compile(code,classLoader);
}

Protocol インターフェースに基づく適応拡張ポイントのロードでは、このときのコードスプライシングの列は次のようになります (レイアウトを美しくするために、いくつかの無駄なコードは削除されます))。

public class Protocol$Adaptive implements Protocol {
    
    
    //省略部分代码
    public Exporter export(Invoker arg0) throws org.apache. dubbo.rpc.RpcException {
    
    
    if (arge == null) throw new IllegalArgumentException("Invoker argument == null");
    if (arg0.getUrl()== nul1)
    	throw new IllegalArgumentException("Invoker argument getUrl() - null");
    URL url = arg0.getUr1();
    String extName = (url.getProtocol() == null ?"dubbo":url.getProtoco1());\
    if (extName == null)
    	throw new IllegalstateException("Failed to get extension (Protocol) name from”+url.toString() +")use keys([protocol])");
    //根据名称获得指定扩展点
    Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    return extension. export(arg0);
}
public Invoker refer(Class arg0,URL arg1) throws RpcException {
    
    
    if (arg1 ==null) throw new IllegalArgumentException("url == null");
    URL url = arg1;
   	String extName =(url.getProtocol()== null ? "dubbo":url.getProtocol());
    if (extName == null) throw new IllegalstateException("Failed to get extension (Protocol) name from ur1("+ url.toString() +") use keys([protocol])");
    Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName):
    return extension.refer(arg0,arg1);
}

Protocol$Adaptive は動的に生成される適応拡張クラスで、次の方法で使用できます。

Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
protocol.export( ...);

protocol.export() が呼び出されると、Protocol$Adaptive クラスの export メソッドが実際に呼び出されます。そして、このメソッドは、Dubbo サービスによって構成されたプロトコル名に従って、getExtension を介して対応する拡張クラスを取得することに他なりません。

ダボの IOC と AOP

IOC と AOP は私たちにとってなじみ深いものであり、Spring Framework のコア機能であり、実際、これら 2 つのメカニズムは Dubbo でも使用されています。以下では、これら 2 つのメカニズムの実装をソース コード レベルから 1 つずつ分析します。

IOC

IoC の非常に重要なアイデアは、システムが実行されているときに、必要な他のオブジェクトをオブジェクトに動的に提供することです. このメカニズムは、DI (依存性注入) によって実現されます。

Dubbo SPI メカニズムを分析すると、createExtension メソッドに次のようなコードがあります。

private T createExtension(String name) {
    
    
    try {
    
    
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
    
    
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T)EXTENSION_INSTANCES.get(clazz);
        }
		injectExtension(instance);
        //省略部分代码
        return instance
    }catch(Throwable t) {
    
    
        //省略部分代码
    }
}

injectExtension は依存性注入の実装であり、全体的なロジックは比較的単純です。

  • 読み込まれた拡張クラスのすべての set メソッドをトラバースする
  • set メソッドでパラメーターの型を取得し、パラメーターの型がオブジェクト型の場合は、set メソッドで属性名を取得します。
  • アダプティブ拡張ポイントを使用して、属性名に対応する拡張クラスをロードします。
  • set メソッドを呼び出して、割り当てを完了します。
private T injectExtension(T instance) {
    
    
    if (objectFactory == null) {
    
    
        return instance;
    }
   	try {
    
    
        for (Method method : instance.getClass().getMethods()) {
    
    
            if (!isSetter(method)) {
    
    
                continue;
            }
            if (method.getAnnotation(DisableInject.class) != null) {
    
    
                continue;
            }
            // 获得扩展类中方法的参数类型
            Class<?> pt = method.getParameterTypes()[0];
            // 如果不是对象类型,跳过
            if (ReflectUtils.isPrimitives(pt)) {
    
    
                continue;
            }
            try {
    
    
                // 获取方法对应的属性名称
                String property = getSetterProperty(method);
                // 根据class及name,使用自适应扩展点加载并且通过set方法进行赋值
                Object object = objectFactory.getExtension(pt,property);
                if (object != null) {
    
    
                    method.invoke(instance,object);
                }
            }catch(Exception e) {
    
    
               logger.error("Failed to inject via method " + method.getName()+"of interface " + type.getName() +":" +e.getMessage(),e);
        	} 
        }catch(Exception e) {
    
    
                logger.error(e.getMessage,e);
        }
        return instance;
    }
}

要約すると、injectExtension の主な機能は、現在読み込まれている拡張クラスに注入する必要があるオブジェクトがある場合 (オブジェクトはセッター メソッドを提供する必要があります)、適応拡張ポイントを介して読み込まれ、割り当てられることです。

例として org.apache.dubbo.registry.integration.RegistryProtocol を取り上げます。その中に Protocol メンバー オブジェクトがあり、そのために setProtocol メソッドが提供されます。次に、RegistryProtocol 拡張クラスが読み込まれると、自動的に のインスタンスが挿入されます。プロトコル メンバー属性。

public class RegistryProtocol implements Protocol {
    
    
    //省略部分代码
    private Protocol protocol;
	public void setProtocol(Protocol protocol) {
    
    
        this.protocol = protocol;
    }
    //省略部分代码
}

AOP

AOP は Aspect Oriented Programming の略で、アスペクト指向プログラミングを意味し、一種の思考またはプログラミング パラダイムです。その主な目的は、ビジネス ロジックを機能ロジックから分離し、実行時またはクラスの読み込み中に織り込むことです。これの利点は、コードの複雑さが軽減され、再利用性が向上することです。

Dubbo API の仕組みでは、ExtensionLoader クラスの createExtension メソッドにも AOP の設計思想が反映されています。

private T createExtension(String name){
    
    
    //..
    try {
    
    
        //...
        Set<class<?>> wrapperClasses = cachedwrapperClasses;if (collectionutils.isNotEmpty(wrapperclasses)){
    
    
        for (class<?> wrapperClass : wrapperClasses){
    
    
            instance = injectExtension((T) wrapperclass.getConstructor(type).newInstance(instance));
            }
        }
        initExtension( instance);
        return instance;
    }catch (Throwable t){
    
    
        //...
    }
}

このコードは前の章で言及されています。次のコード行を注意深く分析してください。

instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

その中で、依存性注入と AOP のアイデアがそれぞれ使用されます.AOP のアイデアの具現化は、元の拡張クラスのインスタンスを Wrapper デコレータ クラスに基づいてラップすることです。

おすすめ

転載: blog.csdn.net/qq_45473439/article/details/125377403