hyperf依赖注入和控制反转

依赖注入和控制反转

  • 之前在laravel会用到依赖注入和控制反转,面试也会遇到,一直觉得自己明白的很透彻了,现在hyperf框架又遇到,发现疑问重重,特此整理一下,以免后期再不明白

概念

1. 依赖注入(DI)

  • 对象之间依赖关系由容器在运行期决定,由容器动态的将依赖关系注入到对象之中

2. 控制反转(Ioc)

  • 即把对象的调用权反转交给容器进行装配和管理

注入的实现

1. 普通注入
1.1 简单对象注入

Controller是由DI进行创建管理的,所以可以其构造函数上的参数会自动进行注入

  • 当您希望定义一个可选的依赖项时,可以通过给参数定义为 nullable 或将参数的默认值定义为 null,即表示该参数如果在 DI 容器中没有找到或无法创建对应的对象时,不抛出异常而是直接使用 null 来注入。(该功能仅在 1.1.0 或更高版本可用)
namespace App\Controller;
use App\Service\UserService;

class IndexController
{
    /**
     * @var UserService
     */
    private $userService;

    // 通过在构造函数的参数上声明参数类型完成自动注入
   public function __construct(?UserService $userService)
    {
        $this->userService = $userService;
    }

    public function index()
    {
        $id = 1;
        if ($this->userService instanceof UserService) {
            // 仅值存在时 $userService 可用
            return $this->userService->getInfoById($id);    
        }
        return null;
    }
}
1.2 通过 @Inject 注解注入
  • 注意调用方也就是 IndexController 必须是由 DI 创建的对象才能完成自动注入,Controller 默认是由 DI 创建的,直接 new 该对象不会生效;

    使用 @Inject 注解时需 use Hyperf\Di\Annotation\Inject; 命名空间;

  • @Inject 注解存在一个 required 参数,默认值为 true,当将该参数定义为 false 时,则表明该成员属性为一个可选依赖,当对应 @var 的对象不存在于 DI 容器或不可创建时,将不会抛出异常而是注入一个 null,如下:

namespace App\Controller;
use App\Service\UserService;
use Hyperf\Di\Annotation\Inject;

class IndexController
{
    /**
     * 通过 `@Inject` 注解注入由 `@var` 注解声明的属性类型对象
     * @Inject
     * @var UserService
     */
    private $userService;

    public function index()
    {
        $id = 1;
        // 直接使用
        return $this->userService->getInfoById($id);
    }
}
  • 注意:Inject注解实现原理:当注解定义在类属性时被扫描时会触Inject注解类下面的collectProperty方法,然后会取到该对象,实现注入;
2. 依赖注入
2.1 抽象对象注入
  • Controller 面向的不应该直接是一个 UserService 类,可能更多的是一个 UserServiceInterface 的接口类,此时我们可以通过 config/autoload/dependencies.php 来绑定对象关系达到目的,我们还是通过代码来解释一下。
2.1.1定义接口类
namespace  App\\Service;

interface UserServiceInterface
{
    public function getInfoById(int $id);
}
2.1.2 UserService 实现接口类
namespace App\Service;

class UserService implements UserServiceInterface
{
    public function getInfoById(int $id)
    {
        // 我们假设存在一个 Info 实体
        return (new Info())->fill($id);
    }
}
2.1.3 在config/autoload/dependencies.php内完成关系配置
return [
    \App\Service\UserServiceInterface::class => \App\Service\UserService::class
];
2.1.4 Contoller注入接口,会自动根据配置进行注入
namespace App\Controller;
use App\Service\UserServiceInterface;
use Hyperf\Di\Annotation\Inject;

class IndexController
{
    /**
     * @Inject
     * @var UserServiceInterface
     */
    private $userService;

    public function index()
    {
        $id = 1;
        // 直接使用
        return $this->userService->getInfoById($id);
    }
}
2.2 工厂对象注入
  • UserServiceInterface的注入交给UserServiceFactory来创建
  • 我们假设 UserService 的实现会更加复杂一些,在创建 UserService 对象时构造函数还需要传递进来一些非直接注入型的参数,假设我们需要从配置中取得一个值,然后 UserService 需要根据这个值来决定是否开启缓存模式(顺带一说 Hyperf 提供了更好用的 模型缓存 功能)
2.2.1 创建工厂类生成 UserService 对象
namespace App\Service;
use Hyperf\Contract\ConfigInterface;
use Psr\Container\ContainerInterface;

class UserServiceFactory
{
    // 实现一个 __invoke() 方法来完成对象的生产,方法参数会自动注入一个当前的容器实例
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->get(ConfigInterface::class);
        // 我们假设对应的配置的 key 为 cache.enable
        $enableCache = $config->get('cache.enable', false);
        // make(string $name, array $parameters = []) 方法等同于 new ,使用 make() 方法是为了允许 AOP 的介入,而直接 new 会导致 AOP 无法正常介入流程
        return make(UserService::class, compact('enableCache'));
    }
}
2.2.2 创建UserService类,其中构造函数提供了一个参数
namespace App\Service;

class UserService implements UserServiceInterface
{

    /**
     * @var bool
     */
    private $enableCache;

    public function __construct(bool $enableCache)
    {
        // 接收值并储存于类属性中
        $this->enableCache = $enableCache;
    }

    public function getInfoById(int $id)
    {
        return (new Info())->fill($id);
    }
}
2.2.3 在config/autoload/dependencies.php调整绑定关系
return [
    \App\Service\UserServiceInterface::class => \App\Service\UserServiceFactory::class
];
  • 依赖注入的好处:解耦
  • 为什么要使用抽象对象注入(好处):普通的依赖注入,没有参数的传入,如果有时候需要做一些判断,需要传入一些参数
  • 依赖注入实现原理:
  • 在使用 容器 来实例化类的时候,hyperf 除了自动解析 __construct 之外,还会将带有 @Inject 注解的属性,自动填充对应的属性
上一篇:使用 Spring 实现 AOP.(代码实例)-- 使用 Spring 的 API 接口实现、使用自定义类实现、使用注解方式实现


下一篇:Spring学习--AOP的实现