从bin/swoft开始,阅读Swoft框架源码(六--三)--BeanProcessor之bean初始化

码农天地 -
从bin/swoft开始,阅读Swoft框架源码(六--三)--BeanProcessor之bean初始化

通过前面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了.

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

php介绍

PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。

Tags 标签

phpswooleswoft

扩展阅读

加个好友,技术交流

1628738909466805.jpg