laravel服务容器是依赖管理调度中心,只存创建逻辑而非实例;bind()注册工厂、singleton()缓存单例、instance()硬塞实例;接口绑定须在register()中完成且确保provider已注册。

bind()、singleton()、instance() 三个方法不是“存对象”,而是注册不同策略的“创建指令”;用错一个,轻则内存泄漏,重则依赖解析失败或返回不可用实例。
bind() 每次 make 都新建,但不等于“每次都执行构造函数”
表面上看 bind() 是最基础的绑定方式,但它实际注册的是一个“工厂回调”。只要闭包或类名能被容器反射调用,每次 make() 就会走完整构造流程——包括递归解析所有类型提示参数。
- 若绑定的是字符串类名(如
'cache'),Laravel 会查config/app.php中的providers和接口映射,最终落到具体实现类 - 若绑定的是闭包,闭包内
new Foo()的时机由你控制,但容器不会干涉其内部逻辑 - 常见错误:在闭包里直接返回未初始化的 DB 实例(比如没调
DB::connection()),make()后拿到的是个“半残”对象 - 性能影响:高频调用的 service 若用
bind()而非singleton(),可能反复建立数据库连接或重复加载配置
singleton() 缓存的是首次 make 的结果,不是“全局单例”
singleton() 底层只是给 bind() 加了个 $shared = true 标记,真正缓存动作发生在 make() 第一次执行后,结果被写入容器的 $instances 数组。后续 make() 直接返回该引用,跳过构造和依赖解析。
- 注意:缓存的是“实例”,不是“闭包执行结果”——如果你的闭包返回的是新对象,那它仍是单例;但如果闭包返回的是静态属性或外部变量,行为就不可控
- 典型适用场景:数据库连接、Redis 客户端、日志处理器等需要复用底层资源的对象
- 陷阱:在测试中用
singleton()绑定 mock 对象,但忘记在每个 test case 前手动$app->forgetInstance(...),会导致状态污染 - 兼容性:Laravel 5.3+ 所有版本行为一致,但自定义容器扩展时若覆盖
resolve(),可能绕过缓存逻辑
instance() 是硬塞,不走任何解析流程
instance() 是唯一一个不触发构造、不检查依赖、不走反射的方法。它把传入的对象原封不动塞进 $instances,下次 make() 就直接返回它。
- 必须确保该实例已完全初始化(比如
DB::connection('mysql')已成功执行,且连接可用) - 不能用于接口绑定后又想换实现——
instance()不支持覆盖,除非先forgetInstance() - 常见误用:在
boot()中用instance()替换某个服务,但该服务已在register()阶段被其他 provider 提前make()过,导致替换无效 - 调试技巧:用
app()->bound('foo')判断是否已绑定,app()->resolved('foo')判断是否已被解析过
绑定失效?先确认注册时机和 Provider 是否加载
绝大多数“绑了但没生效”的问题,根源不在方法本身,而在代码没被执行——服务提供者根本没被 Laravel 加载,或绑定时机太晚。
- 接口绑定(如
$this->app->bind(RepositoryInterface::class, EloquentRepository::class))**必须放在register()方法里**,不能放boot() - 检查
config/app.php的providers数组是否包含你的AppServiceProvider(默认有,但自定义 provider 容易漏) - 运行
php artisan config:clear后再试,避免配置缓存干扰 - 在 tinker 中验证:
app()->make(RepositoryInterface::class)报错 Class not found?说明绑定没注册;报错 Target [RepositoryInterface] is not instantiable?说明绑定存在但没指向具体类
真正难的不是记住三个方法怎么写,而是判断某个 service 在生命周期里会被多少次 make()、是否允许共享、有没有副作用——这些决定了该用哪个绑定策略,而不是凭感觉选一个。











