Lazy作用于容器中非单例、非自动加载的服务,通过@Inject(lazy: true)或makeWith实现延迟实例化,但需避开构造函数依赖、移除@Singleton、确保调用发生在方法体而非初始化阶段。

Hyperf 中 Lazy 配置到底作用在哪儿?
它不作用在控制器、中间件或命令类上,只对容器中通过 make() 或依赖注入获取的「非单例、非自动加载」服务生效。如果你给一个 @Inject 的属性加了 Lazy=true,但对应类没被声明为 singleton=false 或未配置 @Value/@Config 等延迟触发场景,那根本不会延迟——实例化早已在容器构建时完成。
真正起效的典型场景是:某个服务初始化开销大(比如连 Redis、读大配置、启子进程),但它只在特定分支里才被用到,而该服务又不是单例,且你明确控制它的获取方式。
-
Lazy必须配合@Inject(lazy=true)或$container->makeWith(YourService::class, [], true) - 对应服务类不能有
@Singleton注解,否则容器首次make就已实例化,lazy失效 - 若服务被其他单例类构造函数直接依赖,哪怕你标了
lazy,也会因依赖链提前触发——这是最容易忽略的坑
怎么写才能让 Lazy 真正生效?
关键在「切断提前依赖」和「显式延迟获取」。下面是最小可验证写法:
// app/Service/HeavyService.php
<?php
namespace App\Service;
use Hyperf\Di\Annotation\Inject;
class HeavyService
{
public function __construct()
{
// 模拟耗时初始化
sleep(2);
echo "HeavyService instantiated\n";
}
public function doWork(): string
{
return 'done';
}
}
// app/Controller/IndexController.php
<?php
namespace App\Controller;
use App\Service\HeavyService;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
#[Controller]
class IndexController
{
// ❌ 错误:即使加 lazy=true,构造时仍会实例化(因为被 Controller 实例依赖)
// #[Inject(lazy=true)] private HeavyService $heavy;
// ✅ 正确:不放在属性上,改用方法内按需 make
#[GetMapping("/work")]
public function work()
{
// 此时才触发实例化,且仅当请求 /work 时
$heavy = $this->container->makeWith(HeavyService::class, [], true);
return $heavy->doWork();
}
}
- 不要把
@Inject(lazy=true)用在控制器属性上——Hyperf 的控制器本身是每次请求 new 的,其属性注入发生在构造阶段,lazy形同虚设 - 必须用
makeWith(..., [], true)或get(YourService::class, true)显式触发延迟解析 - 如果服务需要传参(如
makeWith(YourService::class, ['host' => 'x'])),lazy=true依然有效,参数会在真正实例化时传入
Lazy 和容器配置 singleton 的关系
Lazy 不改变生命周期策略,它只推迟「实例创建时机」;而 singleton 决定「是否复用同一实例」。两者正交,但组合使用时逻辑容易绕晕:
- 服务标记
@Singleton+@Inject(lazy=true)→ 第一次make才初始化,之后全走缓存,lazy只生效一次 - 服务未标记
@Singleton+@Inject(lazy=true)→ 每次makeWith(..., [], true)都新建实例,lazy每次都起作用 - 服务未标记
@Singleton+ 属性@Inject(无lazy)→ 每次控制器 new 时都立即 new 服务,完全不延迟
所以如果你希望「每个请求都新建 HeavyService,但仅在用到时才建」,就必须去掉 @Singleton,并坚持用 makeWith(..., [], true) 获取。
别忘了检查 scan 配置和注解扫描范围
Hyperf 默认只扫描 app/ 下带注解的类。如果你把 HeavyService 放在 library/ 或 common/ 目录下,又没配 scan,那 @Inject 根本不会识别,更别说 lazy 了。
确认 config/autoload/scan.php 包含对应路径:
'paths' => [
BASE_PATH . '/app',
BASE_PATH . '/common', // 如果服务放这里,必须加
],
- 漏掉路径会导致注解失效,
@Inject(lazy=true)被忽略,容器 fallback 到反射构造,失去所有控制 - 开发时可用
php bin/hyperf.php di:scan手动触发扫描并查看日志,确认类是否被识别 - 线上环境建议关闭自动扫描,用
di:scan预生成映射,避免 runtime 扫描损耗
真正要按需加载,核心就三点:服务不单例、不被提前依赖、用 makeWith(..., [], true) 显式拉取。其余都是围绕这三点的配置校验和路径管理。


















