proto 是 egg.js 中声明服务原型的元数据机制,非容器本身;它通过装饰器标记服务的生命周期、可见性及注入名,容器据此扫描类、维护实例池并按上下文解析注入。

Proto 是 Egg.js 中用于定义可注入服务原型的核心机制,它本身不是容器,而是容器识别和管理服务实例的“注册凭证”。要利用 Proto 与顶层原型(如 SingletonProto / ContextProto)的关联关系编写依赖注入容器,关键在于理解三者角色:Proto 是声明,顶层原型决定生命周期与作用域,容器负责按需解析、实例化并注入。
Proto 声明服务元信息,而非创建实例
Proto 装饰器(如 @SingletonProto 或 @ContextProto)本质是向框架“打标签”,告诉容器:
• 这个类是一个可被注入的服务原型;
• 它的生命周期策略是什么(单例 or 每次请求新建);
• 它是否对外可见(accessLevel);
• 它的注入标识名是什么(name,默认首字母小写)。
它不执行 new,也不管理依赖树,只是静态元数据注册点。
容器需基于 Proto 元信息构建解析逻辑
一个轻量级自研容器要支持 Proto 关联,至少需实现以下能力:
ThinkPHP 5.1.42 是 ThinkPHP 框架 5.1 系列中极具稳定性的成熟版本,专为 PHP 5.6+ 环境设计。它在 5.0 基础上全面重构,引入了依赖注入容器、Facade facade 支持、对象化路由系统及中间件机制(5.1.6+),并原生支持 Swoole/Workerman 常驻内存运行(5.1.18+)。该版本取消了系统常量,采用更优雅的类库别名机制,完美平衡了开发效
- 扫描所有带 Proto 装饰器的类(可通过 TypeScript 的装饰器元数据 API 或文件系统遍历 + AST 分析获取)
- 提取每个类的 name、accessLevel、生命周期类型(Singleton/Context)
- 维护两个实例池:全局单例池(对应 SingletonProto)、请求上下文绑定池(对应 ContextProto)
- 当 @Inject 解析时,先查 name 匹配项,再根据当前上下文判断取哪个池中的实例(如在 HTTP 请求中解析 ContextProto,则返回该请求专属实例)
顶层原型决定实例归属,容器必须区分对待
不能把 SingletonProto 和 ContextProto 当作同一种服务来缓存。容器内部应明确分层:
- SingletonProto 实例:首次请求时创建,存入全局 Map,后续所有请求共享同一实例
- ContextProto 实例:每次请求开始时(如 Egg 的 ctx.ready 阶段)触发创建,绑定到 ctx 对象上,请求结束自动销毁或复用
- @Inject 注入时,若目标是 ContextProto 类型,容器必须能感知当前是否存在有效 ctx,并从中取实例,而非从全局池读
手动注册与自动发现需统一抽象
Egg 默认通过目录约定(如 app/service)自动加载 Proto 类,但自研容器可更灵活:
- 支持显式 register({ token: 'userAdapter', useClass: UserAdapter, scope: 'singleton' })
- 也支持 scan({ include: ['app/**/*Service.ts'], scope: 'context' }) 自动匹配 Proto 类
- 无论哪种方式,最终都归一为“token → 构造函数 + 生命周期策略”的映射表
- 这样 @Inject('userAdapter') 才能跨手动注册与自动发现无缝工作










