根据参数注入正确的服务

Posted

技术标签:

【中文标题】根据参数注入正确的服务【英文标题】:Inject correct service based on parameter 【发布时间】:2020-03-30 08:39:11 【问题描述】:

假设我有两个模块正在导出 BServiceCService,这两个服务都扩展了 AService

所以代码如下所示:

abstract class AService 
    public run() 


@Injectable()
export class BService extends AService 

@Injectable()
export class CService extends AService 

@Module(
    providers: [BService],
    exports: [BService],
)
export class BModule 


@Module(
    providers: [CService],
    exports: [CService],
)
export class CModule 

@Injectable()
class AppService 
    constructor(protected readonly service: AService) 

    public run(context: string)  // let's assume context may be B or C
        this.service.run();
    



@Module(
    imports: [CModule, BModule],
    providers: [
        provide: AppService,
        useFactory: () => 
            return new AppService(); // how to use BService/CService depending on the context?
        
    ]
)
export class AppModule 

但关键是,我不能使用 @nestjs/core 中的 REQUEST(直接将其注入 useFactory),因为我在 cron 作业和 API 调用中使用此服务

我也不认为Factory 模式在那里有用,我的意思是它会起作用,但我想正确地做到这一点

我在想property based injection。

但我不确定如何在我的情况下使用它

【问题讨论】:

在下面查看我的答案,看看它是否符合您的需求。这听起来像是使用策略模式的好地方。 ***.com/a/69502460/8148483 【参考方案1】:

如果属性是静态的(例如环境变量),您可以使用自定义提供程序来选择合适的实例。但是,如果属性在某种程度上是动态的,则您不能单独依赖嵌套的依赖注入,因为它会在启动时实例化提供程序(REQUEST 范围除外,这不是您的选择)。

静态属性

创建一个custom provider,它基于静态属性(例如环境变量)实例化所需的实现。


  provide: AService,
  useClass: process.ENV.useBService ? BService : CService,

具有请求范围的动态属性

假设我们有两种不同的服务实现:

@Injectable()
export class BService 
  public count = 0;
  run() 
    this.count++;
    return 'B';
  


@Injectable()
export class CService 
  public count = 0;
  run() 
    this.count++;
    return 'C';
  

当两者的count变量之和为偶数时,应使用BServiceCService 奇怪的时候。为此,我们使用request scope 创建了一个自定义提供程序。


  provide: 'MyService',
  scope: Scope.REQUEST,
  useFactory: (bService: BService, cService: CService) => 
    if ((bService.count + cService.count) % 2 === 0) 
      return bService;
     else 
      return cService;
    
  ,
  inject: [BService, CService],
,

如果我们的控制器现在注入 MyService 令牌 (@Inject('MyService')) 并通过端点公开其 run 方法,它将返回 B C B ...

具有默认范围的动态属性

由于我们要使用默认作用域(Singleton!),所以不能使用nest 的依赖注入的静态实例化。相反,您可以使用委托模式在根类中选择所需的实例(在您的示例中为AService)。

按原样提供所有服务:

providers: [AService, BService, CService]

在您的 AService 中动态决定使用哪个实现:

@Injectable()
export class AService 
  constructor(private bService: BService, private cService: CService) 

  run(dynamicProperty) 
    if (dynamicProperty === 'BService') 
      return this.bService.run();
     else 
      return this.cService.run();
    
  

【讨论】:

这些解决方案都不符合我的要求,你有没有 docs.nestjs.com/providers#property-based-injection 的工作示例? @KrzysztofSzostak 基于属性的注入只是注入依赖项的另一种方式。您使用实例变量而不是使用构造函数。我看不出这对您的情况有何用处。 Nest 只是不提供您正在寻找的动态实例化。我很确定您只有我列出的三个选项。【参考方案2】:

在我看来,工厂方法正是您所需要的。您描述了您需要基于上下文的不同服务,这非常适合工厂方法。让我们试试这个:

创建可注入工厂:

import  Injectable  from '@nestjs/common';
import  AService  from './AService';
import  BService  from './BService';
import  CService  from './CService';

@Injectable()
export class ServiceFactory 

    public getService(context: string) : AService 

        switch(context) 
            case 'a': return new BService();
            case 'b': return new CService();
            default: throw new Error(`No service defined for the context: "$context"`);
        
    

现在将该工厂导入您的应用模块:

import  ServiceFactory  from './ServiceFactory';
import  AService  from './AService';

@Module(
    providers: [AppService, ServiceFactory]
)
export class AppModule 

现在您的应用服务将获取工厂作为依赖项,该依赖项将根据上下文创建适当的服务:

import  ServiceFactory  from './ServiceFactory';
import  AService  from './AService';

@Injectable()
class AppService 

    constructor(readonly serviceFactory: ServiceFactory)  

    public run(context: string) 
        const service: AService = this.serviceFactory.getService(context);
        service.run();
    

【讨论】:

以上是关于根据参数注入正确的服务的主要内容,如果未能解决你的问题,请参考以下文章

web api中的依赖注入

运行 PHPUnit 测试时,容器参数在 Symfony 5 服务中不可用/注入

SQL注入相关知识整理

sql注入手法详解

具有不可注入参数的服务和通用类的 Blazor 构造函数注入 [重复]

SQL注入小结