laravel框架学习(01):开篇依赖注入和服务容器入门

记录自己源码阅读的过程。

一、开篇

         对于框架源码的阅读,千头万绪,不知道从哪里开始,那就从拜读作者的大作开始吧。先理解作者的思想,有理论的支撑,看起代码来,就很顺畅了。

1.laravel作者: 

Taylor Otwell

2.作者出的书籍:

《From Apprentice To Artisan - Advanced Application Architecture With Laravel 4》 ,译作 《从学徒到工匠 - 基于 Laravel 4 进行高级应用架构》

 二、关键概念:

作者说整个 Laravel 框架的基石是一个功能强大的 IoC 容器(控制反转容器)(java的spring也相似)

要先理解几个概念。依赖注入,服务容器,反射。

1.依赖注入篇

什么是依赖?A类执行依赖B类

什么是注入,注入什么?将B类对象注入到A类的构造方法中

先看官网的一个demo,看注释

//实现一个提醒用户该续费的功能
//定义一个支付接口
interface BillerInterface 
{
    public function bill(array $user, $amount);
}
//定义一个通知接口
interface BillingNotifierInterface 
{
    public function notify(array $user, $amount);
}
//付费接口的实现类:注意构造函数传入的是通知接口参数
class StripeBiller implements BillerInterface
{
    public function __construct(BillingNotifierInterface $notifier)
    {
        $this->notifier = $notifier;
    }
    public function bill(array $user, $amount)
    {
        // todo bill业务,结束后发个通知给用户:这里思考是发短信还是邮件?
        $this->notifier->notify($user, $amount);
    }
}
// 这里就是依赖注入: 支付接口实现类StripeBiller的执行依赖于通知接口的实现类SmsNotifier。 
// 这里设计好处是,支付实现不用考虑如何通知用户,我们直接传递给它一个具体通知实现类 SmsNotifier/EmailNotifier的实例即可。
$sms_biller = new StripeBiller(new SmsNotifier);
$email_biller = new StripeBiller(new EmailNotifier);


        结合上面代码,再理解依赖注入。DI—Dependency Injection,即“依赖注入”组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器;(支付依赖通知)

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;(解耦,组件思维)

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;(通知对象注入支付实例)

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。(对象)

2.服务容器:(IoC 容器,控制反转容器):

       (1)什么是服务容器?

        java也有同样的概念。服务容器即“控制反转容器”。它不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。 

        (2)如何理解好Ioc呢?

        理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

        ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

        举个通俗的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

        (3)那么IoC是如何做的呢?

        有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

        Laravel 的Application 类就是一个继承自 Container 的容器类,它就是整个 Laravel 应用的服务容器。

        在 Laravel 应用中,可以通过 App 门面来访问服务容器,还可以通过辅助函数 app() 来访问,如果是在服务提供者(可以理解为一个专门用于绑定接口与实现到服务容器的地方)中,则一般通过 $this->app 来访问容器。

总之IoC 容器:控制反转容器让依赖注入更方便。

        (4)如何将StripeBiller实现类绑定到容器?        

        接这上面的例子,前面我们定义了支付类接口BillerInterface 和通知类接口BillingNotifierInterface,并在StripeBiller 类中实现了支付功能。那如何将StripeBiller实现类绑定到容器?可以在服务提供者的 register() 方法中完成。

public function register()
{
    $this->app->bind(BillerInterface::class, function ($app) { 
        // 初始化 BillingInterface 实现类时,额外需要一个BillingNotifierInterface 的实现
        return new StripeBiller($app->make(BillingNotifierInterface::class));
    });
}

// 定义接口的实现类 EmailBillingNotifier
namespace App\Services;
use App\Contracts\BillingNotifierInterface;
class EmailBillingNotifier implements BillingNotifierInterface
{
    public function notify(array $user, $amount)
    {
        // TODO: Implement notify() method.
    }
}

// 同样服务提供者中将其绑定到所实现的接口
$this->app->bind(BillingNotifierInterface::class, function ($app) {
   return new EmailBillingNotifier();
});

 

3.反射解决方案

        (1)什么是反射?看解析

// 程序运行中,动态获取类的方法属性等
$reflection = new ReflectionClass(\App\Services\StripeBiller::class);
dump($reflection->getMethods());  # 获取 StripeBiller 类中的所有方法
dump($reflection->getNamespaceName());  # 获取 StripeBiller 的命名空间
dump($reflection->getProperties());  # 获取 StripeBiller 上的所有属性

        (2)如何通过反射来自动解析类的依赖?看代码

class UserController extends BaseController
{
    public function __construct(StripBiller $biller)
    {
        $this->biller = $biller;
    }
}

        这个控制器的构造函数在形参里类型约束了一个 StripBiller 类,通过反射我们可以获取这个类型约束指定的类。

        当 Laravel 的服务容器中没有某个显式绑定类的解析器时(即没有注册接口与对应实现的绑定),将会尝试使用反射来解析。程序流程类似于下面这样:

  1. 已经有一个 StripBiller 的解析器了吗?
  2. 没有?那用反射来检查一下 StripBiller 吧,看看它有没有依赖。
  3. 解析 StripBiller 需要的所有依赖(递归处理)。
  4. 使用 ReflectionClass->newInstanceArgs() 来创建一个新的 StripBiller 实例。(底层逻辑详见 Illuminate\Container\Container 的 resolve 方法)

        修改一下控制器,看看改成这样会发生什么事:

class UserController extends BaseController
{
    public function __construct(BillerInterface $biller)
    {
        $this->biller = $biller;
    }
}

        假设我们没有为 BillerInterface 显式绑定过任何解析器,即没有在服务提供者中定义过下面这样的绑定代码:

$this->app->bind(BillerInterface::class, function ($app) {
    return new StripeBiller($app->make(BillingNotifierInterface::class));
});

        (3)容器该怎么知道要注入什么类呢?

        要知道,接口(或抽象类)本身是不能被实例化的。如果我们没有告知容器更多信息的话,容器是无法实例化这个依赖的。我们需要明确指出哪个类是这个接口的默认实现(正如我们之前在服务提供者 AppServiceProvider 中所做的那样),这就需要用到 bind 方法:

$this->app->bind(BillerInterface::class, StripeBiller::class);

注:这种方式会在绑定时就会实例化 StripeBiller,性能不及匿名函数。

这里,我们只传了一个字符串进去,而不是一个匿名函数。这个字符串告诉容器总是使用 StripBiller 类作为 BillerInterface 接口的默认实现类。

        如果我们需要切换到余额支付作为我们的支付提供者,只需要新写一个 BalancedBiller 来实现 BillerInterface 接口

$this->app->bind(BillerInterface::class, BalancedBiller::class);

         在绑定实现到接口时,你也可以使用 singleton 方法,这样容器在整个请求生命周期中只会实例化这个实现类一次,从而实现单例模式:

$this->app->singleton(BillerInterface::class, StripeBiller::class);

        我们再看看框架已经实现好的服务是如何做的,如redis

<?php

namespace Illuminate\Redis;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;

class RedisServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('redis', function ($app) {
            $config = $app->make('config')->get('database.redis', []);

            return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
        });

        $this->app->bind('redis.connection', function ($app) {
            return $app['redis']->connection();
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['redis', 'redis.connection'];
    }
}

我们看到,也是$this->app->singleton(‘字符串’, 匿名函数);

掌握容器:想了解更多关于容器的知识?去读源码吧!容器在底层只有一个类Illuminate\Container\Container,读完了你就会对容器如何工作有更深的理解。

        以上内容大部分都源自官网,只做摘抄,自己做笔记用,详细内容请移动原文:https://laravelacademy.org/books/laravel-from-appreciate-to-artisan

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值