Skip to content

收益与代价

IoC 容器、运行时扫描和字节码增强不会让复杂度消失。它们会把重复组装逻辑集中起来,同时引入新的运行时规则。评估 Rain 时,应同时看到收益与代价。

先做一次复杂度搬运实验

假设原来有 100 行手工注册代码。引入 Rain 后,它们变成了 10 个注解和一行启动代码。能否得出“复杂度减少了 90%”?

不能。部分复杂度确实因为通用机制复用而消失了,另一部分则移动到了:

  • 扫描规则;
  • 容器创建规则;
  • 注解语义;
  • Loader 执行阶段;
  • 运行时错误处理;
  • 类加载与增强顺序。

框架的价值不是魔法般消灭复杂度,而是把重复复杂度集中管理,让业务代码只保留与业务相关的变化

停下来想一想:什么是“好的隐藏”,什么是“坏的隐藏”?

如果开发者能通过稳定规则预测框架行为,隐藏重复细节是好事。如果规则模糊、错误只能靠猜测和调试发现,隐藏就会变成负担。

主要收益

降低对象组装成本

业务组件只声明依赖,容器递归创建对象图。新增服务时不必持续扩大中央入口或维护手写注册表。

模块之间更容易解耦

模块可以依赖 EventBusJobManager 或自定义接口,而不是依赖具体实现。实现通过自动绑定、命名实例或集合注入接入。

扩展能力具有统一入口

@LoadByLoader 把“发现一批声明并建立基础设施”的流程抽象出来。事件、任务和其他模块可以共享同一发现机制。

Java 与 Kotlin 共享容器

Rain 使用 JVM 类型、javax.inject、Java 反射和 Kotlin 反射实现注入。Java 可以使用标准注解,Kotlin 还能利用主构造器、可空类型、默认参数与属性委托。

框架实现相对可读

DiContext -> ClassContext -> BeanCreator / BeanInjector 的主线较短。对框架开发、插件系统或需要深度定制运行时的团队来说,可以真正读懂并修改容器本身就是价值。

字节码增强突破代理限制

自定义类加载增强可以直接修改待定义类,不必永远通过代理对象访问。这对 final 类型、静态方法和某些特殊调用路径具有吸引力。

数据读取模型统一

Rain 通过 DataReader / DataReaderFactory 为普通对象、列表、映射和配置提供统一读取入口,有利于扩展新的注入数据来源。

主要代价

运行时错误多于编译期错误

缺失 Bean、名称不匹配、配置转换失败、构造器不可访问、循环依赖和类加载冲突,通常在启动或首次获取 Bean 时暴露。

类型系统能保证参数声明的类型,却不能保证运行时容器一定存在可用候选。

隐式行为增加理解成本

普通具体类只要处于扫描范围内,就可能被容器创建;一个注解可能通过 @LoadBy 间接触发 Loader;名称还可能通过配置再次解析。

这些能力减少代码量,却使阅读单个文件时不一定能看到完整行为。

扫描带来启动和边界成本

classpath 扫描需要枚举类并读取信息。扫描范围过大不仅影响启动,还会扩大容器可见类型和默认信任范围。

自定义类加载器提高调试难度

同名类由不同 ClassLoader 加载时,在 JVM 中属于不同类型。待增强类如果过早由父加载器加载,Transformer 可能无法生效。反射、测试、序列化和开发工具也可能受类加载边界影响。

字节码增强存在兼容风险

ASM 转换需要正确处理 JVM 字节码、栈帧、编译器生成结构以及不同 JDK / Kotlin 版本。增强后的异常堆栈和源码映射也可能更难理解。

容器容易退化成全局状态

如果所有代码都调用 DiContext.getBean(),容器就变成全局 Service Locator。这样虽然使用了 DI 框架,却失去了依赖显式、容易测试和模块隔离的主要好处。

生命周期协议较简单

复杂系统可能需要依赖感知的启动顺序、反向关闭、超时、健康检查、异步停止和失败回滚。Rain 当前的 ApplicationService 协议需要应用自行补充这些治理能力。

生态和现成集成有限

采用 Rain 意味着团队需要更主动维护数据库、网络、监控、配置中心、安全和部署集成。自主性提高的同时,工程责任也会提高。

什么时候不应使用容器

以下情况手工组装可能更清楚:

  • 程序只有少量对象且依赖稳定;
  • 一次性命令行工具启动后立即完成任务;
  • 数据对象和值对象只承载数据;
  • 高频创建的短生命周期对象适合普通构造器;
  • 算法内部对象完全属于实现细节;
  • 扫描、反射和类增强成本高于减少的样板代码。

可以用 Rain 管理应用级长期服务,同时在服务内部继续用构造器创建值对象、命令对象和临时计算对象。容器不是 new 的替代品,而是应用级对象关系的组装者。

判断标准

当某类对象满足多个条件时,更适合交给容器:

  • 生命周期接近应用生命周期;
  • 被多个模块共享;
  • 依赖其他应用服务;
  • 需要接口替换或多实现选择;
  • 需要被 Loader、事件、任务或 Controller 发现;
  • 需要配置、注入或框架增强。

反之,局部、短暂、纯数据或无外部依赖的对象通常保持普通创建方式更简单。

情景判断练习

尝试判断下列对象是否适合交给 Rain 管理:

对象建议原因
UserService通常适合长期存在、依赖其他服务、被多个入口调用
DatabasePool通常适合资源昂贵,需要统一启动和关闭
Order 数据对象通常不需要每次业务操作都会创建,生命周期短
Money 值对象不需要纯数据与计算,不依赖基础设施
EventListener适合需要扫描、创建和自动注册
一次循环中的临时 StringBuilder不适合完全属于局部实现细节

没有一个表格能替代设计判断,但可以使用三个问题快速检查:

  1. 它是否需要被应用中许多位置共享?
  2. 它是否需要框架帮助发现、注入或管理生命周期?
  3. 它的创建是否具有稳定、可复用的应用级规则?

多数回答为“否”时,普通构造器通常更合适。

基于 Apache License 2.0 发布