摘要:作者鏈接來源簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。前言為應用提供一個完整的容器作為依賴管理方案,是功能,模塊等功能的實現基礎。的依賴注入管理方案基于服務定位器。源碼剖析系列目錄
作者:bromine
鏈接:https://www.jianshu.com/p/a23...
來源:簡書
著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。
Swoft Github: https://github.com/swoft-clou...
Swoft為應用提供一個完整的IOC容器作為依賴管理方案 ,是Swoft AOP功能,RPC模塊等功能的實現基礎 。
他主要解決的功能有三個:
1. 避免了麻煩地手工管理對象間種種嵌套依賴。
2. 對象的依賴關系不再在編譯期確定,提供了運行期改變行為的更多彈性。
3. 對象可以不再依賴具體實現,而是依賴抽象的接口或者抽象類
對依賴管理有興趣的同學可以查閱馬丁大叔的這篇文章
Bean通過類級別注解@Bean定義,Bean定義后程序可以直接通過App::getBean()獲取到一個Bean的實例。
App::getBean()提供 服務定位器 式的依賴管理方式,用于可以通過訪問服務定位器獲取特定的實例,服務定位器解決了"實例構造,實例間依賴管理,具體實現類選擇"的問題,并對用戶屏蔽相關細節。
Container->set()方法是App::getBean()底層實際創建bean的方法。原理是通過反射和各種注解(參考注解章節)提供的信息和方法構造Bean的一個代理對象。
//SwoftBeanContainer.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的依賴注入管理方案基于服務定位器。提供的注入方式有三種:
屬性注入/** * @Reference("user") * @var AppLibMdDemoInterface */ private $mdDemoService; /** * @Inject() * @var AppModelsLogicUserLogic */ private $logic; /** * the name of pool * * @Value(name="${config.service.user.name}", env="${USER_POOL_NAME}") * @var string */ protected $name = "";
上面@Reference,@Inject,@value三者是典型的屬性注入用的注解聲明,在一個Bean類中聲明這三種注解的屬性會分別被注入特定的Rpc客戶端代理對象 , 普通的Bean代理對象 ,和配置文件配置值。
屬性注入元信息的解析Bean的各個屬性的注入信息是在注解搜集階段完成的,即在Swoft的啟動階段就已經完成
//SwoftBeanWrapperAbstractWrapper.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名或者具體標量值
這兩者最終會封裝進一個SwoftBeanObjectDefinition對象中并保存在AnnotationResource->$definitions中
屬性注入在調用服務定位器App::getBean()生成Bean的時候進行,此時服務定位器根據之前解析到的$isRef,$injectProperty信息注入特定的值到屬性中。
// SwoftBeanContainer.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()中
//SwoftHttpServerRouteHandlerAdapter.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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/30702.html
摘要:作者鏈接來源簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。同時順手整理個人對源碼的相關理解,希望能夠稍微填補學習領域的空白。系列文章只會節選關鍵代碼輔以思路講解,請自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swoft...
摘要:中的注解注解是里面很多重要功能特別是,容器的基礎。主流的框架中使用的注解都是借用型注釋塊型注釋中的定義自己的注解機制。在中是注解信息的最終裝載容器。使用的信息構造實例或獲取現有實例以上就是注解機制的整體實現了。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/ef7...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新...
摘要:基于擴展實現真正的數據庫連接池這種方案中,項目占用的連接數僅僅為。一種是連接暫時不再使用,其占用狀態解除,可以從使用者手中交回到空閑隊列中這種我們稱為連接的歸隊。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/1a7...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swoft Github: https:...
摘要:和服務關系最密切的進程是中的進程組,絕大部分業務處理都在該進程中進行。隨后觸發一個事件各組件通過該事件進行配置文件加載路由注冊。事件每個請求到來時僅僅會觸發事件。服務器生命周期和服務基本一致,詳情參考源碼剖析功能實現 作者:bromine鏈接:https://www.jianshu.com/p/4c0...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。S...
摘要:官方在文檔沒有提供完整的但我們還是可以在單元測試中找得到的用法。解決的問題是分散在引用各處的橫切關注點。橫切關注點指的是分布于應用中多處的功能,譬如日志,事務和安全。通過將真正執行操作的對象委托給實現了能提供許多功能。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/e13...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進...
閱讀 1970·2021-11-23 09:51
閱讀 883·2021-11-19 09:40
閱讀 836·2021-10-27 14:20
閱讀 5027·2021-10-09 09:52
閱讀 3305·2021-10-09 09:44
閱讀 1735·2021-10-08 10:05
閱讀 5095·2021-09-09 11:47
閱讀 3484·2019-08-30 12:47