雖 EJB 3.0 已大幅簡化開發模型,但理解 EJB 2.x 架構對維護舊系統、掌握 EJB 演進歷史仍有價值。本文記錄在 JBoss 4.x 上從零構建首個 Stateless Session Bean 完整流程。
E:.
│ build.xml
│
└─src
├─META-INF
│ application.xml
│ ejb-jar.xml
│ jboss.xml
│
├─powerwind
│ ClientServlet.java
│ Hello.java
│ HelloBean.java
│ HelloHome.java
│
└─WEB-INF
jboss-web.xml
web.xml
Hello.javapackage powerwind;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface Hello extends EJBObject {
public String sayHello() throws RemoteException;
}
HelloHome.javapackage powerwind;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface HelloHome extends EJBHome {
public Hello create() throws RemoteException, CreateException;
}
HelloBean.javapackage powerwind;
import java.rmi.RemoteException;
import javax.ejb.*;
public class HelloBean implements SessionBean {
private SessionContext context;
public void ejbCreate() throws EJBException, RemoteException {}
public void ejbActivate() throws EJBException, RemoteException {}
public void ejbPassivate() throws EJBException, RemoteException {}
public void ejbRemove() throws EJBException, RemoteException {}
public void setSessionContext(SessionContext newContext) throws EJBException {
context = newContext;
}
public String sayHello() throws EJBException {
return "Hello from ejb";
}
}
ClientServlet.javapackage powerwind;
import java.io.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import javax.servlet.*;
import javax.servlet.http.*;
public class ClientServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String message = null;
try {
InitialContext ic = new InitialContext();
Object obj = ic.lookup("HelloEJB");
HelloHome home = (HelloHome) PortableRemoteObject.narrow(obj, HelloHome.class);
Hello hello = home.create();
message = hello.sayHello();
} catch (Exception e) {
e.printStackTrace();
}
out.println(message);
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
ejb-jar.xml (EJB 標準描述檔)<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>HelloEJB</ejb-name>
<home>powerwind.HelloHome</home>
<remote>powerwind.Hello</remote>
<ejb-class>powerwind.HelloBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
jboss.xml (JBoss 專用綁定)<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
<session>
<ejb-name>HelloEJB</ejb-name>
<jndi-name>HelloEJB</jndi-name> <!-- 客戶端 lookup 使用此名稱 -->
</session>
</enterprise-beans>
</jboss>
application.xml (EAR 封裝描述檔)<?xml version="1.0" encoding="UTF-8"?>
<application>
<display-name>HelloEJBApp</display-name>
<module>
<ejb>hello-ejb.jar</ejb>
</module>
<module>
<web>hello-web.war</web>
</module>
</application>
web.xml<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>ClientServlet</servlet-name>
<servlet-class>powerwind.ClientServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ClientServlet</servlet-name>
<url-pattern>/client</url-pattern>
</servlet-mapping>
</web-app>
jboss-web.xml<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<context-root>hello</context-root>
</jboss-web>
build.xml 關鍵片段<property name="jboss.home" value="D:/jboss-4.0.5"/>
<property name="dist.dir" value="dist"/>
<target name="package-ear" depends="compile,war">
<ear destfile="${dist.dir}/HelloEJB.ear" appxml="src/META-INF/application.xml">
<fileset dir="${dist.dir}" includes="hello-ejb.jar,hello-web.war"/>
</ear>
</target>
<target name="deploy" depends="package-ear">
<copy file="${dist.dir}/HelloEJB.ear"
todir="${jboss.home}/server/default/deploy" overwrite="true"/>
</target>
run.bat -c default (或 all 配置)ant deploy,EAR 複製至 server/default/deployHelloEJB 綁定至 JNDI HelloEJBhttps://localhost:8080/hello/client| 概念 | 說明 | 本例體現 |
|---|---|---|
| Home 介面 | 生命週期管理 (create/find/remove) | HelloHome.create() |
| Remote 介面 | 業務方法定義,RMI-IIOP 分散式語義 | Hello.sayHello() |
| Bean 類 | 實作 SessionBean/EntityBean 介面,容器回調方法 | ejbCreate/Activate/Passivate/Remove |
| PortableRemoteObject.narrow | RMI-IIOP 類型安全轉換,跨 JVM 必須 | Servlet 中 lookup 後 narrow |
| JNDI 名稱 | jboss.xml 指定綁定名,客戶端 lookup 關鍵 | HelloEJB |
| 容器管理事務 (CMT) | assembly-descriptor 宣告事務屬性 | Required (預設) |
NameNotFoundException: HelloEJB not bound → 檢查 jboss.xml jndi-name 與 Servlet lookup 名稱一致ClassCastException on narrow → 確認 ejb-jar.xml home/remote 介面全限定名正確EJB not found in JNDI → EAR 結構錯誤,ejb-jar.xml 應在 EJB JAR 的 META-INF 目錄TransactionRolledbackException → 業務方法拋出 RuntimeException 導致容器回滾EJB 2.x 雖需撰寫 Home/Remote 介面、Bean 類、多個 XML 描述檔、處理 RMI-IIOP 窄化轉換,但其嚴格的元件模型、容器管理服務 (事務、安全、並發、持久化) 為企業級分散式應用提供了強有力的結構保證。本範例展示了最小化的 Stateless Session Bean 完整生命週期。對於維護存量 EJB 2.x 系統的開發者,熟練掌握此部署流程與除錯技巧仍是核心基本功。