依赖注入
YasuiJS 提供了一个完整的依赖注入系统,具有自动解析依赖关系和作用域管理功能。它实现了松耦合、更好的可测试性和更清晰的关注点分离。
概述
依赖注入自动管理组件之间的关系。YasuiJS 通过分析类构造函数和方法参数来自动创建和连接对象,而不是手动创建和连接对象。
import { Injectable, Controller } from 'yasui';
@Injectable()
export class UserService {
getUsers() {
return [{ id: 1, name: 'John' }];
}
}
@Controller('/users')
export class UserController {
// UserService 被自动创建和注入
constructor(private userService: UserService) {}
@Get('/')
getUsers() {
return this.userService.getUsers();
}
}可注入服务
Injectable 装饰器
@Injectable()- 将类标记为可注入(无参数,所有服务都需要)
使用 @Injectable() 装饰器将类标记为可注入。此装饰器对所有将被注入的服务来说都是必需的。
import { Injectable } from 'yasui';
@Injectable()
export class UserService {
getUser(id: string) {
// 业务逻辑
return { id, name: 'John Doe' };
}
}
@Injectable()
export class EmailService {
sendEmail(to: string, subject: string) {
// 邮件逻辑
console.log(`Sending email to ${to}: ${subject}`);
}
}构造函数注入
只需在控制器、中间件或服务构造函数中声明依赖项。您可以在同一个构造函数中注入多个服务。它们将被自动解析和注入:
@Injectable()
export class OrderService {
constructor(
private userService: UserService,
private emailService: EmailService,
private paymentService: PaymentService
) {}
processOrder(orderData: any) {
const user = this.userService.getUser(orderData.userId);
const payment = this.paymentService.processPayment(orderData.amount);
this.emailService.sendEmail(user.email, 'Order confirmed');
return { order: orderData, payment };
}
}依赖作用域
Scope 装饰器
@Scope(scope)- 指定依赖作用域(需要作用域参数)
YasuiJS 支持三种不同的依赖作用域,用于控制实例的创建和共享方式:
Scopes.SHARED(默认):在整个应用程序中共享的单例实例Scopes.LOCAL:为每个注入上下文创建新实例Scopes.DEEP_LOCAL:创建新实例,并将局部性传播到其自身的依赖项
@Scope() 装饰器应用于注入点,而不是服务类本身。
构造函数级作用域
您可以在构造函数中为单个依赖项指定作用域:
@Injectable()
export class MyService {
constructor(
@Scope(Scopes.LOCAL) private tempService: TempService,
@Scope(Scopes.DEEP_LOCAL) private isolatedService: IsolatedService,
private sharedService: SharedService // 默认为 SHARED
) {}
}作用域选择指南
- SHARED:用于无状态服务、缓存、数据库连接
- LOCAL:用于请求特定的服务、临时处理器
- DEEP_LOCAL:用于完全隔离的操作、测试场景
方法级注入
Inject 装饰器
@Inject(token?)- 将依赖项注入到方法参数中(可选的自定义令牌)
您可以直接将依赖项注入到控制器或中间件方法参数中。这将注入限制在特定端点而不是整个控制器,允许细粒度的作用域管理。例如,您可以在构造函数中注入共享服务,但特定路由需要同一服务的专用实例。
@Controller('/users')
export class UserController {
// 控制器的共享实例
constructor(private userService: UserService) {}
@Get('/:id')
getUser(
@Param('id') id: string,
@Inject() userService: UserService // 此端点的特定实例
) {
return userService.getUser(id);
}
}方法级作用域
作用域也适用于方法级注入:
@Controller('/api')
export class ApiController {
@Get('/data')
getData(
@Inject() @Scope(Scopes.LOCAL) tempService: TempService,
@Inject() @Scope(Scopes.SHARED) cacheService: CacheService
) {
return tempService.processData(cacheService.getData());
}
}自定义注入令牌
使用自定义令牌
对于复杂场景,使用带有 @Inject() 的自定义注入令牌。这对于注入原始值、配置或当您需要同一个类的多个实例时很有用:
@Injectable()
export class DatabaseService {
constructor(
@Inject('DATABASE_URL') private dbUrl: string,
@Inject('CONFIG') private config: AppConfig
) {
console.log(`Connecting to: ${this.dbUrl}`);
}
}
@Controller('/users')
export class UserController {
@Get('/')
getUsers(
@Inject('API_VERSION') apiVersion: string,
@Inject() userService: UserService
) {
return {
version: apiVersion,
users: userService.getAllUsers()
};
}
}注册自定义令牌
在应用程序配置中注册自定义令牌,使用 provide 提供直接值或使用 factory 提供计算值:
yasui.createServer({
controllers: [UserController],
injections: [
// 直接值
{ token: 'CONFIG', provide: 'value' },
// 异步工厂函数
{
token: 'DATABASE',
factory: async () => {
const db = new Database();
await db.connect();
return db;
}
}
]
});默认情况下,所有工厂注入都会在服务器启动前解析。
延迟异步注入
对于不应阻塞服务器启动且接受其暂时缺失或错误的依赖项,使用 deferred: true。服务器立即启动,同时依赖项在后台初始化。
工作原理:
- 服务器立即启动(非阻塞)
- 服务在工厂函数解析前为
null - 开发者处理 null 状态(返回 503、跳过操作、使用后备方案等)
- 初始化后,服务正常工作
- 如果初始化失败,服务保持
null
点击查看完整示例
import { Inject, HttpError } from 'yasui';
yasui.createServer({
controllers: [AnalyticsController],
injections: [{
token: 'ANALYTICS',
deferred: true,
factory: async () => {
try {
const analytics = new AnalyticsClient();
await analytics.connect();
return analytics;
} catch (err) {
// 做一些事情,比如发送警报。
throw err; // 服务将保持 null
}
},
}]
});
export class AnalyticsController {
// YasuiJS 会强制你使用 null 联合类型
constructor(
@Inject('ANALYTICS') private analytics: AnalyticsService | null
) { }
@Get('/events')
getEvents() {
// 处理未就绪/失败状态:
if (!this.analytics) {
throw new HttpError(503, 'Analytics not ready'); // 例如返回 503
// 或:return { events: [] }; // 后备值
// 或:this.logger.warn('Analytics unavailable'); // 跳过操作
// return ;
}
return this.analytics.track('page_view');
}
}重要的类型要求:
YasuiJS 验证延迟注入是否使用 | null 类型标注,仅适用于类类型。这是由于 TypeScript 反射元数据的限制。
✅ 有效(类类型):
如果缺少 | null,YasuiJS 启动时会引发装饰器验证错误:
@Inject('TOKEN') service: MyService | null // ✅ 已验证❌ 未验证(可以编译但不会检查):
@Inject('TOKEN') config: object | null // ⚠️ 未验证(字面类型)
@Inject('TOKEN') data: { foo: string } | null // ⚠️ 未验证(内联类型)循环依赖
YasuiJS 在启动时自动检测并防止循环依赖:
// 这将被检测并报告为错误
@Injectable()
export class ServiceA {
constructor(private serviceB: ServiceB) {}
}
@Injectable()
export class ServiceB {
constructor(private serviceA: ServiceA) {} // 循环依赖!
}