雖然 EJB 3 / Jakarta EE 8+ 已以註解簡化開發,但維護舊系統或學習企業級元件模型演進時,仍需理解 EJB 2.x 的「介面 + 部署描述檔 + Home/Remote 介面」經典架構。本文在 JBoss 4.x 環境下,以 Ant 建構、EAR 封裝、Servlet 測試的完整流程示範無狀態 Session Bean HelloEJB。
HelloEJB/
├── build.xml # Ant 建構腳本
└── src/
├── META-INF/
│ ├── application.xml # EAR 組裝描述
│ ├── ejb-jar.xml # EJB 標準部署描述
│ └── jboss.xml # JBoss 專屬 JNDI 綁定
├── powerwind/ # EJB 模組原始碼
│ ├── Hello.java # Remote 介面
│ ├── HelloHome.java # Home 介面
│ └── HelloBean.java # Bean 實作類
└── WEB-INF/ # Web 模組 (測試 Servlet)
├── web.xml
└── jboss-web.xml
Hello.java
package powerwind;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface Hello extends EJBObject {
String sayHello() throws RemoteException;
}
HelloHome.java
package powerwind;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface HelloHome extends EJBHome {
Hello create() throws RemoteException, CreateException;
}
HelloBean.java
package powerwind;
import javax.ejb.*;
public class HelloBean implements SessionBean {
private SessionContext ctx;
public void ejbCreate() throws CreateException, RemoteException {}
public void ejbActivate() throws RemoteException {}
public void ejbPassivate() throws RemoteException {}
public void ejbRemove() throws RemoteException {}
public void setSessionContext(SessionContext ctx) { this.ctx = ctx; }
public String sayHello() { return "Hello from ejb"; }
}
ClientServlet.java
package powerwind;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ClientServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
String msg = "ERROR";
try {
InitialContext ic = new InitialContext();
Object ref = ic.lookup("HelloEJB"); // jboss.xml 綁定的 JNDI 名
HelloHome home = (HelloHome) PortableRemoteObject.narrow(ref, HelloHome.class);
Hello hello = home.create();
msg = hello.sayHello();
} catch (Exception e) {
e.printStackTrace(out);
}
out.println(msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { doGet(req, resp); }
}
ejb-jar.xml (標準)
HelloEJB
HelloEJB
powerwind.HelloHome
powerwind.Hello
powerwind.HelloBean
Stateless
Container
jboss.xml (JBoss 專屬 JNDI 名稱)
HelloEJB
HelloEJB
application.xml (EAR 組裝)
HelloEJB J2EE Application
HelloEJB.war
/HelloEJB
HelloEJB.jar
web.xml (Servlet 註冊與 EJB 參考)
ClientServlet
powerwind.ClientServlet
ClientServlet
/ClientServlet
HelloEJB
Session
powerwind.HelloHome
powerwind.Hello
jboss-web.xml (Web 模組 EJB 參考綁定)
HelloEJB
HelloEJB
build.xml 關鍵目標
export JBOSS_HOME=/path/to/jboss-4.2.3.GAant deploy (依序編譯 → JAR → WAR → EAR → 複製至 $JBOSS_HOME/server/default/deploy)。$JBOSS_HOME/bin/run.sh -c default。https://localhost:8080/HelloEJB/ClientServlet,顯示 Hello from ejb 即成功。| 現象 | 原因 | 解決 |
|---|---|---|
| javax.naming.NameNotFoundException: HelloEJB not bound | JNDI 名稱不匹配 | 確認 jboss.xml jndi-name、jboss-web.xml、Servlet lookup 三者一致 |
| ClassCastException on narrow | Stub 類別未正確產生 | 確保 ejb-jar.xml home/remote 全限定名正確;重新部署清理暫存 |
| NoClassDefFoundError: javax/ejb/EJBObject | 編譯/執行時缺少 jboss-j2ee.jar | Ant classpath 包含 ${jboss.home}/client/jboss-j2ee.jar |
| Servlet 404 | context-root 或 url-pattern 錯誤 | 驗證 application.xml context-root + web.xml url-pattern |