java技术史001:EJB 侵入性的历史阵痛与 Spring 的突围

1999 年,Java 正带着"一次编写,到处运行"的梦想席卷企业级开发领域。彼时的分布式应用、事务管理、安全控制等需求如同一座座大山,压得开发者喘不过气。就在这时,EJB(Enterprise JavaBeans)作为 Sun 公司力推的企业级开发规范横空出世,试图用标准化的方式终结混乱。然而,谁也没想到,它为了实现"容器托管"而设计的强制继承机制,会成为后来无数开发者的噩梦,也为 Spring 的崛起埋下了伏笔。

一、无奈的选择:为什么必须继承 EJBObject/EJBHome?

在今天看来,强制业务代码继承框架接口似乎是一种"反模式",但在 1999 年的 Java 技术栈里,这几乎是唯一可行的方案

当时的 Java 还没有注解(Annotation 直到 JDK 5.0 才出现),动态代理(Java Dynamic Proxy)虽在 JDK 1.3 中初露端倪,但远未普及,早期 EJB 主要依赖 RMI(远程方法调用)的静态代理机制。容器要想管理 Bean 的生命周期、拦截远程调用、注入事务上下文,就必须和业务代码建立一套"强制契约"------而 EJBObjectEJBHome 就是这套契约的载体。

  • EJBObject 定义了容器需要的生命周期钩子:getEJBHome()(获取工厂)、remove()(销毁对象)、getHandle()(远程句柄)等;

  • EJBHome 则负责对象的创建与销毁:create()remove() 等方法是容器实例化 Bean 的唯一入口。

在那个没有反射、AOP 还只是学术概念的年代,强制继承就像一把"生硬的钥匙",只有通过它,容器才能"打开"业务对象的大门,完成托管。

二、历史的阵痛:强制继承带来的五大真实大坑

强制继承的设计虽然解决了"容器托管"的燃眉之急,却把业务代码和 EJB 规范牢牢焊死在了一起。每一个影响,都是当年 Java 开发者踩过的血泪坑。

1. 业务代码被"绑架":离开容器就成废代码

这是最致命的问题。假设你写了一个 UserServiceRemote 接口:

java 复制代码
public interface UserServiceRemote extends EJBObject {
    boolean login(String username, String password) throws RemoteException;
}

一旦业务需求变化------比如想把登录逻辑复用到普通 Java SE 桌面工具,或者公司决定从 EJB 迁移到 Spring------你会发现:

  • 普通 Java SE 项目里根本没有 javax.ejb.EJBObject 这个类,接口直接报错;

  • 迁移到 Spring 时,所有接口必须重写成纯 POJO(Plain Old Java Object),实现类里的 SessionContext、生命周期方法也得全部删掉,工作量相当于代码重写一遍。

当年很多公司从 EJB 迁移到 Spring,光接口和实现类的重构就花了几个月------业务逻辑没变,却因为规范绑定,不得不推倒重来。

2. 单元测试的噩梦:测一个方法要等 10 分钟

纯 POJO 的测试很简单:直接 new 对象,调用方法,几毫秒就能跑完。但 EJB 2.x 的 Bean 不行------它必须由容器创建,依赖容器注入的 SessionContext,自己 new 出来的对象根本跑不起来。

当年测试一个 login 方法,流程是这样的:

  1. 启动 JBoss/WebLogic 容器(慢的话要等 5 分钟);

  2. 把 EJB 打包成 ejb-jar,部署到容器;

  3. 写客户端代码,通过 JNDI 查找 Home 接口,创建 Remote 接口;

  4. 调用 login 方法验证结果;

  5. 关闭容器,清理部署。

一次测试 5-10 分钟,容器启动失败、JNDI 查找失败、端口冲突......这些和业务无关的问题都会导致测试失败。后果就是:当年很多 EJB 项目的单元测试覆盖率不到 10%------不是开发者不想写,是写测试太麻烦了。

3. 代码冗余:空方法堆成山,新人接手就晕

强制继承不仅要求接口继承 EJBObject,实现类还必须实现 SessionBean 接口,里面有 5 个生命周期方法:

java 复制代码
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 开发,你必须先学一堆和业务无关的规范:

  • 记住 EJBObjectEJBHomeSessionBean 里的所有方法,知道它们什么时候被调用;

  • 理解 RemoteException 为什么必须抛,不抛会怎么样;

  • 学会写 ejb-jar.xml 配置文件,搞懂每个标签的含义;

  • 掌握 JNDI 查找,会配置 InitialContext

这些东西和"登录逻辑""转账逻辑"没有任何关系,但你必须全部掌握,否则代码根本跑不起来。当年很多 Java 新手,学 EJB 学到放弃------本来想写个简单的业务功能,结果被一堆规范接口、配置文件搞晕了。

5. 灵活性被扼杀:Java 单继承的枷锁被锁死

Java 是单继承的,业务接口必须继承 EJBObject,就意味着你不能再继承其他自定义父接口了。

比如你有一个通用的 BaseService 接口,里面定义了所有业务服务都需要的 log() 方法:

复制代码

public interface BaseService { void log(String message); }

你想让 UserServiceRemote 继承 BaseService 复用 log() 方法------但不行,因为它必须继承 EJBObject,Java 不支持多继承。你只能把 log() 方法复制到每个业务接口里,或者用更复杂的组合设计,代码灵活性被大大限制。

三、变革的曙光:Spring 如何用纯 POJO 突围

就在 EJB 2.x 把开发者折磨得苦不堪言时,2003 年,一个叫 Rod Johnson 的人带着《Expert One-on-One J2EE Design and Development》和 Spring 框架横空出世,用"纯 POJO + IoC + AOP"彻底解决了"侵入性"问题。

同样的登录逻辑,在 Spring 里是这样的:

java 复制代码
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 技术史上最经典的"问题-创新"案例之一。

相关推荐
BUG创建者1 小时前
openlayers上跟据经纬度画出轨迹
开发语言·javascript·vue·html
23.1 小时前
【Java】NIO零拷贝技术揭秘:CPU不参与的数据传输
java·开发语言·nio
宸津-代码粉碎机1 小时前
SpringBoot 任务执行链路追踪实战:TraceID 透传全解析,实现从调度到执行的全链路可观测
开发语言·人工智能·spring boot·后端·python
茉莉玫瑰花茶2 小时前
CMake 工程指南 - 工程场景(5)
开发语言·c++·cmake
BUTCHER52 小时前
Netty Channel 生命周期
java·服务器·网络
Java爱好狂.2 小时前
2026如何备战互联网大厂Java面试?
java·分布式·高并发·java面试·后端开发·java架构师·互联网大厂
想做后端的前端2 小时前
Lua的元表和元方法
开发语言·junit·lua
大尚来也2 小时前
Spring Boot 3 + Spring Cloud 2026 微服务实战:云原生、AI 融合与架构演进
开发语言
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
EasyExcel中AnalysisEventListener<T>抽象类的方法执行顺序
java