服务提供者
本指南介绍 AdonisJS 应用中的服务提供者。你将学到如何:
- 使用生命周期钩子在应用启动和关闭期间的特定时刻执行代码
- 创建自定义服务提供者
- 向 IoC 容器注册 bindings
概述
服务提供者是带有生命周期钩子的 JavaScript 类,这些钩子在应用启动和关闭期间的特定时刻执行。这让你可以向 IoC 容器注册 bindings、使用 Macros 扩展框架类、在精确的时刻执行初始化,以及在优雅关闭期间清理资源。
其关键优势在于集中式的初始化逻辑,它在可预测的时刻运行,无需修改核心框架代码,也不必将设置代码分散在整个应用中。每个 AdonisJS 应用和包都使用服务提供者来挂入应用生命周期,因此理解它们对于理解框架至关重要。
理解服务提供者
在创建你自己的服务提供者之前,先了解它们在 AdonisJS 应用中的工作方式会很有帮助。
服务提供者在哪里注册
服务提供者在项目根目录的 adonisrc.ts 文件中注册。该文件定义了应加载哪些提供者,以及它们应在哪些运行时环境中执行。
import { defineConfig } from '@adonisjs/core/app'
export default defineConfig({
providers: [
() => import('@adonisjs/core/providers/app_provider'),
() => import('@adonisjs/core/providers/hash_provider'),
{
file: () => import('@adonisjs/core/providers/repl_provider'),
environment: ['repl', 'test'],
},
() => import('@adonisjs/core/providers/http_provider'),
],
})
提供者使用 () => import() 语法的惰性导入,确保它们仅在需要时才被加载。
内置服务提供者
一个典型的 AdonisJS 应用包含几个处理核心功能的框架提供者。
-
app_provider - 注册每个 AdonisJS 应用都需要的基础应用服务和辅助方法。
-
hash_provider - 注册用于密码哈希和验证的 hash 服务。
-
repl_provider - 添加 REPL 特定的 bindings。请注意它只在
repl和test环境中运行,展示了环境限制。 -
http_provider - 设置 HTTP 服务器及用于处理 Web 请求的相关服务。
当你安装诸如用于数据库访问的 @adonisjs/lucid 或用于身份验证的 @adonisjs/auth 等额外的包时,这些包包含它们自己的服务提供者,你需将其添加到此数组中。
执行顺序与环境
AdonisJS 按阶段跨所有已注册的提供者调用生命周期钩子。首先,register 钩子按提供者的注册顺序为所有提供者运行。然后 boot 钩子按注册顺序为所有提供者运行,接着是 start、ready,最后是 shutdown。
环境限制决定提供者是否会运行。例如,为 web 环境配置的 WebSocket 提供者在你运行控制台命令时不会执行。
这种执行顺序与环境过滤的组合,让你能够精确控制什么在何时运行。
何时创建服务提供者
当你需要向 IoC 容器注册服务、用 macros 扩展框架类、在特定生命周期时刻执行初始化、设置需要在关闭期间清理的资源,或在整个应用范围内配置第三方包时,请创建自定义服务提供者。
对于简单的工具函数、仅在单个位置运行一次的设置,或仅在单个控制器或中间件内使用的服务,你通常不需要服务提供者。在这些情况下,请使用普通模块或直接注入依赖。
创建自定义服务提供者
既然你已了解服务提供者何时适用,现在让我们构建一个将 Cache 服务注册到 IoC 容器的提供者。
-
生成提供者
AdonisJS 包含一个用于生成服务提供者文件的命令。
node ace make:provider cache# Output: # CREATE: providers/cache_provider.ts该命令会创建提供者文件,并自动在你的
adonisrc.ts文件中注册它。 -
理解生成的代码
打开生成的
providers/cache_provider.ts文件。你会看到一个基本的提供者结构。providers/cache_provider.tsimport type { ApplicationService } from '@adonisjs/core/types' export default class CacheProvider { constructor(protected app: ApplicationService) {} /** * Called when the provider is registered */ register() {} /** * Called when the application boots */ async boot() {} /** * Called when the application starts */ async start() {} /** * Called when the application is ready */ async ready() {} /** * Called during graceful shutdown */ async shutdown() {} }提供者通过其构造函数接收
ApplicationService,让你可以访问 IoC 容器和其他应用服务。所有生命周期方法都是可选的。你只需实现你所需要的钩子。 -
注册容器 binding
让我们使用
register方法将一个简单的 Cache 类注册到容器中。在本示例中,我们将在同一文件中创建一个最小化的 Cache 类,尽管在真实世界的包中,这个类通常会存放在别处。providers/cache_provider.tsimport type { ApplicationService } from '@adonisjs/core/types' /** * A simple Cache service. * In real-world packages, this would be in a separate * file like src/cache.ts */ export class Cache { get(key: string) { // Implementation would go here return null } set(key: string, value: any) { // Implementation would go here } } export default class CacheProvider { constructor(protected app: ApplicationService) {} register() { this.app.container.bind(Cache, () => { return new Cache() }) } } -
使用你已注册的服务
一旦注册,你就可以将 Cache 服务注入到控制器或其他由容器管理的类中。
app/controllers/posts_controller.tsimport { inject } from '@adonisjs/core' import type { HttpContext } from '@adonisjs/core/http' export default class PostsController { @inject() constructor(protected cache: Cache) {} async index({ response }: HttpContext) { const cachedPosts = this.cache.get('posts') if (cachedPosts) { return response.json(cachedPosts) } // Fetch from database and cache... return response.json([]) } }
理解所有生命周期钩子
服务提供者提供五个生命周期钩子,它们在应用生命周期的不同阶段运行。以下是每个钩子的执行时机:
| 钩子 | 类型 | 运行时机 | 常见用例 |
|---|---|---|---|
register | 同步 | 提供者被导入时立即运行 | 注册 IoC 容器 bindings |
boot | 异步 | 所有提供者注册完成后 | 扩展框架类、配置服务 |
start | 异步 | HTTP 服务器启动前 / 命令运行前 | 注册路由、预热缓存 |
ready | 异步 | HTTP 服务器就绪后 / 命令运行前 | 挂接到运行中的服务器(WebSockets) |
shutdown | 异步 | 优雅终止期间 | 关闭连接、清理资源 |
register 钩子
register 方法在 AdonisJS 导入你的提供者时立即被调用,处于引导流程的极早期,早于任何其他钩子运行。它的主要目的是向 IoC 容器注册 bindings。
import type { ApplicationService } from '@adonisjs/core/types'
export default class DatabaseProvider {
constructor(protected app: ApplicationService) {}
register() {
this.app.container.singleton('database', () => {
return new Database(this.app.config.get('database'))
})
}
}
register 钩子是同步的,且必须保持同步。不要试图解析 bindings、执行 I/O 操作,或访问可能尚未就绪的框架服务。
boot 钩子
boot 方法在所有提供者完成其 bindings 注册之后运行。此时,容器已被完全填充,你可以安全地解析任何 binding。这使得 boot 成为扩展框架类或配置依赖于其他已注册 bindings 的服务的自然场所。
import { HttpResponse } from '@adonisjs/core/http'
import type { ApplicationService } from '@adonisjs/core/types'
export default class ResponseExtensionProvider {
constructor(protected app: ApplicationService) {}
async boot() {
HttpResponse.macro('apiSuccess', function (data: any) {
return this.json({
success: true,
data,
})
})
}
}
使用 boot 来为验证器配置自定义规则、注册 Edge 模板辅助方法,或设置任何依赖于其他容器 bindings 可用性的服务。
start 钩子
start 方法在 HTTP 服务器启动前(web 环境中)或 Ace 命令执行前(console 环境中)运行。预加载文件在此钩子完成后被导入。
import router from '@adonisjs/core/services/router'
import type { ApplicationService } from '@adonisjs/core/types'
export default class RoutesProvider {
constructor(protected app: ApplicationService) {}
async start() {
/**
* Load routes from database or external configuration
*/
const dynamicRoutes = await this.loadRoutesFromDatabase()
dynamicRoutes.forEach((route) => {
router.get(route.path, route.handler)
})
}
private async loadRoutesFromDatabase() {
// Implementation would fetch routes from database
return []
}
}
使用 start 来注册路由、预热缓存,或对外部服务执行健康检查。
ready 钩子
ready 方法在 HTTP 服务器已开始接受连接之后(web 环境中)或在执行 Ace 命令的 run 方法之前(console 环境中)运行。这是你执行需要一个完全初始化的应用的设置的最后机会。
import type { ApplicationService } from '@adonisjs/core/types'
import { Server } from 'socket.io'
export default class WebSocketProvider {
constructor(protected app: ApplicationService) {}
async ready() {
if (this.app.getEnvironment() === 'web') {
const httpServer = await this.app.container.make('server')
const io = new Server(httpServer, {
cors: {
origin: '*',
},
})
this.app.container.singleton('websocket', () => io)
io.on('connection', (socket) => {
console.log('Client connected:', socket.id)
})
}
}
}
使用 ready 来集成必须挂接到运行中 HTTP 服务器的服务(如 WebSocket 服务器),或执行启动后任务(如发送应用已上线的通知)。
shutdown 钩子
shutdown 方法在 AdonisJS 收到优雅终止信号时运行。这是你清理资源、关闭连接,并确保应用在不丢失数据或不留下悬空进程的情况下关闭的机会。
import type { ApplicationService } from '@adonisjs/core/types'
export default class DatabaseProvider {
constructor(protected app: ApplicationService) {}
register() {
this.app.container.singleton('database', () => {
return new Database(this.app.config.get('database'))
})
}
async shutdown() {
const database = await this.app.container.make('database')
await database.closeAllConnections()
console.log('Database connections closed')
}
}
使用 shutdown 来关闭数据库连接池、刷写待写入的日志、断开与 Redis 的连接、关闭文件句柄,或执行优雅终止所需的任何其他清理。框架会等待所有 shutdown 钩子完成后才退出。