关于php:从binswoft开始阅读Swoft框架源码六三BeanProcessor之bean初始化

4次阅读

共计 6856 个字符,预计需要花费 18 分钟才能阅读完成。

通过后面 2 大节的工作, 当初容器内曾经保留了残缺的 bean 定义对象和名称、别名映射.

本章的工作是梳理 swoft 如何利用 bean 的定义对象, 生成最终可供业务应用的 bean 对象.

初始化 bean 的入口办法:

private function initializeBeans(): void
{
     // 遍历保留的定义对象数组, 失去 bean 名称和定义对象
     /* @var ObjectDefinition $objectDefinition */
     foreach ($this->objectDefinitions as $beanName => $objectDefinition) {
         // 获取定义对象的类型
         $scope = $objectDefinition->getScope();
         // 如果是 request 类型
         // Exclude request
         if ($scope === Bean::REQUEST) {
             // 将定义对象转存到 request 定义对象数组
             $this->requestDefinitions[$beanName] = $objectDefinition;
             // 从全局定义对象数组中删除这个定义对象
             unset($this->objectDefinitions[$beanName]);
             // 跳过
             continue; 
         }
         // 如果类型是 session
         // Exclude session
         if ($scope === Bean::SESSION) {
             // 转存定义对象到 session 定义对象数组
             $this->sessionDefinitions[$beanName] = $objectDefinition;
             // 从全局定义对象数组中移除这个定义对象
             unset($this->objectDefinitions[$beanName]);
             // 跳过
             continue; 
         }
         // 创立 bean 对象
         // New bean
         $this->newBean($beanName);
     }
}

创立 bean 对象:

private function newBean(string $beanName, string $id = '')
{
     // 首先查看这个 bean 是否曾经存在, 如果存在则间接返回这个 bean
     // First, check bean whether has been create.
     if (isset($this->singletonPool[$beanName]) || isset($this->prototypePool[$beanName])) {return $this->get($beanName);
     }
     // 获取定义对象
     // Get object definition
     $objectDefinition = $this->getNewObjectDefinition($beanName);
     // 获取类型
     $scope = $objectDefinition->getScope();
     // 获取别名
     $alias = $objectDefinition->getAlias();
     // Cache reflection class info
     // 获取定义对象保留的类名
     $className = $objectDefinition->getClassName();
     // 创立一个新的类反射对象或从缓存中取出这个类的反射对象
     $reflectClass = Reflections::cache($className);
     // 调用初始化前回调
     // Before initialize bean
     $this->beforeInit($beanName, $className, $objectDefinition);
     // 结构参数
     $constructArgs = [];
     // 获取结构注入对象
     $constructInject = $objectDefinition->getConstructorInjection();
     // 如果存在
     if ($constructInject !== null) {
        // 获取结构参数
        $constructArgs = $this->getConstructParams($constructInject, $id);
     }
     // 如果存在代理类, 则将原来的类反射对象替换为代理类的类反射对象
     // It's proxy class. eg: AOP, RPC client class
     if ($this->handler) {
         $oldCName = $className;
         $className = $this->handler->classProxy($className);
         // New class name from handler->classProxy()
         if ($oldCName !== $className) {$reflectClass = new ReflectionClass($className);
         }
     }
     // 传入反射类和结构参数, 创立反射实例
     $reflectObject = $this->newInstance($reflectClass, $constructArgs);
     // 获取须要注入的属性
     $propertyInjects = $objectDefinition->getPropertyInjections();
     // 递归从类的最顶层父类开始, 顺次遍历属性注入数组, 查找须要注入的属性, 而后给该属性赋值
     // Inject properties values
     $this->newProperty($reflectObject, $reflectClass, $propertyInjects, $id);
     // Alias name
     // Fix: $aliasId !== $id for deny loop get 
     // 设置 bean 的别名
     if ($alias && $alias !== $beanName) {$this->aliases[$alias] = $beanName;
     }
     // 如果反射类有 init 办法
     // Call init method if exist
     if ($reflectClass->hasMethod(self::INIT_METHOD)) {
        // 调用反射实例的 init 办法
        $reflectObject->{self::INIT_METHOD}();}
     // 将反射实例按 bean 名称保留在对应类型的对象数组中
     // 并依据类型返回对应的实例
     return $this->setNewBean($beanName, $scope, $reflectObject, $id);
}

保留新 bean 对象的办法,4 中类型别离保留在 4 个数组中:

private function setNewBean(string $beanName, string $scope, $object, string $id = '')
{switch ($scope) {
         case Bean::SINGLETON: // Singleton
             $this->singletonPool[$beanName] = $object;
             break; 
         case Bean::PROTOTYPE:
             $this->prototypePool[$beanName] = $object;
             // Clone it
             // 将返回值设置为克隆对象
             $object = clone $object;
             break; 
         case Bean::REQUEST:
             // 这里的 ID 应该是申请的协程 ID
             $this->requestPool[$id][$beanName] = $object;
             break; 
         case Bean::SESSION:
             // 这里的 ID 应该是 sessionID
             $this->sessionPool[$id][$beanName] = $object;
             break; 
     }
     return $object;
}

属性注入办法实现:

 private function newProperty(
 $reflectObject,
 ReflectionClass $reflectionClass,
 array $propertyInjects,
 string $id = ''
 ): void {
     // 获取反射类的父反射类对象
     // New parent properties
     $parentClass = $reflectionClass->getParentClass();
     // 如果有父类, 则持续递归, 直到找到最顶层父类
     if ($parentClass !== false) {$this->newProperty($reflectObject, $parentClass, $propertyInjects, $id);
     }
     // 遍历属性注入对象数组
     /* @var PropertyInjection $propertyInject */
     foreach ($propertyInjects as $propertyInject) {
         // 获取属性注入对象标识的属性名
         $propertyName = $propertyInject->getPropertyName();
         // 如果以后类中没有这个属性名, 则跳过后续的注入逻辑
         if (!$reflectionClass->hasProperty($propertyName)) {continue;}
         /** @noinspection PhpUnhandledExceptionInspection */
         // 从反射类中获取对应的反射属性对象
         $reflectProperty = $reflectionClass->getProperty($propertyName);
         // 如果是 static 类型, 则抛出异样
         if ($reflectProperty->isStatic()) {throw new InvalidArgumentException(sprintf('Property %s for bean can not be `static`', $propertyName));
         }
         // 获取属性注入对象中保留的 value
         // Parse property value
         $propertyValue = $propertyInject->getValue();
         // 如果值是字符串且存在以这个值命名的接口
         // Inject interface
         if (is_string($propertyValue) && interface_exists($propertyValue)) {$propertyValue = InterfaceRegister::getInterfaceInjectBean($propertyValue);
         } elseif (is_array($propertyValue)) {
            // 如果是数组, 且数组内存在援用类型
            // 则将其替换成
            $propertyValue = $this->newPropertyArray($propertyValue, $id);
         }
         // Refer config or bean
         if ($propertyInject->isRef()) {$propertyValue = $this->getRefValue($propertyValue, $id);
            // Optimize: Value not exists, skip call setter 
            if ($propertyValue === null) {continue;}
         }
         // Parser property type $propertyType = ObjectHelper::getPropertyBaseType($reflectProperty);
         if (!empty($propertyType)) {$propertyValue = ObjectHelper::parseParamType($propertyType, $propertyValue);
         }
         // First, try set value by setter method
         $setter = 'set' . ucfirst($propertyName);
         if (method_exists($reflectObject, $setter)) {$reflectObject->$setter($propertyValue);
             continue; 
         }
         if (!$reflectProperty->isPublic()) {$reflectProperty->setAccessible(true);
         }
         // Set value by reflection
         $reflectProperty->setValue($reflectObject, $propertyValue);
     }
 }

解决数组参数的办法:

private function newPropertyArray(array $propertyValue, string $id = ''): array
{
     // 遍历属性值数组
     foreach ($propertyValue as $proKey => &$proValue) {
         // 如果属性值是注入对象, 且是援用类型
         if ($proValue instanceof ArgsInjection && $proValue->isRef()) {
             // 获取援用的值
             $refValue = $proValue->getValue();
             // 将援用的值解析为实在的 bean 对象并替换原数组中的值
             $proValue = $this->getRefValue($refValue, $id);
         }
     }
     // 返回被替换后的值数组
     return $propertyValue;
}

通过援用获取实在 bean 对象的办法:

private function getRefValue($value, string $id = '')
 { 
     // 如果援用不是一个字符串, 则间接返回该值
     if (!is_string($value)) {return $value;}
     // 如果值的第一个字符不是.(. 这个字符是配置的应用形式)
     // 则调用 safeNewBean 去获取或创立
     // 实在的 bean 对象
     if (strpos($value, '.') !== 0) {return $this->safeNewBean($value, $id);
     }
     
     // 移除第一个. 字符
     // Remove `.`
     $value = substr($value, 1);
     // Other: read config reference
     if ($this->handler) {
        // 调用 BeanHandler 的 getReferenceValue 办法获取值
        // 实际上就是去 Config 的 bean 对象中获取配置信息
        $value = $this->handler->getReferenceValue($value);
     }
     return $value;
 }

平安获取 bean 办法:

private function safeNewBean(string $beanName, string $id = '')
{
     try {
        // 调用 newBean 获取或创立 bean
        // 这里就呈现了递归
        // 如果两个 bean 对象互相援用, 此处就会导致死循环
        return $this->newBean($beanName, $id);
     } catch (Throwable $e) {throw new InvalidArgumentException($e->getMessage(), 500, $e);
     }
}

总结:

初始化流程:
    1. 遍历定义对象数组.
    2. 如果定义对象是 request 或者 session 类型, 则转存定义对象到对应数组中保留.
    3. 如果是 singleton 或者 prototype 类型, 则依据定义对象创立真正的 bean 对象, 保留在容器对应的对象池中.
创立 bean 对象的流程:
    1. 查看 bean 是否曾经存在于对应的对象池, 如果存在则不再创立.
    2. 获取要创立的 bean 对象的定义对象.
    3. 依据定义对象中设置的类名创立反射类对象, 并将该反射类对象缓存起来.
    4. 调用 bean 生命周期 -- 初始化前回调.
    5. 从定义对象获取结构注入对象, 再从结构注入对象中获取须要注入的结构参数.
    6. 查看以后要创立的 bean 对象的类是否有代理类, 如果有, 则将先前创立的类反射对象替换成代理类的类反射对象.
    7. 通过类反射对象和结构参数, 创立该类的实例对象(这就是咱们最终须要的 bean 对象).
    8. 通过定义对象获取属性注入对象数组.
    9. 依据将属性注入对象中的值类型, 以不同形式将值赋值给实例对象.
        9.1. 递归遍历反射类及其所有父类.
        9.2. 顺次遍历属性注入对象数组, 找到匹配的属性.
        9.3. 判断值的类型, 如果是一般类型, 则间接进入赋值流程.
        9.4. 如果是援用类型 (或数组中蕴含援用类型), 则调用获取援用值的办法, 获取(递归, 如果这个 bean 对象不存在则持续调用 bean 对象生成办法) 援用值对应的 bean 对象.
    10. 保留别名和 bean 名称的映射.
    11. 如果反射类有 init 办法, 则调用反射实例的 init 办法.
    12. 将最终的反射实例保留在对应的对象池中, 并按类型返回一个对应的对象, 此处保留的反射实例就是咱们最终生成的 bean 对象.
当然, 还有一些干线细节, 此大节中没有讲到的, 可能后续回再做补充. 比方创立 bean 对象流程中的:
    1. 第 6 步中波及的代理类.
    2.……

至此,bean 处理器的工作曾经实现. 当初, 咱们曾经能够在业务中应用 bean 了.

正文完
 0