【问题标题】:In Nest.js, how to get a service instance inside a decorator?在 Nest.js 中,如何在装饰器中获取服务实例?
【发布时间】:2019-02-05 22:51:18
【问题描述】:

CustomDecorator中,如何访问Nest.js中定义的服务实例?

export const CustomDecorator = (): MethodDecorator => {
  return (
    target: Object,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
    ) => {

    // Here, is possibile to access a Nest.js service (i.e. TestService) instance?

    return descriptor;
  }
};

【问题讨论】:

  • 到目前为止,您找到更好的解决方案了吗?

标签: javascript typescript ecmascript-6 nestjs typescript-decorator


【解决方案1】:

聚会迟到了,但因为我遇到了类似的问题 (Use global nest module in decorator) 并偶然发现了这个问题。

import { Inject } from '@nestjs/common';
export function yourDecorator() {
  const injectYourService = Inject(YourServiceClass);

  return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
    // this is equivalent to have a constructor like constructor(yourservice: YourServiceClass)
    // note that this will injected to the instance, while your decorator runs for the class constructor
    injectYourService(target, 'yourservice');

    // do something in you decorator

    // we use a ref here so we can type it
    const yourservice: YourServiceClass = this.yourservice;
    yourservice.someMethod(someParam);
  };
}

【讨论】:

    【解决方案2】:

    我试图在 ParamDecorator 中使用我的配置服务,所以我通过创建它的新实例来访问我的服务:

    export const MyParamDecorator = createParamDecorator((data, req) => {
    
      // ...
      const configService = new ConfigService(`${process.env.NODE_ENV || 'default'}.env`);
      const myConfigValue = configService.getMyValue();
      // ...
    });
    

    【讨论】:

    • 在 nest.js 中创建 @Injectable 服务类的新实例是不好的做法,应该避免
    【解决方案3】:

    遇到这个问题,花了一天时间试图找出一个好的答案。这可能并不适合所有用例,但我能够在 Nest 的核心包中复制一个通用模式以满足我的需求。

    我想创建自己的装饰器来注释控制器方法以处理事件(例如,@Subscribe('some.topic.key') async handler() { ... }))。

    为了实现这一点,我的装饰器使用来自@nestjs/commonSetMetadata 来注册一些我需要的元数据(它被应用到的方法名称、它所属的类、对该方法的引用)。

    export const Subscribe = (topic: string) => {
      return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
        SetMetadata<string, RabbitSubscriberMetadataConfiguration>(
          RABBITMQ_SUBSCRIBER,
          {
            topic,
            target: target.constructor.name,
            methodName: propertyKey,
            callback: descriptor.value,
          },
        )(target, propertyKey, descriptor);
      };
    };
    

    从那里,我能够创建自己的模块,该模块与 Nest 的生命周期钩子挂钩,以查找我用我的装饰器装饰的所有方法,并对其应用一些逻辑,例如:

    @Module({
      imports: [RabbitmqChannelProvider],
      providers: [RabbitmqService, MetadataScanner, RabbitmqSubscriberExplorer],
      exports: [RabbitmqService],
    })
    export class RabbitmqModule implements OnModuleInit {
      constructor(
        private readonly explorer: RabbitmqSubscriberExplorer,
        private readonly rabbitmqService: RabbitmqService,
      ) {}
    
      async onModuleInit() {
        // find everything marked with @Subscribe
        const subscribers = this.explorer.explore();
        // set up subscriptions
        for (const subscriber of subscribers) {
          await this.rabbitmqService.subscribe(
            subscriber.topic,
            subscriber.callback,
          );
        }
      }
    }
    

    资源管理器服务使用@nestjs/core 中的一些实用程序来内省容器并处理查找所有带有元数据的修饰函数。

    @Injectable()
    export class RabbitmqSubscriberExplorer {
      constructor(
        private readonly modulesContainer: ModulesContainer,
        private readonly metadataScanner: MetadataScanner,
      ) {}
    
      public explore(): RabbitSubscriberMetadataConfiguration[] {
        // find all the controllers
        const modules = [...this.modulesContainer.values()];
        const controllersMap = modules
          .filter(({ controllers }) => controllers.size > 0)
          .map(({ controllers }) => controllers);
    
        // munge the instance wrappers into a nice format
        const instanceWrappers: InstanceWrapper<Controller>[] = [];
        controllersMap.forEach(map => {
          const mapKeys = [...map.keys()];
          instanceWrappers.push(
            ...mapKeys.map(key => {
              return map.get(key);
            }),
          );
        });
    
        // find the handlers marked with @Subscribe
        return instanceWrappers
          .map(({ instance }) => {
            const instancePrototype = Object.getPrototypeOf(instance);
            return this.metadataScanner.scanFromPrototype(
              instance,
              instancePrototype,
              method =>
                this.exploreMethodMetadata(instance, instancePrototype, method),
            );
          })
          .reduce((prev, curr) => {
            return prev.concat(curr);
          });
      }
    
      public exploreMethodMetadata(
        instance: object,
        instancePrototype: Controller,
        methodKey: string,
      ): RabbitSubscriberMetadataConfiguration | null {
        const targetCallback = instancePrototype[methodKey];
        const handler = Reflect.getMetadata(RABBITMQ_SUBSCRIBER, targetCallback);
        if (handler == null) {
          return null;
        }
        return handler;
      }
    }
    

    我并不认为这是处理此问题的最佳方法,但它对我来说效果很好。使用此代码需要您自担风险,它应该可以帮助您入门:-)。我改编了这里提供的代码:https://github.com/nestjs/nest/blob/5.1.0-stable/packages/microservices/listener-metadata-explorer.ts

    【讨论】:

    • 谢谢分享。我也需要这样做。 (在这种情况下,安排可重复的任务)。
    • 我发现了npmjs.com/package/@nestjs-plus/discovery,这很有帮助。
    • 我和那个库的维护者一起工作,很高兴你找到它;)
    • 酷,我在那里提出了关于缓存的建议。
    【解决方案4】:

    我们有几点:

    • 将创建在decorated instance 之前执行的属性装饰器。
    • 装饰者想使用some instancedecorated instance 的注入器解析。

    作为一种简单的方法 - 使用由decorated instance 注入的some instance

    @Injectable()
    export class CatsService {
      constructor(public myService: MyService){}
    
      @CustomDecorator()
      foo(){}
    }
    
    export const CustomDecorator = (): MethodDecorator => {
      return (
        target: Object,
        propertyKey: string | symbol,
        descriptor: PropertyDescriptor
      ) => {
    
        const originalMethod = descriptor.value;
    
        descriptor.value = function () {
          const serviceInstance = this;
          console.log(serviceInstance.myService);
    
        }
    
        return descriptor;
      }
    };
    

    PS 我认为 somehow 可以使用 Injector 的实例来获取任何所需的实例(如 angular does)。

    【讨论】:

    • 好主意,但不是我想要的。我需要CustomDecorator 中的服务实例,而不需要CatService 注入MyService。也就是说,MyService 应该只注入我的装饰器(我放置这些 cmets 的地方)。
    • 很好的解决方法,但我也在为此苦苦挣扎。最终我需要一种方法来注入和MyService 只在装饰器中。
    猜你喜欢
    • 2019-08-28
    • 2019-02-01
    • 2018-08-08
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-18
    相关资源
    最近更新 更多