Skip to content

IOC 机制

后端系统中,会有很多对象:

  • Controller:接收 HTTP 请求,调用 Service,返回响应;
  • Service:实现业务逻辑;
  • Repository:实现对数据库的增删改查。

此外还有数据库链接对象 DataSource,配置对象 Config 等。

这些对象都有各自依赖的关系:

Controller 依赖 Service 实现业务逻辑,Service 依赖 Repository 进行增删改查,Repository 依赖 DataSource 连接数据库,DataSource 从 Config 中读取数据库连接信息。

那么,代码实现起来就类似这样:

js
const config = new Config({ database: 'xxx', password: 'xxx' })

const dataSource = new DataSource(config)

const repository = new Repository(dataSouce)

const service = new Service(repository)

const controller = new Controller(service)

经过层层调用后,我们才可以使用 Controller 对象。而且,这些对象一般都是保持单例,只需 new 一个新的即可。在初始化对象时,我们需要理清对象之间的层级关系,创建一堆对象组合起来,保证保持单例,十分麻烦。

解决这个问题的方法是 IOC(Inverse Of Controll),即反转控制。

IOC 的实现思路是通过分析声明的依赖关系,根据先后顺序自动创建对象并且组装起来。

IOC 有一个放对象的容器,程序初始化的时候扫描 class 上声明的依赖关系,然后将这些 class 都新建一个实例放在容器里。创建对象的时候,还会把它们依赖的对象注入进去。这种方式叫 DI(Dependency Injection),依赖注入。

通过 IOC,我们能从主动创建依赖过渡到等待依赖注入。我们使用装饰器在 class 上声明依赖。

接下来我们看看对象创建的过程。如下代码所示,AppService 是一个 class 对象,装饰器 @Injectable 代表这个 class 是可注入的 Service。Nest 会把它放在 IOC 容器里。

ts
@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!'
  }
}

AppController 通过装饰器 @Controller 表示这个 class 是可被注入的 Controller,Nest 也会把它放在容器里。

ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

这两者的区别是,Service 通过 @Injectable 声明,可以被注入,也能注入到别的对象;而 Controller 通过 @Controller 声明,只能被注入。

将 Controller 和 Service 在 AppModule 中引入。@Module 装饰器负责声明模块。

ts
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

在入口模块里跑起来:

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  await app.listen(3000)
}

bootstrap()

与此同时,Nest 还支持模块机制。可以把不同业务的 Controller 和 Service 放在不同的模块中。当 import 模块后,模块中 export 的 provider 就可以在当前模块里注入了。

Released under the MIT License.