做者:bromine
连接:https://www.jianshu.com/p/a23...
來源:简书
著做权归做者全部,本文已得到做者受权转载,并对原文进行了从新的排版。
Swoft Github: https://github.com/swoft-clou...php
Swoft为应用提供一个完整的IOC容器做为依赖管理方案 ,是Swoft AOP功能,RPC模块等功能的实现基础 。
他主要解决的功能有三个:
1. 避免了麻烦地手工管理对象间种种嵌套依赖。
2. 对象的依赖关系再也不在编译期肯定,提供了运行期改变行为的更多弹性。
3. 对象能够再也不依赖具体实现,而是依赖抽象的接口或者抽象类
对依赖管理有兴趣的同窗能够查阅马丁大叔的这篇文章 <Inversion of Control Containers and the Dependency Injection pattern>html
Bean经过类级别注解@Bean
定义,Bean定义后程序能够直接经过App::getBean()
获取到一个Bean的实例。git
App::getBean()
提供 服务定位器 式的依赖管理方式,用于能够经过访问服务定位器获取特定的实例,服务定位器解决了"实例构造,实例间依赖管理,具体实现类选择"的问题,并对用户屏蔽相关细节。github
Container->set()
方法是App::getBean()
底层实际建立bean的方法。原理是经过反射和各类注解(参考注解章节)提供的信息和方法构造Bean的一个代理对象。web
//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; }
相对于 服务定位器,依赖注入是一种更加先进的依赖管理实践。segmentfault
在服务定位器模式中,客户端须要调用服务定位器自己,对服务定位器自己存在依赖;
在依赖注入模式中,客户端和依赖注入管理器之间关系也是控制反转的,客户端并不知道依赖管理器的存在,由依赖管理器调用客户端并注入具体的依赖对象。数组
Swoft的依赖注入管理方案基于服务定位器。提供的注入方式有三种:app
/** * @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三者是典型的属性注入用的注解声明,在一个Bean类中声明这三种注解的属性会分别被注入特定的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名或者具体标量值
这二者最终会封装进一个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会根据参数类型,参数名等规则自动给方法的参数填充合适的值。
<?php //App\Controllers\RouteController.php; /** * 这个例子中,除了Request 和Response 是固定的注入特定结构的对象,其余参数都是根据路由规则注入 * @RequestMapping(route="user/{uid}/book/{bid}/{bool}/{name}") * * @param bool $bool 参考RequestMapping * @param Request $request * @param int $bid * @param string $name * @param int $uid * @param Response $response * * @return array */ public function funcArgs(bool $bool, Request $request, int $bid, string $name, int $uid, Response $response) { //... }
方法注入的实现较为零散,每一个方法注入点都会有相似的代码处理注入的数据,这里看一下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...