Swoftソース剖析-SwoftにおけるIOC容器の実現原理

9457 ワード

作者:bromineリンク:https://www.jianshu.com/p/a23...出典:簡書の著作権は作者の所有であり、本文はすでに作者の許可を得て転載し、原文を再配置した.Swoft Github: https://github.com/swoft-clou...
前言
Swoftはアプリケーションに完全なIOCコンテナを依存管理案として提供し、Swoft AOP機能、RPCモジュールなどの機能の実現基礎である.彼が主に解決した機能は3つある.オブジェクト間の様々なネスト依存を手作業で管理する手間が省けます.2.オブジェクトの依存関係はコンパイル期間中に決定されなくなり、実行期間が動作を変更するより多くの弾力性を提供します.3.対象は具体的な実装に依存するのではなく、抽象的なインタフェースや抽象的なクラスに依存して依存管理に興味のある学生は、マーティンおじさんのこの文章を読むことができます.
サービスロケータ
Beanはクラスレベル注記@Beanによって定義され、Bean定義後のプログラムはApp::getBean()によってBeanのインスタンスを直接取得することができる.App::getBean()サービスロケータ式の依存管理方式を提供し、サービスロケータにアクセスして特定のインスタンスを取得することができ、サービスロケータは「インスタンス構造、インスタンス間依存管理、具体的なクラス選択を実現する」という問題を解決し、ユーザーに関連する詳細を遮断する.Container->set()メソッドは、App::getBean()下位層が実際にbeanを作成する方法である.原理は,反射と種々の注釈(注釈章参照)によって提供される情報と方法によってBeanのエージェントオブジェクトを構築することである.
//Swoft\Bean\Container.php
/**
 *   Bean
 *
 * @param string           $name               
 * @param ObjectDefinition $objectDefinition bean  
 * @return object
 * @throws \ReflectionException
 * @throws \InvalidArgumentException
 */
private function set(string $name, ObjectDefinition $objectDefinition)
{
    // bean    
    $scope = $objectDefinition->getScope();
    $className = $objectDefinition->getClassName();
    $propertyInjects = $objectDefinition->getPropertyInjections();
    $constructorInject = $objectDefinition->getConstructorInjection();

    //ref         ,     Interface          Bean ,            
    if (!empty($objectDefinition->getRef())) {
        $refBeanName = $objectDefinition->getRef();
        return $this->get($refBeanName);
    }

   //         
    $constructorParameters = [];
    if ($constructorInject !== null) {
        $constructorParameters = $this->injectConstructor($constructorInject);
    }

      
    $reflectionClass = new \ReflectionClass($className);
    $properties = $reflectionClass->getProperties();

    //     new  
    $isExeMethod = $reflectionClass->hasMethod($this->initMethod);
    $object = $this->newBeanInstance($reflectionClass, $constructorParameters);

    //     
    $this->injectProperties($object, $properties, $propertyInjects);

    //   Swoft Bean        `init()`
    if ($isExeMethod) {
        $object->{$this->initMethod}();
    }

    //    ,   AOP  
    if (!$object instanceof AopInterface) {
        $object = $this->proxyBean($name, $className, $object);
    }

    //     
    if ($scope === Scope::SINGLETON) {
        $this->singletonEntries[$name] = $object;
    }

    return $object;
}

依存注入
サービスロケータに比べて、依存注入はより先進的な依存管理実践である.
サービスロケータモードでは、クライアントはサービスロケータ自体を呼び出し、サービスロケータ自体に依存する必要がある.依存注入モードでは、クライアントと依存注入マネージャの関係も制御が反転し、クライアントは依存マネージャの存在を知らず、依存マネージャによってクライアントを呼び出し、特定の依存オブジェクトを注入する.
Swoftの依存注入管理スキームは、サービスロケータに基づいている.提供される注入方法は3つあります.
属性注入
/**
 * @Reference("user")
 * @var \App\Lib\MdDemoInterface
 */
private $mdDemoService;

/**
 * @Inject()
 * @var \App\Models\Logic\UserLogic
 */
private $logic;

/**
 * the name of pool
 *
 * @Value(name="${config.service.user.name}", env="${USER_POOL_NAME}")
 * @var string
 */
protected $name = "";

上の@Reference,@Inject,@valueの3つは典型的な属性注入用の注釈宣言であり,1つのBeanクラスでこれら3つの注釈を宣言する属性はそれぞれ特定のRpcクライアントエージェントオブジェクト,通常のBeanエージェントオブジェクト,プロファイル構成値に注入される.
属性注入メタ情報の解析
Beanの各属性の注入情報は,注記収集段階,すなわちSwoftの起動段階で完了している.
//Swoft\Bean\Wrapper\AbstractWrapper.php
/**
 *     
 *
 * @param  array $propertyAnnotations
 * @param string $className
 * @param string $propertyName
 * @param mixed  $propertyValue
 *
 * @return array
 */
private function parsePropertyAnnotations(array $propertyAnnotations, string $className, string $propertyName, $propertyValue)
{
   
    $isRef = false;
    $injectProperty = "";

    //       
    if (empty($propertyAnnotations) || !isset($propertyAnnotations[$propertyName])
        || !$this->isParseProperty($propertyAnnotations[$propertyName])
    ) {
        return [null, false];
    }

    //       
    foreach ($propertyAnnotations[$propertyName] as $propertyAnnotation) {
        $annotationClass = get_class($propertyAnnotation);
        if (!in_array($annotationClass, $this->getPropertyAnnotations())) {
            continue;
        }

        //         ( ValueParser,ReferenceParser )       
        $annotationParser = $this->getAnnotationParser($propertyAnnotation);
        if ($annotationParser === null) {
            $injectProperty = null;
            $isRef = false;
            continue;
        }
        list($injectProperty, $isRef) = $annotationParser->parser($className, $propertyAnnotation, $propertyName, "", $propertyValue);
    }
    return [$injectProperty, $isRef];
}
$isRef属性にBeanを注入する必要があるか、スカラー値を注入する必要があるかを決定します$injectProperty属性に注入するBean名または特定のスカラー値を指します.この2つは最終的に1つのSwoft\Bean\ObjectDefinitionオブジェクトにカプセル化され、AnnotationResource->$definitionsに保存されます.
属性注入
属性注入は、サービスロケータApp::getBean()を呼び出してBeanを生成するときに行われ、このときサービスロケータは、以前に解析した$isRef$injectProperty情報に基づいて特定の値を属性に注入する.
// Swoft\Bean\Container.php
/**
 *     
 *
 * @param  mixed                $object
 * @param \ReflectionProperty[] $properties $properties
 * @param  mixed                $propertyInjects
 * @throws \InvalidArgumentException
 */
private function injectProperties($object, array $properties, $propertyInjects)
{
    foreach ($properties as $property) {
        //...
      
        //      
        if (\is_array($injectProperty)) {
            $injectProperty = $this->injectArrayArgs($injectProperty);
        }

        //    bean  
        if ($propertyInject->isRef()) {
            $injectProperty = $this->get($injectProperty);
        }

        if ($injectProperty !== null) {
            $property->setValue($object, $injectProperty);
        }
  }

プロパティ注入はサービスロケータに依存し、オブジェクトがユーザーによって手動でnewされている場合、プロパティ注入機能は得られません.
メソッドパラメータ注入
Swoftには、Beanの特定のメソッドを約束通りに直接呼び出すフレームワークがたくさんあります.例えば、フレームワークは、Webリクエストを受信したときにControllertのactionメソッドを呼び出し、適切なAOP接続ポイントがあれば対応する通知メソッドを呼び出します.これらのフレームワーク呼び出しの様々な方法では基本的にメソッドパラメータ注入がサポートされており,Swoftはパラメータタイプ,パラメータ名などの規則に従ってメソッドのパラメータに適切な値を自動的に埋め込む.

メソッド注入の実現は比較的ばらばらであり,各メソッド注入点には同様のコード処理注入データがあり,ここでactionの注入処理を見る.Actionのパラメータ注入処理コードはHandlerAdapter->bindParams()にある
//Swoft\Http\Server\Route\HandlerAdapter.php
/**
 * binding params of action method
 *
 * @param ServerRequestInterface $request request object
 * @param mixed $handler handler
 * @param array $matches route params info
 *
 * @return array
 * @throws \ReflectionException
 */
private function bindParams(ServerRequestInterface $request, $handler, array $matches)
{
    if (\is_array($handler)) {
        list($controller, $method) = $handler;
        $reflectMethod = new \ReflectionMethod($controller, $method);
        $reflectParams = $reflectMethod->getParameters();
    } else {
        $reflectMethod = new \ReflectionFunction($handler);
        $reflectParams = $reflectMethod->getParameters();
    }

    $bindParams = [];
    // $matches    = $info['matches'] ?? [];
    $response   = RequestContext::getResponse();

    // binding params
    foreach ($reflectParams as $key => $reflectParam) {
        $reflectType = $reflectParam->getType();
        $name        = $reflectParam->getName();

        //            $matches   
        if ($reflectType === null) {
            if (isset($matches[$name])) {
                $bindParams[$key] = $matches[$name];
            } else {
                $bindParams[$key] = null;
            }
            continue;
        }

        /**
         * @notice \ReflectType::getName() is not supported in PHP 7.0, that is why use __toString()
         */
        $type = $reflectType->__toString();
        //         Request/Response,        ,          $matches   
        if ($type === Request::class) {
            $bindParams[$key] = $request;
        } elseif ($type === Response::class) {
            $bindParams[$key] = $response;
        } elseif (isset($matches[$name])) {
            $bindParams[$key] = $this->parserParamType($type, $matches[$name]);//      
        } else {
            $bindParams[$key] = $this->getDefaultValue($type);//            (   0)
        }
    }

    return $bindParams;
}
$matchesは、例えば、RESTテンプレート型ルーティング特定フィールドの具体的な値に対応する.実際のアクセス/user/100が一致するルーティングが/user/{uid}である場合、$matches['uid'=>'100']情報を格納する.他の方法パラメータ注入点の実現は大同小異である
コンストラクタ注入
Swoftの現在のコンストラクタ注入の実現はまだ不完全であり,変動がある可能性があるが,ここでは先に述べない.
Swoftソース剖析シリーズ目次:https://segmentfault.com/a/11...