容器服务
本指南介绍 AdonisJS 中的容器服务(Container Services)。你将学习:
- 什么是容器服务以及它们是如何工作的
- 如何在你的应用中使用现有的服务
- 何时使用服务而非依赖注入
- 如何为包创建你自己的服务
概述
容器服务是 AdonisJS 中的一种便捷模式,它简化了你访问框架功能的方式。当你需要使用路由、哈希或日志等功能时,你可以导入一个即用型实例,而不必手动构造类或直接与 IoC 容器交互。
这种模式之所以存在,是因为许多框架组件需要 IoC 容器已经知道如何提供的依赖。AdonisJS 包会暴露预先配置好的实例作为标准的 ES 模块导出,而不是让你在每个文件中自己解析这些依赖。你可以像导入任何其他模块一样导入它们,它们会立即生效。
理解容器服务
如果没有容器服务,你有两种使用框架类的方式。你可以导入一个类并自己构造它,手动提供它的所有依赖。
import { Router } from '@adonisjs/core/http'
export const router = new Router(/** Router dependencies */)
或者,你可以使用 IoC 容器的 make 方法来构造类,让容器处理依赖解析。
import app from '@adonisjs/core/services/app'
import { Router } from '@adonisjs/core/http'
export const router = await app.make(Router)
容器服务通过完成与第二种方法完全相同的操作,但将其打包为一个便捷的导入,从而消除了这些繁琐环节。服务模块在内部使用 IoC 容器,并导出已解析的实例。
import router from '@adonisjs/core/services/router'
import hash from '@adonisjs/core/services/hash'
import logger from '@adonisjs/core/services/logger'
当你导入一个服务时,你获得的是一个由 IoC 容器构造、并正确注入了所有依赖的单例实例。服务本身只是一个让这个实例作为标准模块导出可用的薄包装器。
使用容器服务
当你安装 AdonisJS 包时,容器服务会自动可用。无需配置或注册。你只需导入该服务并使用它。
以下是使用 Drive 服务将文件上传到 S3 的示例。
import drive from '@adonisjs/drive/services/main'
export class PostsController {
async store(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
/**
* drive 服务让你直接访问
* DriveManager 实例。用它来选择一个磁盘
* 并执行文件操作。
*/
const disk = drive.use('s3')
await disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}
这种方式简单直接,除了导入服务之外无需任何设置。Drive 服务是一个单例,因此相同的实例会在你的整个应用中共享。
改用依赖注入
对于偏好依赖注入的应用,你可以将底层类直接注入到你的服务或控制器中。这种方式使你的代码更具可测试性,因为依赖可以轻松地被模拟或打桩。
以下是使用构造函数注入的相同文件上传功能。
import { Disk } from '@adonisjs/drive'
import { inject } from '@adonisjs/core'
@inject()
export class PostService {
/**
* Disk 实例由 IoC 容器注入。
* 这使得在测试期间交换实现
* 或使用不同的磁盘配置变得容易。
*/
constructor(protected disk: Disk) {
}
async save(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
await this.disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}
使用依赖注入时,IoC 容器会自动解析并注入 Disk 实例。你的类声明它需要什么,容器就提供什么。当你需要编写与框架细节保持解耦的业务逻辑时,这种模式尤其有价值。
可用的服务
AdonisJS 核心和官方包暴露了以下容器服务。每个服务都对应于一个容器绑定,并提供对完全构造的类实例的访问。
| Binding | Class | Service |
|---|---|---|
app
|
Application |
@adonisjs/core/services/app
|
ace
|
Kernel |
@adonisjs/core/services/kernel
|
config
|
Config |
@adonisjs/core/services/config
|
encryption
|
Encryption |
@adonisjs/core/services/encryption
|
emitter
|
Emitter |
@adonisjs/core/services/emitter
|
hash
|
HashManager |
@adonisjs/core/services/hash
|
logger
|
LoggerManager |
@adonisjs/core/services/logger
|
repl
|
Repl |
@adonisjs/core/services/repl
|
router
|
Router |
@adonisjs/core/services/router
|
server
|
Server |
@adonisjs/core/services/server
|
testUtils
|
TestUtils |
@adonisjs/core/services/test_utils
|
创建你自己的服务
如果你正在构建一个包,或者想要将你自己的容器绑定作为服务暴露出来,你可以遵循 AdonisJS 内部使用的相同模式。容器服务只不过是一个从容器中解析绑定并将其导出的模块。
你可以在 GitHub 上查看完整实现 ,了解 Drive 包是如何创建其服务的。
import app from '@adonisjs/core/services/app'
let drive: DriveManager
await app.booted(async () => {
drive = await app.container.make('drive')
})
export { drive as default }
该服务会等待应用启动,然后从容器中解析绑定并将其导出。这确保了所有服务提供者都已注册了它们的绑定,之后服务才会尝试解析它们。