资讯专栏INFORMATION COLUMN

Swoft 源码剖析 - Swoft 中 IOC 容器的实现原理

Astrian / 3047人阅读

摘要:作者链接來源简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。前言为应用提供一个完整的容器作为依赖管理方案,是功能,模块等功能的实现基础。的依赖注入管理方案基于服务定位器。源码剖析系列目录

作者: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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30702.html

相关文章

  • Swoft 源码剖析 - 目录

    摘要:作者链接來源简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。同时顺手整理个人对源码的相关理解,希望能够稍微填补学习领域的空白。系列文章只会节选关键代码辅以思路讲解,请自行配合源码阅读。 作者:bromine链接:https://www.jianshu.com/p/2f6...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft...

    qpwoeiru96 评论0 收藏0
  • Swoft 源码剖析 - Swoft 注解机制

    摘要:中的注解注解是里面很多重要功能特别是,容器的基础。主流的框架中使用的注解都是借用型注释块型注释中的定义自己的注解机制。在中是注解信息的最终装载容器。使用的信息构造实例或获取现有实例以上就是注解机制的整体实现了。源码剖析系列目录 作者:bromine链接:https://www.jianshu.com/p/ef7...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新...

    zzbo 评论0 收藏0
  • Swoft 源码剖析 - 连接池

    摘要:基于扩展实现真正的数据库连接池这种方案中,项目占用的连接数仅仅为。一种是连接暂时不再使用,其占用状态解除,可以从使用者手中交回到空闲队列中这种我们称为连接的归队。源码剖析系列目录 作者:bromine链接:https://www.jianshu.com/p/1a7...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft Github: https:...

    rozbo 评论0 收藏0
  • Swoft 源码剖析 - Swoole和Swoft那些事 (Http/Rpc服务篇)

    摘要:和服务关系最密切的进程是中的进程组,绝大部分业务处理都在该进程中进行。随后触发一个事件各组件通过该事件进行配置文件加载路由注册。事件每个请求到来时仅仅会触发事件。服务器生命周期和服务基本一致,详情参考源码剖析功能实现 作者:bromine链接:https://www.jianshu.com/p/4c0...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。S...

    张汉庆 评论0 收藏0
  • Swoft 源码剖析 - Swoft AOP 实现原理

    摘要:官方在文档没有提供完整的但我们还是可以在单元测试中找得到的用法。解决的问题是分散在引用各处的横切关注点。横切关注点指的是分布于应用中多处的功能,譬如日志,事务和安全。通过将真正执行操作的对象委托给实现了能提供许多功能。源码剖析系列目录 作者:bromine链接:https://www.jianshu.com/p/e13...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进...

    chenjiang3 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<