在深刻細節以前,須要確保咱們理解"IOC控制反轉"和"DI依賴注入"是什麼,可以解決什麼問題,這些在維基百科中有很是清晰的說明。php
依賴注入與依賴查找是控制反轉的2種實現方式,後者不多見,咱們主要研究依賴注入。程序員
若是此前沒有接觸過這些概念,可能仍是過於抽象不容易理解,可是下面這個場景你應該是見過的:編程
由於大多數應用程序都是由兩個或是更多的類經過彼此的合做來實現業務邏輯,這使得每一個對象都須要獲取與其合做的對象(也就是它所依賴的對象)的引用。若是這個獲取過程要靠自身實現,那麼這將致使代碼高度耦合而且難以維護和調試。swift
也就是說:"Class A中用到了Class B的對象b,通常狀況下,須要在A的代碼中顯式的new一個B的對象",這就致使若是A想將B替換爲一個更優的實現版本B+時,須要修改代碼顯式的new一個B+對象。緩存
解決這個問題的傳統作法通常是爲B和B+提取一個InterfaceOfB接口,而後讓class A只依賴InterfaceOfB,最終由A類的調用方決定傳入B仍是B+對象,修改調用方代碼和修改類A代碼對咱們來講並無本質的改變,那是否有更好的方式呢?yii2
終於,懶惰的程序員對這種代碼開發方式感到厭煩:由於咱們在代碼裏控制了B類對象的生成,從而致使代碼耦合,即使A類依賴InterfaceOfB,仍是要在程序某處寫死new B()或者new B+()這樣的代碼,怎麼破解?框架
答案是:將B類對象的生成交給一個獨立的對象生成器來負責,那麼A類只須要依賴這個對象生成器,而至於究竟是生成B仍是B+對象,則是對象生成器內部的行爲,這樣就將A和B解耦開了,這就是所謂的"控制反轉",即將控制權交給了對象生成器。yii
這麼簡單的將問題拋給對象生成器可不行,由於對象生成器還要面臨new B仍是new B+的硬編碼問題,所以必須賦予對象生成器一個超能力:函數
總結上述流程就是:對象生成器經過反射機制分析A類的構造函數依賴,並根據配置中的關係生成依賴的對象實例傳入給構造函數,最終完成A類對象的建立。性能
上面的過程就是"依賴注入"主要實現方式了,對象生成器咱們一般成爲"DI Container",也就是"依賴注入容器"。
須要注意的是:B或者B+的構造函數能夠會依賴InterfaceOfC,所以整個依賴關係的分析是遞歸的。
上面在談'DI依賴注入"的時候,咱們很是清楚的瞭解到 DI會根據構造函數進行依賴分析,可是很容易忽視{"InterfaceOfB" : "Class B+"}這個信息的來源。若是DI不知道這個信息,那麼在分析構造函數時是不可能知道接口InterfaceOfB應該對應什麼對象的,這個信息在DI實現中通常是經過set方法主動設置到DI容器的依賴關係中的,固然這個信息的存儲介質能夠是配置文件或者硬編碼傳入。
下面拿PHP的Yii2.0框架爲例,看看它實現DI時的核心思路是什麼,不會講的太細,但上面提到的思路和概念都會有所體現。
public function set($class, $definition = [], array $params = []) { $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); $this->_params[$class] = $params; unset($this->_singletons[$class]); return $this; }
這就是上面提到{"InterfaceOfB" : "Class B+"}的設置接口,好比這樣用:
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
意思就是若是遇到依賴MailInterface的,那麼構造一個Mailer對象給它,params是用於傳給Mailer::__construct的構造參數,以前提過依賴分析是遞歸的,Mailter對象的構造也是DI負責的(不是簡單的new出來),一旦你傳了構造參數給Mailer,那麼DI就不用反射分析Mailter的依賴了,直接傳入params既可new一個Mailer出來。
public function get($class, $params = [], $config = []) { if (isset($this->_singletons[$class])) { // singleton // 此前已經get過而且設置爲單例,那麼返回單例對象既可 return $this->_singletons[$class]; } elseif (!isset($this->_definitions[$class])) { // 非單例須要生成新對象,可是此前沒有set過類定義, // 所以只能直接反射分析構造函數的依賴 return $this->build($class, $params, $config); } // 此前設置過的類定義,對類進行了更具體的定義,幫助咱們更快的構造出對象 $definition = $this->_definitions[$class]; // 類定義能夠是一個函數,用於直接爲DI生成對象 if (is_callable($definition, true)) { // 將set設置的構造參數和本次傳入的構造參數merge到一塊兒 // 而後分析這些傳入的構造參數是否爲實參(好比:int,string),這是由於yii容許 // params是Instance對象,它表明了另一個類定義(它內部指向了DI容器中某個definition) // 爲了這種構造參數可以傳入到當前的構造函數,須要遞歸調用di->get將其建立爲實參。 $params = $this->resolveDependencies($this->mergeParams($class, $params)); // 這個就是函數式的分配對象,前提是構造參數須要確保都是實參 $object = call_user_func($definition, $this, $params, $config); } elseif (is_array($definition)) { // 普通的類定義 $concrete = $definition['class']; unset($definition['class']); // 把set設置的config和此次傳入的config合併一下 $config = array_merge($definition, $config); // 把set設置的params構造參數和此次傳入的構造參數合併一下 $params = $this->mergeParams($class, $params); // 這裏: $class表明的就是MailInterface,而$concrete表明的是Mailer if ($concrete === $class) { // 這裏是遞歸出口,生成目標class對象既可,沒有什麼可研究的 $object = $this->build($class, $params, $config); } else { // 顯然,這裏要構造MailInterface是等同於去構造Mailer對象 $object = $this->get($concrete, $params, $config); } } elseif (is_object($definition)) { return $this->_singletons[$class] = $definition; } else { throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition)); } if (array_key_exists($class, $this->_singletons)) { // singleton $this->_singletons[$class] = $object; } return $object; }
實現思路在此前的分析裏都說的很明白了,並非很難理解。這個函數經過class指定要分配的類,params指定了構造參數,和以前的set原理同樣:若是構造參數齊全是不須要分析依賴的。(最後的config是要注入到對象的額外屬性,屬於yii2特性,不是重點)。
至於build構造對象時,又須要作什麼呢?就是基於反射機制獲取構造函數依賴了哪些類,而後若是params傳入了構造參數那麼直接使用params參數,若是沒有指定則須要遞歸的DI->get()去生成實參,最終經過構造函數生成對象。
protected function build($class, $params, $config) { /* @var $reflection ReflectionClass */ // 利用反射,分析類構造函數的參數, // 其中,返回值reflection是class的反射對象, // dependencies就是構造函數的全部參數了,有幾種狀況: // 1,參數有默認值,直接用 // 2, 沒有默認值,而且不是int這種非class,那麼返回Instance指向對應的class,等待下面的遞歸get list ($reflection, $dependencies) = $this->getDependencies($class); // 傳入的構造函數參數優先級最高,直接覆蓋前面反射分析的構造參數 foreach ($params as $index => $param) { $dependencies[$index] = $param; } // 完整的檢查一次參數,若是依賴是指向class的Instance,那麼遞歸DI->get獲取實例 // 若是是指定int,string這種的Instance,那麼說明調用者並無params傳入值,構造函數默認參數也沒有值, // 必須拋異常 // 若是不是Instance,說明是params用戶傳入的實參能夠直接用 $dependencies = $this->resolveDependencies($dependencies, $reflection); if (empty($config)) { return $reflection->newInstanceArgs($dependencies); } // 最後經過反射對象,傳入全部構造實參,完成對象建立 if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) { // set $config as the last parameter (existing one will be overwritten) $dependencies[count($dependencies) - 1] = $config; return $reflection->newInstanceArgs($dependencies); } else { $object = $reflection->newInstanceArgs($dependencies); foreach ($config as $name => $value) { $object->$name = $value; } return $object; } }
若是你感興趣能夠看看getDependencies和resolveDependencies實現,前者緩存了每一個類的反射信息(反射很耗費性能),後者體現了Instance的用法:表明還沒有實例化的class類對象,須要DI->get獲取。
protected function getDependencies($class) { if (isset($this->_reflections[$class])) { return [$this->_reflections[$class], $this->_dependencies[$class]]; } $dependencies = []; $reflection = new ReflectionClass($class); $constructor = $reflection->getConstructor(); if ($constructor !== null) { foreach ($constructor->getParameters() as $param) { if ($param->isDefaultValueAvailable()) { $dependencies[] = $param->getDefaultValue(); } else { $c = $param->getClass(); $dependencies[] = Instance::of($c === null ? null : $c->getName()); } } } $this->_reflections[$class] = $reflection; $this->_dependencies[$class] = $dependencies; return [$reflection, $dependencies]; } protected function resolveDependencies($dependencies, $reflection = null) { foreach ($dependencies as $index => $dependency) { if ($dependency instanceof Instance) { if ($dependency->id !== null) { $dependencies[$index] = $this->get($dependency->id); } elseif ($reflection !== null) { $name = $reflection->getConstructor()->getParameters()[$index]->getName(); $class = $reflection->getName(); throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); } } } return $dependencies; }
最後還想簡單說一下yii2的ServiceLoader,它基於DI Container封裝了一層,將組件component單例維護在ServiceLoader內,而component的生成則經過DI Container實現。
不過有意思的是,ServiceLoader這樣的實現並沒能充分的使用DI Container的構造依賴注入能力,僅僅是傳入component的class完成對象建立,最後注入了幾個config指定的屬性而已,並無控制params的能力,這個能夠看ServiceLoader中的set和get方法,然而這個設計基本要求了component的構造函數參數都應該能獨立構造而不須要外部干預(干預是指DI->set進行類定義)。
除去ServiceLoader不談,整個yii2.0框架也沒找到能夠經過配置文件自動化調用DI->set進行類定義的能力,硬編碼屬於走倒退的路,這基本上致使yii2.0對DI的應用能力停留在ServiceLoader層面,在遞歸解析依賴時也基本只能走無構造參數或者默認參數構造的路子。
正是在這種背景下,yii2.0的"依賴注入"也基本蛻化爲ServiceLoader的get嵌套get,也就是相似"依賴查找"概念:在配置中分別寫好A和B的component配置,而且配置A compoenent依賴B component,而後經過ServiceLoader獲得A component,A類內部從配置中取出依賴的component(也就是B),最後經過ServiceLoader獲得B component。