EJB第二版源码实战:从Ant编译到JNDI部署的避坑指南

发布时间:2026/10/14 18:12:53
EJB第二版源码实战:从Ant编译到JNDI部署的避坑指南 简介本资源为《精通EJB源代码 第二版》配套源码包面向具备Java与面向对象基础、希望深入掌握企业级JavaBeans开发的中高级开发者。内容围绕EJB 3.x展开涵盖实体Bean、会话Bean与消息驱动Bean的组件模型容器提供的生命周期、事务与安全管理JPA持久化映射与JPA QL查询RMI远程调用、并发事务控制以及Web服务集成等核心主题并配有真实案例的源代码分析与测试调试思路。压缩包为rar格式共279个文件约594KB其中79个java源文件构成主体实现33个xml与1个properties承载配置26个bat脚本用于编译与启动另有jsp、jar、class、policy、sql、ear等文件支撑部署与运行。已有132人学习下载适合对照书中章节逐模块研读源码理解EJB设计原则与实现技巧将理论转化为实际项目能力。1. 翻出这份 EJB 第二版源码包时我为什么先跑了一遍构建脚本前阵子帮一个做金融系统维护的朋友排查老项目的部署问题对方丢过来一个压缩包文件名写着“精通EJB源代码 第二版”。打开一看目录结构是典型的 Eclipse 老工程布局src下按包名分层ejbModule里塞着会话 Bean 和实体 Beanbuild.xml用的是 Ant 而不是 Maven。这套东西放在今天看确实有点年代感但如果你手上正好有基于 EJB 2.x 或 3.x 的老系统要维护、要迁移、要补测试这份源码的价值就出来了——它不是那种只给你看个 Hello World 的示例而是把会话 Bean、实体 Bean、消息驱动 Bean、事务注解、JNDI 查找这些核心机制都铺开写了。适合谁看一是接手了遗留 Java EE 项目、被Stateless和EJB绕晕的维护岗二是想搞明白 EJB 容器到底在背后替你做了什么、不想只停留在“会用注解”层面的后端开发三是做技术考古、需要一份能跑起来的参考实现来对照规范文档的人。这份源码包最大的好处是“能落地”——你能把它导进 IDE、能编译、能部署到兼容的容器里看日志而不是对着一堆理论干瞪眼。2. 把源码包跑起来环境选型与工程导入的取舍2.1 为什么我不建议直接上最新 JDKEJB 第二版源码的编译目标大概率是 Java 5 或 Java 6 级别里面可能用到javax.ejb包下的老 API也可能有sun.misc之类的内部调用。如果你直接拿 JDK 17 去编译第一个撞上的就是javax命名空间被jakarta取代的问题——EJB 3.1 之后规范迁移到了jakarta.ejb老源码里的 import 全部会报红。常见做法是准备一个 JDK 8 的运行环境配合一个支持 Java EE 7 或更早规范的容器。我一般会选 GlassFish 4.x 或者 JBoss AS 7这两个对 EJB 2.x/3.x 的兼容性比较稳部署时不会因为规范版本对不上直接拒绝加载。如果你只是想看代码逻辑、不打算真部署那用 JDK 8 加一个纯编译环境就够了至少能把.class文件生成出来验证语法。提示先确认源码根目录下有没有pom.xml。如果没有只有build.xml说明这是 Ant 工程别硬套 Maven 的目录约定。2.2 导入工程时先做一次“依赖体检”老工程的lib目录里通常躺着一堆 jar文件名可能是jboss-ejb-api.jar、jta.jar、jndi.jar这种。导入 IDE 后第一件事不是急着编译而是把这些 jar 逐个加到 Build Path 里然后看Problems视图还剩多少红叉。# 先看工程根目录结构确认构建脚本和依赖目录 ls -la # 典型输出里应该能看到 # build.xml src/ ejbModule/ lib/ META-INF/上面这条命令的作用是快速判断工程类型。如果build.xml存在且lib目录里有 jar那基本可以确定是 Ant 驱动的老式 Java EE 工程。参数上没什么好调的重点看lib里有没有缺失的 API 包——有时候源码包为了减小体积会把容器提供的 jar 剔掉你需要从目标容器的安装目录里把对应的client包补进来。2.3 用 Ant 编译并观察第一个报错!-- build.xml 里典型的编译目标重点看 srcdir 和 classpath -- target namecompile dependsinit javac srcdir${src.dir} destdir${build.dir} includeantruntimefalse debugtrue classpath fileset dir${lib.dir} includes**/*.jar/ /classpath /javac /target这段 Ant 脚本的逻辑很直白把src.dir下的 Java 文件编译到build.dirclasspath 来自lib.dir下的所有 jar。关键参数是includeantruntimefalse不加这个的话 Ant 会把自身的运行时塞进 classpath容易和容器 API 冲突。执行ant compile后如果报package javax.ejb does not exist说明lib里缺 EJB API 包如果报cannot find symbol: class EntityManager那是 JPA 的包没引全。按报错逐个补 jar 就行别一次性把容器所有 jar 都倒进去那样容易引发版本冲突。3. 会话 Bean 与实体 Bean 的代码拆解从注解到 JNDI 查找3.1 无状态会话 Bean 的典型写法与调用链源码里最常见的入口是Stateless标注的会话 Bean。它实现一个本地接口或远程接口容器负责池化实例、管理事务边界。下面这段是我从源码包里摘出来并做了简化的结构// 本地接口定义业务方法 public interface OrderServiceLocal { String createOrder(String userId, double amount); } // 无状态会话 Bean 实现 Stateless public class OrderServiceBean implements OrderServiceLocal { PersistenceContext(unitName orderPU) private EntityManager em; // 容器注入的实体管理器 Override public String createOrder(String userId, double amount) { Order order new Order(); order.setUserId(userId); order.setAmount(amount); em.persist(order); // 持久化到数据库 return ORDER- order.getId(); } }逻辑说明Stateless告诉容器这个 Bean 不需要维护会话状态可以放进实例池复用。PersistenceContext是 JPA 的注入点unitName对应persistence.xml里的持久化单元名。em.persist()把实体对象交给 EntityManager 管理事务提交时自动落库。参数上要注意unitName必须和配置文件里写的一致否则部署时直接报PersistenceUnit not found。调用方通常通过 JNDI 查找或者EJB注入来拿到这个 Bean。源码里两种方式都有示例JNDI 那种更适合跨模块调用// JNDI 查找方式适合不在容器管理范围内的客户端 Context ctx new InitialContext(); OrderServiceLocal service (OrderServiceLocal) ctx.lookup( java:comp/env/ejb/OrderServiceLocal); String result service.createOrder(U1001, 299.0);这里的java:comp/env/ejb/OrderServiceLocal是部署描述符里配置的引用名不是随便写的字符串。如果你在ejb-jar.xml里改了ejb-ref-name这里也要同步改否则NameNotFoundException就是你的下场。3.2 实体 Bean 的两种流派BMP 与 CMP 的取舍第二版源码里实体 Bean 部分同时覆盖了 Bean-Managed PersistenceBMP和 Container-Managed PersistenceCMP。BMP 需要你自己在ejbCreate、ejbLoad、ejbStore里写 JDBC 代码控制权大但代码量大CMP 则由容器生成 SQL你只需要在部署描述符里声明字段和关系。对比项BMPCMP持久化逻辑开发者手写 JDBC容器自动生成灵活性高可优化 SQL低受容器实现限制代码量大每个方法都要填小只声明抽象方法适用场景复杂查询、遗留数据库标准 CRUD、快速开发我一般建议先看 CMP 部分因为它的抽象层次更高能帮你理解 EJB 容器到底替你做了哪些事。BMP 部分则适合对照着看搞明白“如果不靠容器自己该怎么写”。源码里 CMP 的实体 Bean 通常是抽象类字段的 getter/setter 由容器在部署时生成子类实现这个机制在调试时容易让人懵——你打断点发现调用的是一个不存在的类别慌那是容器动态生成的。3.3 消息驱动 Bean 与事务注解的配合消息驱动 BeanMDB是 EJB 里比较有特色的东西它监听 JMS 队列或主题收到消息后触发onMessage方法。源码里有一个订单异步处理的例子MessageDriven(activationConfig { ActivationConfigProperty(propertyName destinationType, propertyValue javax.jms.Queue), ActivationConfigProperty(propertyName destination, propertyValue java:/jms/queue/OrderQueue) }) public class OrderMessageBean implements MessageListener { Resource private MessageDrivenContext context; Override TransactionAttribute(TransactionAttributeType.REQUIRED) public void onMessage(Message message) { try { // 解析消息并调用会话 Bean 处理业务 String userId message.getStringProperty(userId); // ... 业务逻辑 } catch (JMSException e) { context.setRollbackOnly(); // 异常时回滚事务 } } }activationConfig里的destination必须和 JMS 提供者里配置的队列名完全一致大小写敏感。TransactionAttribute(REQUIRED)表示如果调用方已经有事务就加入没有就新建一个。context.setRollbackOnly()是手动标记回滚的后悔药——消息处理失败时不让事务提交消息会重新投递。这里有个坑如果异常没被捕获容器默认也会回滚但消息可能被反复重投直到达到最大重试次数日志会被刷爆。4. 部署与调试把源码包扔进容器后要看什么4.1 打包结构与部署描述符的最小集EJB 工程的打包产物通常是 jar结构如下order-ejb.jar ├── META-INF/ │ ├── ejb-jar.xml # 部署描述符声明 Bean 和引用 │ ├── persistence.xml # JPA 持久化单元配置 │ └── MANIFEST.MF ├── com/example/ejb/ │ ├── OrderServiceBean.class │ └── OrderServiceLocal.class └── ...ejb-jar.xml在 EJB 3.x 里是可选的注解已经能表达大部分信息。但源码包里保留了完整的描述符这对理解“注解和 XML 的优先级”很有帮助——当两者冲突时XML 通常覆盖注解。persistence.xml里要配好jta-data-source指向容器里配置的 JDBC 数据源名字对不上就是部署时直接失败。4.2 部署后先查 JNDI 树别急着调业务# 以 GlassFish 为例用 asadmin 查看 JNDI 绑定 asadmin list-jndi-entries --context java:comp/env # 输出里应该能看到你的 EJB 引用名和队列名这条命令的作用是确认容器到底把你的 Bean 绑到了哪个 JNDI 名字上。不同容器、不同版本的默认绑定策略不一样有的会加__EJB31_Generated__前缀有的按接口全限定名绑定。先看清楚再写查找代码能省掉大量NameNotFoundException的排查时间。参数--context指定要查看的 JNDI 上下文不指定的话默认看根上下文东西太多反而不好找。4.3 日志里要盯住的几个关键词部署 EJB 时容器日志会刷很多行重点搜这几个Portable JNDI names后面跟的是你的 Bean 绑定名EJB Container初始化信息能看出容器识别到了几个会话 Bean、几个 MDB如果有WARNING: Cannot resolve reference之类的说明某个EJB注入点没找到目标通常是接口名写错或者目标 Bean 没被打进同一个包。把这些日志行截出来对照源码里的注解基本能定位到问题。5. 避坑与排查这份源码包最容易翻车的五个地方5.1 现象编译通过但部署报ClassNotFoundException原因源码包的lib里放了容器已经提供的 API jar打包时又把这些 jar 打进了 EJB jar 的lib目录容器加载时产生冲突。解决把javax.ejb、javax.persistence这类容器提供的包从打包路径里排除只保留第三方依赖。Ant 里可以用zipfileset的excludes属性控制。5.2 现象实体 Bean 的Id字段生成策略报错原因源码里可能用了GeneratedValue(strategy GenerationType.AUTO)但目标数据库不支持自增或者序列配置缺失。解决换成GenerationType.IDENTITY或SEQUENCE并在persistence.xml里补上对应的properties。别小看这个AUTO 在不同容器加不同数据库的组合下行为差异很大属于玄学范畴。5.3 现象MDB 收到消息后业务方法抛异常消息无限重投原因onMessage里异常没被正确处理容器默认认为消息处理失败并重新投递。解决在 catch 块里判断异常类型不可恢复的错误用context.setRollbackOnly()之外还要考虑配死信队列。源码里如果没写死信配置你需要自己在容器层面加。5.4 现象JNDI 查找在单元测试里能用部署后报NameNotFoundException原因单元测试时可能用的是本地上下文或者 mock和容器运行时的 JNDI 树不是一回事。解决以容器启动日志里打印的Portable JNDI names为准把查找字符串改成实际绑定名。血泪经验是别信源码注释里写的 JNDI 名以运行时为准。5.5 现象事务注解不生效数据没回滚原因TransactionAttribute加在了私有方法或者非业务接口方法上容器代理拦截不到。解决确保注解加在 public 的业务方法上且调用方是通过接口引用调用而不是new出来的实例。EJB 的事务管理依赖容器生成的代理绕过代理就等于绕过了事务。6. 进阶技巧用源码包里的模式改造自己的老项目这份源码包最大的价值不是让你照抄而是给你一套可对照的“标准答案”。我后来在维护一个老系统时就借用了里面 CMP 实体 Bean 的关系映射写法把一堆手写 JDBC 的 DAO 逐步替换成了 JPA 注解。具体做法是先挑一个最简单的实体照着源码里的OneToMany、ManyToOne把关系配好用persistence.xml里的hibernate.show_sql打开 SQL 日志对比生成的 SQL 和原来手写的 SQL 是否一致。一致就继续不一致就调注解参数。验证方法上我习惯写一个最小的集成测试用 Arquillian 或者容器自带的测试框架把会话 Bean 注入进来跑一遍增删改查看事务是否按预期提交和回滚。源码包里如果有测试类直接拿来改改就能用。另一个技巧是关注ejb-jar.xml里的assembly-descriptor部分那里定义了方法级的事务属性和安全角色很多老系统的权限控制就藏在这里比注解更隐蔽。从那以后我每次拿到一份老 EJB 源码都强制自己先跑一遍ant compile再部署到容器里看 JNDI 绑定最后才去读业务代码。这个顺序能帮你把环境问题和代码问题分开省掉大量来回折腾的时间。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询