摘要:构造器注入实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
基本概念
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:使用一张映射表,通过查表实现(或直接获取)具体的实例
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)); }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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...
阅读 1652·2019-08-30 15:54
阅读 3276·2019-08-26 17:15
阅读 3477·2019-08-26 13:49
阅读 2554·2019-08-26 13:38
阅读 2258·2019-08-26 12:08
阅读 2920·2019-08-26 10:41
阅读 1345·2019-08-26 10:24
阅读 3340·2019-08-23 18:35