在Laravel框架中,服务容器是通过Illuminate\Container\Container类来实现的,其实现原理与上述实例相同,这里给出该容器类的工作示意图,如图所示。需要说明的是,服务绑定有时也称为服务注册,在全文中两者意义相同,只是对于不同上下文环境某种说法更加贴切而已。
对于程序设计来说,源码是最好的老师,一切的概念通过描述或者加工后,都会存在意义上的偏差,只有通过了解源码才能真正领会其中的含义。这里给出Laravel框架中关于服务容器类实现的部分源码,通过与上面实例的对照,并结合Laravel框架容器类的示意图,进一步理解实现的方法和思想,加深对IoC等概念的理解。具体代码如下:
服务容器类中定义了两个用于管理服务的属性,分别是$bindings和$instances,其中$bindings用于存储提供服务的回调函数,而$instances用于存储程序中共享的实例,也可以称为单例。
这几个函数实现了Laravel框架中服务容器的服务绑定功能,主要是由bind()函数实现的。singleton()函数实现的是单例绑定,即程序中如果没有服务名称对应的实例对象,则通过服务容器实例化一个后并进行记录,如果在后续程序中还需要同名的服务时则返回先前创建的服务实例对象。该函数相当于bind()函数的一个特例,即参数$shared值为true的情况。对于bind()函数实现的服务绑定功能,在忽略$shared参数的情况下,即不讨论单例还是普通的服务,可以分为两种情况,如果参数$concrete为一个回调函数,则直接将回调函数与服务名称$abstract进行绑定;如果参数$concrete为一个名称,则首先需要通过getClosure()函数创建服务回调函数,然后将该回调函数与服务名称绑定,总之需要实现一个可以生成相应服务实例对象的回调函数与服务名称进行绑定。接下来介绍服务解析的实现,代码如下:
服务解析过程略微复杂一点,可以将其分为两个步骤来完成,一个是完成对应服务的查找,另一个是完成服务的实现,一般是指完成实例化对象的创建。这两个步骤分别由make()和build()函数完成。首先介绍服务查找过程,即由make()函数实现的功能。该函数需要提供两个参数,分别是$abstract和$parameters,$abstract可以看做是服务名称,而$parameters是创建实例化对象需要的参数,即一个类实例化时的依赖。对于服务的查找是根据服务名称$abstract来进行的,首先通过getAlias()函数来查找服务名称是否有别名,对于服务别名的管理是通过服务容器类中的$aliases数组属性实现的,而内容基本是通过Illuminate\Foundation\Application类中的registerCoreContainerAliases()函数注册的,如一个简单的实例,Illuminate\Contracts\Container\Container抽象类的别名为“app”,如果查找到了别名,将查找该别名对应的服务,如果该抽象类没有别名,则继续进行查找。然后在服务容器的共享实例数组($instances属性)中查找服务名称的实例,如果查找到则说明该服务名称对应为单例,直接返回先前实例化的对象,否则继续查询。接下来,会通过getConcrete()获取服务名称的实体,在服务绑定时,一个服务名称一般绑定一个回调函数用于生成实例对象,而这个回调函数就相当于服务名称的实体。这个实体的查找就是通过容器中的$bindings数组属性实现的,如果查找到则返回实体,否则修改服务名称的形式继续下一次的查找。然后,会通过isBuildable()函数判断服务实体能否创建实例化对象,如果可以则转到下一个步骤,否则继续通过make()函数来查找。在完成实例对象的创建后,通过isShared()判断该服务是否为单例,如果是需要在共享实例对象数组($instances)中记录。下面介绍实例化对象的创建步骤,代码如下:
在通过make()函数查找到服务实体后,会将其传递给build()函数用于对象的创建,如果服务实体就是一个闭包函数,则直接调用该闭包函数完成服务实例化对象的创建,如果服务实体只是一个具体类的类名,则需要通过反射机制来完成实例化对象的创建。通过反射机制完成对象实例化的过程,首先是根据将要实例化的类名称获取反射类(ReflectionClass)实例,然后获取该类在实例化过程中的依赖,即构造函数需要的参数,在build()函数中,通过getDependencies()函数来实现依赖的生成,如果在服务解析时提供了相应的参数,即通过$parameters参数提供,则直接使用提供的参数,如果没有提供,则通过服务容器中的resolveNonClass()函数来获取默认参数,或者通过resolveClass()函数来创建,而创建的方式也是通过服务容器,所以服务容器解决依赖注入的问题就是通过这部分代码实现的。在解决了依赖的问题后,可以直接通过反射机制完成服务实例对象的创建。通过在Laravel框架源码的基础上分析服务容器的实现过程,读者应该对服务容器的概念、IOC模式及依赖注入等概念有了进一步的了解,在后续章节中,还会在实际应用中多次遇到这些概念,掌握这部分内容对于后面了解Laravel框架的工作过程将十分有益。