Spring 为何没有在 Java之外的地方存在?

📅 2026/8/25 2:20:31
Spring 为何没有在 Java之外的地方存在?
上次提到 Cordis时我觉得它现在的设计很像 Spring。所不同的是 Spring 管理的是 Java 对象Cordis 控制的是 plugin 都有一个 Runtime 容器的概念而且都可以按需集成相关模块。写完那篇文章后我突然好奇起来。“为什么其他的语言并没有 Spring 类似的容器来管理对象的声明周期”作为一个常年使用 Java 的开发我真心觉得这是一个伟大的设计。Ioc、DI、AOP这三项基础功能太强大了几乎是 Java 开发的必备技能。可为什么其他语言都是由开发者自己控制对象的生命周期似乎并没有类似对象容器的这种概念。用这种统一的容器框架之后后面的很多其他功能只需要围绕容器的生命周期来集成并提供集成包。那使用者在集成时就都能做到开箱即用即使对这个集成的功能知之甚少。最明显的例子就是 spring 的各种整合包。只要依赖一下相关的包再加上相关的启动配置基本就能用了比如 spring-jdbc、spring-logging。而其他语言似乎都需要开发者自己写代码来控制应用与要依赖的模块之间的集成。灵活性这是我第一时间能想到的解释。容器技术虽然好但是它太庞大不够灵活。有时候你可能只想写一段验证代码就像 Test 一样。如果你集成容器之后你会发现整个启动会变得特别慢。Python 和 Node.js 很多时候会有这样的场景。比如写一段算法逻辑来验证、写一个ETL脚本等等。可是 Go 似乎很少这样的场景但它怎么没有用容器呢历史包袱既然答案不在现在那我觉得肯定是因为历史原因才让这个容器技术只存在于 Java 中。我们来深入“案发现场”看看Spring诞生前基于EJB的J2EE开发到底有多“地狱”。我把当时的痛点拆解成五个具体的“灾难现场”1. 必须继承“重量级”父类代码极度“肮脏”你的业务类必须实现SessionBean或EntityBean接口并重写ejbCreate()、ejbPassivate()、ejbRemove()等一大堆生命周期方法。哪怕你只想写个“Hello World”也得带上几十行空实现。**业务逻辑被强行绑死在EJB容器API上**想单独跑个单元测试门都没有。2. 部署描述符是“XML噩梦”那时没有注解一个简单的无状态Bean需要在ejb-jar.xml里写大段配置包括JNDI名称、事务属性、安全角色等。更崩溃的是还要为每个字段写cmp-field映射。改一个字段名要改Java代码、改XML、改SQL漏一个就启动失败。3. “开发-部署-测试”循环慢到怀疑人生修改一行代码后不能直接重启Web容器而是需要**完整打包、重新生成桩代码、部署到WebLogic/WebSphere、等待服务器启动**通常要几分钟甚至十几分钟。程序员大量时间花在喝咖啡等服务器重启上一天能有效迭代的次数屈指可数。4. 单元测试几乎不可能必须依赖容器EJB必须运行在容器里你想测试业务逻辑**必须把整个应用部署到真实服务器上**通过客户端远程调用。调试时无法设断点单步跟踪只能靠打印日志、部署、看日志、再改、再部署……效率极低。5. 性能与设计上的“过度设计”-远程调用本地化即使Bean和应用在同一台机器、同一个JVM方法调用也必须走**RMI-IIOP**网络协议要经过序列化、网络传输、反序列化存在巨大的性能开销。-CMP容器管理持久化极其智障EJB的实体Bean映射机制非常原始经常生成低效SQL比如全表锁、N1次查询且无法精细控制缓存。总结一句那时的开发不是“写代码”而是“伺候容器”。Rod Johnson在书里直接用大量代码证明**80%的业务代码根本不需要这些重型基础设施**。正是为了终结这场“寒冬”Spring才带着IoC摆脱依赖查找、AOP解耦横切逻辑和轻量级模板直接写JDBC横空出世让程序员重新找回了“写普通Java类”的快乐。简单来讲就是当时 EJB 太臃肿Spring 是在收拾EJB的烂摊子。我心里又产生了一个疑问“为什么就一定要使用这类的容器技术呢其他语言没使用不也一样可以进行开发吗”分两层回答你**为什么要设计它初衷**以及**当时到底能不能不用它现实**。### 1. 为什么要设计EJB初衷很美好EJB的初衷是“标准化中间件”。它想把复杂的底层技术事务管理、对象池、远程调用打包成“容器”由服务器自动接管。开发者只需把业务代码塞进规定好的接口里容器就会在运行时“魔法般”地注入这些能力。**说白了EJB想当Java界的“水电煤”基础设施**让程序员只关心业务。### 2. 那当时不用不行吗现实极其残酷技术上完全可以用纯JDBC Servlet写但极其“自虐”。- 你要手动管理数据库连接、手写try-catch-finally处理事务提交/回滚、手动处理多线程安全问题。- 如果要分布式你得自己搭RMI远程方法调用处理各种网络异常。- 这些代码重复、脆弱且极度依赖程序员的个人水平大型项目几乎难以维护。行业生态上几乎“不得不”用。- 当时**EJB就是“J2EE企业级开发”的代名词**。Sun公司力推所有大厂IBM、Oracle的应用服务器WebSphere、WebLogic只认这个标准。- 甲方招标书会明确要求“基于J2EE标准即EJB”拿不到这个标签项目连入围资格都没有。- 市场上没有像样的替代方案Spring还没出生Hibernate也才刚萌芽开源社区力量远不如现在。转折点来了Rod JohnsonSpring之父在2002年的书里直接用**3万行纯JDBC 简单封装**的代码成功搭建了一个完整的交易系统性能远超当时的EJB。他硬生生证明了**“EJB带来的复杂度远远大于它解决的问题而且90%的项目根本不需要分布式”**换句话说当时分布式刚起步行业整体的技术水平还不够需要 EJB 来封装复杂的分布式调用、数据库的事务操作。后来 EJB 太臃肿Spring 解决了EJB 的臃肿问题。这里就很有意思了这些答案似乎在告诉我在 Java 中只有容器技术才能做好统一的封装AOP。仔细一想好像还真是可是为什么其他语言不需要呢这里的“静态语言”在计算机科学里通常指“静态类型语言”即**变量的类型必须在编译阶段就确定下来**不能运行时改变。为了让你彻底理解我把它和“动态语言”对比并结合Spring的痛点来解释1. 类型检查的时机核心区别-静态语言Java、Rust、Go写代码时必须明确类型如 String name 123。编译器会在运行前严格检查如果试图把 name 赋值为数字 123**代码根本编译不过去**。好处是极其严谨坏处是**缺乏灵活性**。-动态语言Python、JS变量只是个标签不绑定类型。你可以写 name 123下一秒改 name 456完全合法。这种灵活性让代码写起来很快但也容易在运行时因类型错误而崩溃。2. 这对Spring容器意味着什么正是因为Java的“静态”特性才逼出了Spring的大容器-Python/JS搞注入很简单因为它们是动态的我可以在运行时随意给对象加属性、改方法。想要事务直接用装饰器把原函数“换掉”就行不需要提前声明复杂的接口。-Java搞注入很吃力Java太死板了你不能随便改一个已编译好的类的行为。所以Spring不得不动用复杂的**反射Reflection**和**动态代理Proxy**在内存里“捏造”一个新的代理类来替你执行事务。这个过程消耗性能且报错极其晦涩。搞了半天原来 Spring 是替 EJB 收拾了烂摊子然后成为了行业事实标准。它们都是为了能在抽象层面统一地去解决复杂的分布式通讯、数据库操作而 Java 的静态语言特性不支持就只能搞容器。通过运行时的反射功能来给业务代码附加这些业务之外的功能。所以在使用 Java 开发的时候大家下意识的就会用。其他的静态语言在设计之初都避开了容器的设计~~