依赖注入和控制反转
- 之前在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
注解的属性,自动填充对应的属性