
1999 年Java 正带着“一次编写到处运行”的梦想席卷企业级开发领域。彼时的分布式应用、事务管理、安全控制等需求如同一座座大山压得开发者喘不过气。就在这时EJBEnterprise JavaBeans作为 Sun 公司力推的企业级开发规范横空出世试图用标准化的方式终结混乱。然而谁也没想到它为了实现“容器托管”而设计的强制继承机制会成为后来无数开发者的噩梦也为 Spring 的崛起埋下了伏笔。一、无奈的选择为什么必须继承 EJBObject/EJBHome在今天看来强制业务代码继承框架接口似乎是一种“反模式”但在 1999 年的 Java 技术栈里这几乎是唯一可行的方案。当时的 Java 还没有注解Annotation 直到 JDK 5.0 才出现动态代理Java Dynamic Proxy虽在 JDK 1.3 中初露端倪但远未普及早期 EJB 主要依赖 RMI远程方法调用的静态代理机制。容器要想管理 Bean 的生命周期、拦截远程调用、注入事务上下文就必须和业务代码建立一套“强制契约”——而EJBObject和EJBHome就是这套契约的载体。EJBObject定义了容器需要的生命周期钩子getEJBHome()获取工厂、remove()销毁对象、getHandle()远程句柄等EJBHome则负责对象的创建与销毁create()、remove()等方法是容器实例化 Bean 的唯一入口。在那个没有反射、AOP 还只是学术概念的年代强制继承就像一把“生硬的钥匙”只有通过它容器才能“打开”业务对象的大门完成托管。二、历史的阵痛强制继承带来的五大真实大坑强制继承的设计虽然解决了“容器托管”的燃眉之急却把业务代码和 EJB 规范牢牢焊死在了一起。每一个影响都是当年 Java 开发者踩过的血泪坑。1. 业务代码被“绑架”离开容器就成废代码这是最致命的问题。假设你写了一个UserServiceRemote接口public interface UserServiceRemote extends EJBObject { boolean login(String username, String password) throws RemoteException; }一旦业务需求变化——比如想把登录逻辑复用到普通 Java SE 桌面工具或者公司决定从 EJB 迁移到 Spring——你会发现普通 Java SE 项目里根本没有javax.ejb.EJBObject这个类接口直接报错迁移到 Spring 时所有接口必须重写成纯 POJOPlain Old Java Object实现类里的SessionContext、生命周期方法也得全部删掉工作量相当于代码重写一遍。当年很多公司从 EJB 迁移到 Spring光接口和实现类的重构就花了几个月——业务逻辑没变却因为规范绑定不得不推倒重来。2. 单元测试的噩梦测一个方法要等 10 分钟纯 POJO 的测试很简单直接new对象调用方法几毫秒就能跑完。但 EJB 2.x 的 Bean 不行——它必须由容器创建依赖容器注入的SessionContext自己new出来的对象根本跑不起来。当年测试一个login方法流程是这样的启动 JBoss/WebLogic 容器慢的话要等 5 分钟把 EJB 打包成ejb-jar部署到容器写客户端代码通过 JNDI 查找Home接口创建Remote接口调用login方法验证结果关闭容器清理部署。一次测试 5-10 分钟容器启动失败、JNDI 查找失败、端口冲突……这些和业务无关的问题都会导致测试失败。后果就是当年很多 EJB 项目的单元测试覆盖率不到 10%——不是开发者不想写是写测试太麻烦了。3. 代码冗余空方法堆成山新人接手就晕强制继承不仅要求接口继承EJBObject实现类还必须实现SessionBean接口里面有 5 个生命周期方法public class UserServiceBean implements SessionBean { private SessionContext ctx; // 不管用不用必须实现 public void setSessionContext(SessionContext ctx) { this.ctx ctx; } public void ejbCreate() {} // 空实现也得写 public void ejbRemove() {} // 空实现也得写 public void ejbActivate() {} // 无状态Bean根本不会调用也得写 public void ejbPassivate() {} // 无状态Bean根本不会调用也得写 // 真正的业务逻辑只有这一个 public boolean login(String username, String password) { return admin.equals(username) 123456.equals(password); } }代码里一堆空方法新人接手时根本分不清哪些是业务方法哪些是规范要求的“摆设”。维护时不敢删怕容器跑不起来只能留着代码越来越臃肿。4. 学习门槛拉满新手学 EJB 学到放弃纯 POJO 开发懂 Java 基础就行。但 EJB 2.x 开发你必须先学一堆和业务无关的规范记住EJBObject、EJBHome、SessionBean里的所有方法知道它们什么时候被调用理解RemoteException为什么必须抛不抛会怎么样学会写ejb-jar.xml配置文件搞懂每个标签的含义掌握 JNDI 查找会配置InitialContext。这些东西和“登录逻辑”“转账逻辑”没有任何关系但你必须全部掌握否则代码根本跑不起来。当年很多 Java 新手学 EJB 学到放弃——本来想写个简单的业务功能结果被一堆规范接口、配置文件搞晕了。5. 灵活性被扼杀Java 单继承的枷锁被锁死Java 是单继承的业务接口必须继承EJBObject就意味着你不能再继承其他自定义父接口了。比如你有一个通用的BaseService接口里面定义了所有业务服务都需要的log()方法public interface BaseService { void log(String message); }你想让UserServiceRemote继承BaseService复用log()方法——但不行因为它必须继承EJBObjectJava 不支持多继承。你只能把log()方法复制到每个业务接口里或者用更复杂的组合设计代码灵活性被大大限制。三、变革的曙光Spring 如何用纯 POJO 突围就在 EJB 2.x 把开发者折磨得苦不堪言时2003 年一个叫 Rod Johnson 的人带着《Expert One-on-One J2EE Design and Development》和 Spring 框架横空出世用“纯 POJO IoC AOP”彻底解决了“侵入性”问题。同样的登录逻辑在 Spring 里是这样的public interface BaseService { void log(String message); }可复用接口和实现类可以直接拿到任何 Java 项目里用不需要 Spring 容器易测试直接new UserServiceImpl()就能测试几毫秒跑完低门槛新手只需要懂 Java 基础不用学框架接口高灵活接口可以自由继承自定义父接口不受单继承限制。Spring 用 IoC控制反转通过反射和依赖注入管理对象生命周期用 AOP面向切面编程动态织入事务、安全等能力——不再需要强制契约接口业务代码终于从容器的枷锁中解放了出来。四、史鉴技术选择的时代局限与问题驱动创新回望 EJB 2.x 的历史我们不能简单地把它批判为“糟糕的设计”——它是 1999 年 Java 技术栈限制下的无奈但最优的选择。它试图用标准化解决企业级开发的混乱却因为技术局限付出了“侵入性”的代价。而 Spring 的崛起恰恰是技术史“问题驱动创新”的最佳例证它没有否定 EJB 的企业级需求而是用更先进的技术反射、动态代理、AOP解决了 EJB 的痛点用“非侵入性”重新定义了 Java 企业级开发。这段历史告诉我们技术选择永远受时代局限没有绝对的“对错”只有“是否适合当时的场景”。而那些曾经让开发者痛苦的问题恰恰是下一次技术变革的起点。后来EJB 3.0 也吸取了 Spring 的经验引入注解、支持纯 POJO完成了自我救赎——但 Spring 早已凭借“轻量级、非侵入性”的理念成为了 Java 企业级开发的事实标准。这段 EJB 与 Spring 的历史纠葛也成了 Java 技术史上最经典的“问题-创新”案例之一。