You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Inversify中如何覆盖已注入单例服务的依赖?

Inversify中如何覆盖已注入单例服务的依赖?

我完全理解你的困惑——这种情况在使用Inversify单例服务时太常见了,核心问题其实和单例的生命周期绑定逻辑有关,咱们一步步拆解清楚:

为什么会出现这个问题?

当你把ConsumerService声明为单例(Inversify默认就是单例作用域,除非指定其他类型)时,容器在你第一次调用container.get<ConsumerService>(ConsumerService)的时候,就会创建这个实例并缓存起来。此时ConsumerService构造函数里注入的ProviderService,是当时容器绑定的那个原始实例。

后续你调用container.rebindSync修改ProviderService的绑定,只是改变了容器未来resolve时会返回的实例,但不会去修改已经缓存的ConsumerService实例里的providerService引用——毕竟Inversify不会自动追踪和更新已创建实例的内部依赖,这不符合单例“一旦创建就保持稳定”的设计初衷。

解决方案:让Consumer动态获取最新的Provider

如果你想保持ConsumerService为单例,同时能动态切换它依赖的ProviderService,有几种优雅的方式:

方案1:注入工厂函数,延迟获取Provider

不要直接注入ProviderService实例,而是注入一个能从容器中获取最新ProviderService的工厂函数。这样每次调用consume时,都会拿到当前容器绑定的最新实例:

import { injectable, inject, Container } from 'inversify';
import sinon from 'sinon';

const container = new Container();

@injectable()
class ProviderService {
  public provide() {
    return 'Original';
  }
}

// 绑定Provider的工厂函数
container.bind<() => ProviderService>(ProviderService).toFactory(() => {
  return () => container.get<ProviderService>(ProviderService);
});
// 同时绑定Provider本身的实例
container.bind(ProviderService).toSelf();

@injectable()
class ConsumerService {
  constructor(
    // 注入工厂函数,而不是直接注入实例
    @inject(ProviderService) private getProvider: () => ProviderService,
  ) { }

  public consume() {
    // 每次调用时获取最新的Provider实例
    const provider = this.getProvider();
    console.log('Consumed', provider.provide());
  }
}

container.bind(ConsumerService).toSelf();

// 第一次调用
let consumerService = container.get<ConsumerService>(ConsumerService);
consumerService.consume(); // 输出 "Consumed Original"

// 替换Provider
const providerStub = sinon.createStubInstance(ProviderService);
providerStub.provide.returns('Overwritten!');
container.rebindSync(ProviderService).toConstantValue(providerStub);

// 第二次调用
consumerService = container.get<ConsumerService>(ConsumerService);
consumerService.consume(); // 输出 "Consumed Overwritten!"

方案2:使用Inversify的Provider接口

Inversify内置了Provider接口,专门用于延迟获取依赖,用法和工厂函数类似,但更贴合DI的设计风格:

import { injectable, inject, Container, Provider } from 'inversify';
import sinon from 'sinon';

const container = new Container();

@injectable()
class ProviderService {
  public provide() {
    return 'Original';
  }
}

// 绑定Provider的Provider接口实现
container.bind<Provider<ProviderService>>(ProviderService).toProvider<ProviderService>((context) => {
  return () => context.container.get<ProviderService>(ProviderService);
});
container.bind(ProviderService).toSelf();

@injectable()
class ConsumerService {
  constructor(
    @inject(ProviderService) private providerProvider: Provider<ProviderService>,
  ) { }

  public consume() {
    const provider = this.providerProvider.get();
    console.log('Consumed', provider.provide());
  }
}

container.bind(ConsumerService).toSelf();

// 测试流程和之前一致,第二次调用会输出Overwritten!

方案3:手动更新已缓存的Consumer实例(不推荐)

如果你实在不想修改注入逻辑,也可以在rebind之后,手动修改已缓存的ConsumerService实例的providerService属性,但这种方式破坏了封装性,不推荐在生产代码中使用:

// rebind之后
const consumer = container.get<ConsumerService>(ConsumerService);
consumer.providerService = providerStub; // 直接修改内部属性
consumer.consume(); // 此时会输出"Consumed Overwritten!"

总结

  • 单例服务的依赖是在实例化时注入的,容器不会自动更新已缓存实例的依赖;
  • 要动态切换依赖,推荐使用工厂函数Inversify Provider接口,让Consumer每次使用时都获取最新的依赖实例;
  • 如果你能接受ConsumerService为Transient作用域,那确实是最简单的方式,但如果需要保持单例,上面的前两种方案更合适。

火山引擎 最新活动