摘要:構造器注入實現特定參數的構造函數,在新建對象時傳入所依賴類型的對象。
基本概念
1.依賴倒置(反轉)原則(DIP):一種軟件架構設計的原則(抽象概念,是一種思想)
在面向對象編程領域中,依賴反轉原則(Dependency inversion principle,DIP)是指一種特定的解耦(傳統的依賴關系創建在高層次上,而具體的策略設置則應用在低層次的模塊上)形式,使得高層次的模塊不依賴于低層次的模塊的實現細節,依賴關系被顛倒(反轉),從而使得低層次模塊依賴于高層次模塊的需求抽象。
該原則規定:
1.高層次的模塊不應該依賴于低層次的模塊,兩者都應該依賴于抽象接口。
2.抽象接口不應該依賴于具體實現。而具體實現則應該依賴于抽象接口。
在上圖中,高層對象A依賴于底層對象B的實現;圖2中,把高層對象A對底層對象的需求抽象為一個接口A,底層對象B實現了接口A,這就是依賴反轉。
該原則顛倒了一部分人對于面向對象設計的認識方式。如高層次和低層次對象都應該依賴于相同的抽象接口。它轉換了依賴,高層模塊不依賴于低層模塊的實現,而低層模塊依賴于高層模塊定義的接口。通俗的講,就是高層模塊定義接口,低層模塊負責實現。
2.控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式,一種設計原則)
控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。
它把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的“控制反轉”概念就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器。
實現控制反轉主要有兩種方式:
1.依賴注入:
2.依賴查找
兩者的區別在于,前者是被動的接收對象,在類A的實例創建過程中即創建了依賴的B對象,通過類型或名稱來判斷將不同的對象注入到不同的屬性中,而后者是主動索取相應類型的對象,獲得依賴對象的時間也可以在代碼中自由控制。
3.依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)
依賴注入有如下實現方式:
接口注入(Interface Injection):實現特定接口以供外部容器注入所依賴類型的對象。
設值注入(Setter Injection): 實現特定屬性的public set方法,來讓外部容器調用傳入所依賴類型的對象。
構造器注入(Constructor Injection): 實現特定參數的構造函數,在新建對象時傳入所依賴類型的對象。
基于注解 : 基于Java的注解功能,在私有變量前加“@Autowired”等注解,不需要顯式的定義以上三種代碼,便可以讓外部容器傳入對應的對象。該方案相當于定義了public的set方法,但是因為沒有真正的set方法,從而不會為了實現依賴注入導致暴露了不該暴露的接口(因為set方法只想讓容器訪問來注入而并不希望其他依賴此類的對象訪問)。
3.依賴查找(DL):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)
依賴查找更加主動,在需要的時候通過調用框架提供的方法來獲取對象,獲取時需要提供相關的配置文件路徑、key等信息來確定獲取對象的狀態
小結
依賴倒置原則(DIP):一種軟件架構設計的原則(抽象概念,一種思想)。
控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式)。
依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)。
IoC容器(也稱DI Container):提供了動態地(自動化)創建、注入依賴單元,映射依賴關系等功能,減少了許多代碼量(DI框架)。
需要注意的一些地方
1.控制反轉的層面
在傳統的應用中,程序流程的順序是由開發者主導的,由于IoC,主導權轉移到了框架的手里(因為IoC容器)
2.控制反轉需要解決的問題
查找,生成所需的實例,返回給需要者(因此又叫依賴注入)
3.實現依賴注入的目的
盡管一個類A對它所依賴的類B是如何實現的一無所知,類A依然能夠與類B通信(通過定義一些通用接口)。類B在開發中可能會有多種實現,依賴注入(同時也是IoC)解決的問題就是自動地將這些類B的實現在需要的時候傳遞給類A。
4.如何實現依賴注入
最基本的思路是構造一個獨立的類,它的功能就是統一為其他所有類的依賴生成所需的實例(assembler,類似容器),然后構造并返回這個類
備注:對類A,類B,類C的定義如下
類A (需要通過容器獲取的)
類B (類A的依賴,廣義上的接口)
類C (類B的具體實現)
1.構造器注入
a)在類A的構造器參數列表中定義了該類所有需要被依賴注入的東西(類B)
b)在容器中需要先定義好某個接口(廣義上的interface,即類B)關聯的某個具體實現類(有時還需要配置一些具體參數,即類C),這些容器配置在不同的開發中很可能是不一樣的。通常這些配置會是一個獨立的文件
c)在需要某個類A的時候通過容器來生成而不是直接new
class MovieLister... (MovieLister相當于類A,MovieFinder相當于類B) public MovieLister(MovieFinder finder) { this.finder = finder; } class ColonMovieFinder... (這個相當于類c) public ColonMovieFinder(String filename) { this.filename = filename; } (這里返回的pico就是IoC容器) private MutablePicoContainer configureContainer() { MutablePicoContainer pico = new DefaultPicoContainer(); Parameter[] finderParams = {new ConstantParameter("movies1.txt")}; //在使用容器前需要先配置,下面的代碼就是對容器的配置 pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams); pico.registerComponentImplementation(MovieLister.class); return pico; } 下面是通過容器來獲得類A的過程 public void testWithPico() { MutablePicoContainer pico = configureContainer();//獲得一個配置好的容器 MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);//通過容器來獲得類A Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }
2.setter注入
a)在類A中為所有需要注入的依賴類(類B)創建setter方法
b)在獨立的文件配置類A中的依賴的具體實現(即配置類B的具體實現類C)
c)通過容器生成生成類A
class MovieLister... (同樣的MovieLister為類A,MovieFinder為類B) private MovieFinder finder; public void setFinder(MovieFinder finder) { this.finder = finder; } class ColonMovieFinder... (這個同樣的相當于類C) public void setFilename(String filename) { this.filename = filename; } //下面是容器的配置Service Locatorpublic void testWithSpring() throws Exception { ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");//獲得配置好的容器 MovieLister lister = (MovieLister) ctx.getBean("MovieLister"); //通過容器獲取類A Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); } movies1.txt
與DI類似,Service Locator也是用來打破依賴的
基本思想
提供一個獨立的類(即Service Locator),它能夠為整個應用提供所需的所有service(也可以理解為component)。
具體實現
1.在類A中,依賴的所有類都是通過Service Locator獲取的
MovieFinder finder = ServiceLocator.movieFinder();
2.通過配置可以定制在Service Locator中實現如何返回一個特定實例,這個與DI類似
小結
1.實際上可以將Service Locator和DI結合使用,在類A中通過Service Locator獲取依賴,而在Service Locator中則可以通過DI來實現獲取具體的實例(或者將Service Locator與DI互換也可以?)
2.動態的Service Locator:使用一張映射表,通過查表實現(或直接獲?。┚唧w的實例
3.Service Locator與DI 的區別:使用Service Locator時是顯式地調用Locator,而Di并沒有顯式地調用
Yii2中的依賴注入相關的類:
yiidiContainer 容器
yiidiinstance 容器或Service Locator中的東西: 本質上是對于某一個類實例的引用
yiidiServiceLocator
1.yiidiinstance
主要用在兩個地方:
1.在配置DI容器的時候,使用Instance來引用一個類名,接口名或者是別名(即Instance的id屬性)。因此后續DI容器可以將這個引用解析成相應的對象
2.用在那些使用service locator獲取依賴對象的類中
對于 yiidiInstance:
1.表示的是容器中的內容,代表的是對于實際對象的引用。
2.DI容器可以通過他獲取所引用的實際對象。
3.Instance類僅有的一個屬性id一般表示的是實例的類型(即component ID, class name, interface name or alias name)。
2.yiidiContainer
注意:下面所說的“對象類型”的具體定義為“類名,接口名,別名”
對于yiidiContainer
a) 5個私有屬性(都是數組):$_singletons,$_definitions,$_params,$_reflections,$_dependencies
b) $_singletons // 用于保存單例Singleton對象,以對象類型為鍵
c) $_definitions // 用于保存依賴的定義,以對象類型為鍵
d) $_params // 用于保存構造函數的參數,以對象類型為鍵
e) $_reflections // 用于緩存ReflectionClass對象,以對象類型為鍵
f) $_dependencies // 用于緩存依賴信息,以對象類型為鍵
注意
1.在DI容器中,依賴關系的定義是唯一的。 后定義的同名依賴,會覆蓋前面定義好的依賴。
2.上面的鍵具體就是:帶命名空間的類名,接口名,或者是一個別名
3.對于 $_definitions 數組中的元素,它要么是一個包含了”class” 元素的數組,要么是一個PHP callable, 再要么就是一個具體對象。這就是規范化后的最終結果
4.對于$_singletons數組中的元素,要不就是null(表示還未實例化),要不就是一個具體的實例
5.對于$_params數組中的元素,就是一個數組,包含構造函數的所有參數
6.對于$_reflections數組中的元素,就是一個ReflectionClass對象
7.setter注入可以在實例化后
yiidiContainer使用的具體過程
一個簡單的例子
namespace appmodels; use yiiaseObject; use yiidbConnection; use yiidiContainer; interface UserFinderInterface { function findUser(); } class UserFinder extends Object implements UserFinderInterface { public $db; public function __construct(Connection $db, $config = []) { $this->db = $db; parent::__construct($config); } public function findUser() { } } class UserLister extends Object { public $finder; public function __construct(UserFinderInterface $finder, $config = []) { $this->finder = $finder; parent::__construct($config); } } $container = new Container; $container->set("yiidbConnection", [ "dsn" => "...", ]); $container->set("appmodelsUserFinderInterface", [ "class" => "appmodelsUserFinder", ]); $container->set("userLister", "appmodelsUserLister"); $lister = $container->get("userLister"); // which is equivalent to: $db = new yiidbConnection(["dsn" => "..."]); $finder = new UserFinder($db); $lister = new UserLister($finder);
1.在類A的構造器參數列表中定義了該類所有需要被依賴注入的東西(類B)
2.注冊依賴:
a)yiidiContainer::set()
b)yiidiContainer::setSinglton()
使用到了$_definitions ,$_params, $_singletons
3.對象的實例化
a)解析依賴信息
yiidiContainer::getDependencies() (會被后續的build()調用)
getDependencies():操作$_reflections與$_dependencies
1.會向$_reflections 和 $_dependencies寫入信息
2.使用PHP的反射機制來獲取類的有關信息,主要就是為了從構造器中獲取依賴信息,會將反射得到的信息寫入$_reflections
3.將從構造器中獲取的依賴信息(即構造函數的參數列表)寫入$_dependencies
4.返回值: 數組[$reflection, $dependencies]
yiidiContainer::resolveDependencies() (同樣的會被后續的build()調用)
resolveDependencies()利用getDependencies()獲得的信息進一步具體處理(遞歸調用)。處理依賴信息, 將依賴信息中保存的Instance實例所引用的類或接口進行實例化。
b)創建實例
yiidiContainer::build()
由getDependencies()獲得第一層依賴
由resolveDependencies()遞歸分析依賴,最終生成所有依賴的實例
$reflection->newInstanceArgs($dependencies);//生成所有依賴后生成這個實例
注意:DI容器只支持 yiibaseObject 類,也就是說如果你想你的類可以放在DI容器里,那么必須繼承自 yiibaseObject 類。
4.獲取依賴實例化對象
yiidiContainer::get() a)如果是已經實例化的單例,直接返回($_singletons) b)如果是尚未定義(不存在于$_definition),則說明其實例化沒有依賴,調用build() c)存在$_definition i.$definition為callable,直接調用 ii.$definition為數組,根據$definition數組中的‘class’,遞歸調用get(),遞歸終止的條件是(當具體實現類就是當前的依賴類時),遞歸結束時調用build()進行實例化 iii.$definition為對象,直接返回該對象,并將該對象設置為單例
setSinglton()類似 public function set($class, $definition = [], array $params = []) { //normalizeDefinition()處理后,返回值要么是一個包含了”class” 元素的數組,要么是一個PHP callable, 再要么就是一個具體對象 $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); $this->_params[$class] = $params; unset($this->_singletons[$class]); return $this; } public function get($class, $params = [], $config = []) { if (isset($this->_singletons[$class])) {//是單例,且已經實例化(不為null) // singleton return $this->_singletons[$class]; } elseif (!isset($this->_definitions[$class])) {//還沒有定義過,需要build return $this->build($class, $params, $config); } $definition = $this->_definitions[$class]; if (is_callable($definition, true)) { $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"]); $config = array_merge($definition, $config); $params = $this->mergeParams($class, $params); if ($concrete === $class) {//$concrete相當于之前提到的具體實現類C,而$class則相當于接口類B $object = $this->build($class, $params, $config); } else { $object = $this->get($concrete, $params, $config);//遞歸,直到找到具體的實現類C } } 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; } protected function build($class, $params, $config) { /* @var $reflection ReflectionClass */ list ($reflection, $dependencies) = $this->getDependencies($class); //獲取第一層依賴關系 foreach ($params as $index => $param) { $dependencies[$index] = $param; //額外提供的構造函數參數,添加到依賴中 } $dependencies = $this->resolveDependencies($dependencies, $reflection);//遞歸解析依賴,并會在此返回依賴的實例 if (!$reflection->isInstantiable()) { throw new NotInstantiableException($reflection->name); } if (empty($config)) { return $reflection->newInstanceArgs($dependencies);//通過反射實例生成對象 } //config中的對象作為該類的property使用 if (!empty($dependencies) && $reflection->implementsInterface("yiiaseConfigurable")) { // 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; } } 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();//這里要能獲取到類名需要在構造函數中用類型限制參數,否則獲取到null,而且注意對于php的基本類型,獲取到的也是null $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) { //這里的dependency是Instance的實例 $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; }
遞歸調用的示意圖
先看一下各個類的繼承關系
下面以Yii::$app->db為例
1.配置組件
配置的內容:
"components" => [ "db" => [ "class" => "yiidbConnection", "dsn" => "mysql:host=localhost;dbname=wechat", "username" => "root", "password" => "michael", "charset" => "utf8", ],
2.在框架的啟動過程中加載組件的定義
Yii2的啟動
入口腳本:
(new yiiwebApplication($config))->run(); 1.new yiiwebApplication($config) 2.run()
yiiwebApplication的構造函數
public function __construct($config = []) { Yii::$app = $this; static::setInstance($this); //將當前module存到Yii::$app->loadedModules[] $this->state = self::STATE_BEGIN; //1.通過$config配置別名,基本參數 //2.配置核心組件(僅僅是配置,將Yii框架寫好的配置與自己的配置合并) $this->preInit($config); $this->registerErrorHandler($config); //下面這一行是重點,Component是當前類的祖先 //在下面的構造函數中執行了Yii::configure($this, $config),將$config中的配置作為屬性添加到$app中 Component::__construct($config); }
Component::__construct($config)
//實際上下面這個構造函數的定義在yiiaseObject中 public function __construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init(); }
Yii::configure($this, $config)
public static function configure($object, $properties) { foreach ($properties as $name => $value) { //下面這行代碼會觸發魔術方法 ($object->components = $value) //實際執行的代碼是ServiceLocator::setComponents($components) $object->$name = $value; } return $object; }
ServiceLocator::setComponents($components)
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
最終加載組件配置的代碼
//從下面的代碼中可以看到,最終組件的配置被存儲在$app->_definitions數組中 public function set($id, $definition) { if ($definition === null) { unset($this->_components[$id], $this->_definitions[$id]); return; } unset($this->_components[$id]); if (is_object($definition) || is_callable($definition, true)) { // an object, a class name, or a PHP callable $this->_definitions[$id] = $definition; } elseif (is_array($definition)) { // a configuration array if (isset($definition["class"])) { $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException("The configuration for the "$id" component must contain a "class" element."); } } else { throw new InvalidConfigException("Unexpected configuration type for the "$id" component: " . gettype($definition)); } }
3.獲取組件
Yii::$app->db會觸發魔術方法,調用ServiceLocator::__get()
public function __get($name) { if ($this->has($name)) { //已經定義過組件 return $this->get($name); } else { //沒有定義過組件 return parent::__get($name); } } public function has($id, $checkInstance = false) { //這里因為在框架啟動過程中將組件的配置加載到$_definitions中了,所以會返回true return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]); } //通過下面的代碼可以看到,如果組件已經實例化過存儲在$_components中了,就直接返回 //否則通過Yii::createObject($definition)來生成組件實例,并存儲到$_components中 public function get($id, $throwException = true) { if (isset($this->_components[$id])) { return $this->_components[$id]; } if (isset($this->_definitions[$id])) { $definition = $this->_definitions[$id]; if (is_object($definition) && !$definition instanceof Closure) { return $this->_components[$id] = $definition; } else { return $this->_components[$id] = Yii::createObject($definition); } } elseif ($throwException) { throw new InvalidConfigException("Unknown component ID: $id"); } else { return null; } }
Yii::createObject($definition)
//在Yii2框架中要使用DI來生成對象的話,可以通過調用Yii::createObject($definition)實現 public static function createObject($type, array $params = []) { if (is_string($type)) { //使用容器 return static::$container->get($type, $params); } elseif (is_array($type) && isset($type["class"])) { $class = $type["class"]; unset($type["class"]); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { return static::$container->invoke($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException("Object configuration must be an array containing a "class" element."); } throw new InvalidConfigException("Unsupported configuration type: " . gettype($type)); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25699.html
摘要:本文代碼什么是依賴注入對象由框架來創建而不是程序員通過創建。解除了調用者與被調用者之間的依賴。的依賴注入通過提供容器特性。靈活使用可以使我們從依賴關系中解脫出來,專注于業務邏輯。 本文代碼 https://github.com/xialeistudio/yii2-di-demo 什么是依賴注入(DI)? 對象由框架來創建而不是程序員通過 new 創建。跟IoC差不多一個意思。 為什么要...
摘要:調用方法創建類得實例化對象,實際上又調用了依賴注入容器獲取每一個類的實例化對象。依賴注入容器自動解決待實例化類的依賴關系,并返回待實例化類的實例對象。 以下是Yii2源碼中,ServiceLocator(服務定位器)與Container(依賴注入容器)的關系解析圖。 一句話總結 Application繼承了ServiceLocator,是一個服務器定位器,ServiceLocator用...
摘要:反射簡介參考官方簡介的話,具有完整的反射,添加了對類接口函數方法和擴展進行反向工程的能力。此外,反射提供了方法來取出函數類和方法中的文檔注釋。 反射簡介 參考官方簡介的話,PHP 5 具有完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔注釋。 YII2框架中示例 對于yii2框架,應該都知道di容器,...
摘要:好啦,我們看看在框架的不同版本中是怎么處理攻擊,注入等問題的。那要是,又是怎樣處理的喃考慮目前國內網站大部分采集文章十分頻繁,更有甚者不注明原文出處,原作者更希望看客們查看原文,以防有任何問題不能更新所有文章,避免誤導繼續閱讀 作者:白狼 出處:http://www.manks.top/yii2_filter_xss_code_or_safe_to_database.html 本文版權...
摘要:行為是如何注冊到組件的呢通過注冊行為之后,實際上是添加到了的屬性中那么行為中的屬性,就添加到了,中進行直接調用行為里面的方法的時候,實際上觸發了里面的魔術方法繼承鏈圖解 Yii2 框架Trace 準備 了解composer的autoload psr0 psr4 加載機制 了解spl_autoload_register 了解依賴注入的實現原理反射 了解常用魔術方法__set,__get...
閱讀 1682·2019-08-30 15:54
閱讀 3332·2019-08-26 17:15
閱讀 3522·2019-08-26 13:49
閱讀 2582·2019-08-26 13:38
閱讀 2291·2019-08-26 12:08
閱讀 3035·2019-08-26 10:41
閱讀 1369·2019-08-26 10:24
閱讀 3376·2019-08-23 18:35