1.在Web应用中访问EJB组件
1.1 JavaEE系统结构简介
一个 JavaEE 应用由多种组件组合而成,这些组件安装在不同的机器上。
一个多层次的 JavaEE 应用结构如图1-1所示,它包含如下4个层次:
-
客户层:运行在客户机器上。客户层可以是普通的应用程序,直接访问业务层的EJB组件;也可以是浏览器程序,访问Web层的JSP和Servlet组件。
-
Web层:运行在JavaEE服务器上(应用服务器)。Web层的组件主要包括JSP和Servlet,用于动态生成HTML页面。Web层的组件会访问业务层的EJB组件。
-
业务层:运行在JavaEE服务器上。业务层的主要组件为EJB,它们负责实现业务逻辑。
-
Enterprise Infomation System(EIS)层:运行在数据库服务器上,用于存储业务数据。
图1-1 JavaEE 的多层次软件
1.2 安装和配置WildFly服务器
WildFly服务器的前身是JBoss,是一个免费的JavaEE服务器软件。WildFly服务器同时提供了Servlet容器和EJB容器,因此既能运行Java Web应用,又能运行EJB组件。
WildFly服务器的一个显著优点是需要比较小的内存和硬盘空间。安装和启动WildFly的步骤如下。
- WildFly是一个纯Java软件,它的运行需要JDK,因此在安装WildFly前应该先安装好JDK,并且在操作系统中加入加JAVA_HOME系统环境变量。
- WildFly的官方下载地址为:http://wildfly.org/downloads/。
- WildFly安装软件包是一个压缩文件,应该把这个压缩文件解压到本地磁盘(例如,把它解压到 C:/WildFly 目录),假定WildFly的根目录为<WILDFLY_HOME>。
- 接下来运行<WILDFLY_HOME>/bin/standalone.bat,这个命令启动WildFly服务器。WildFly服务器的Servlet容器默认情况下监听的 HTTP 端口为 8080。可以通过浏览器访问http://localhost:8080/。
WildFly的管理平台默认情况下监听9990端口,可以通过修改<WILDFLY_HOME>/standalone/configuration/standalone.xml配置文件中的端口配置代码:“${jboss.management.http.port:9990}”。
此外,此外,如果希望WildFly服务器的Servlet容器监听其他的HTTP服务器端口,只需修改以上配置文件中的“${jboss.http.port:8080}”端口号即可。
1.3 创建EJB组件
在本范例中,将创建一个遵循 EJB3 规范的无状态的会话Bean,名为 BookDBEJB。他将取代原来的 BookDB JavaBean ,负责操纵数据库。
一个 EJB 至少需要生成2个Java文件:Remote 接口和 Enterprise Bean类。
本例中 BookDBEJB 的2个Java 文件分别为:
- BookDBEJB.java:Remote 接口
- BookDBEJBImpl.java:Enterprise Bean 类
1.3.1 编写Remote接口
Remote接口中定义了客户可以调用的业务方法。这些业务方法在 Enterprise Bean 类中实现。以下是远程接口 BookDBEJB.java 的代码。@Remote标注用于声明 BookDBEJB 接口为远程接口:
package mypack;
import javax.ejb.Remote;
import java.util.*;
@Remote
public interface BookDBEJB{
public BookDetails getBookDetails(String bookId)throws Exception ;
public int getNumberOfBooks()throws Exception ;
public Collection getBooks()throws Exception ;
public void buyBooks(ShoppingCart cart)throws Exception ;
}
当客户程序访问 EJB 组件的业务方法时,这些方法的参数以及返回值都会在网络上传输,如图1-2所示。Oracle公司的 EJB 规范规定,如果在Remote接口中声明的方法的参数类型或返回类型为类,那么这个类必须实现 java.io.Serializable 接口。以上代码中,getBookDetails()方法返回类型为BookDetails 类,buyBooks()方法的参数类型为ShoppingCart 类。因此,必须修改BookDetails 和ShoppingCart 类的声明,确保他们都实现了Serializable 接口。此外,在一个ShoppingCart 对象中会包含多个ShoppingCartItem 对象。ShoppingCartItem 对象也会作为参数的一部分在网络上传输。因此,ShoppingCartItem 也必须实现Serializable 接口。
1.3.2 编写 Enterprise Java Bean类
本例中的Enterprise Java Bean 名为 BookDBEJBImpl,它实现了远程接口BookDBEJB中定义的业务方法。
package mypack;
import javax.ejb.Stateless;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;
import java.util.*;
@Stateless(name="bookdb")
public class BookDBEJBImpl implements BookDBEJB {
private String dbUrl = "jdbc:mysql://localhost:3306/BookDB?useUnicode=true&characterEncoding=GB2312&useSSL=false";
private String dbUser="dbuser";
private String dbPwd="1234";
private String driverName="com.mysql.jdbc.Driver";
public BookDBEJBImpl () {
try{
Class.forName(driverName);
}catch(Exception e){e.printStackTrace();}
}
public Connection getConnection()throws Exception{
return java.sql.DriverManager.getConnection(dbUrl,dbUser,dbPwd);
}
public void closeConnection(Connection con){
try{
if(con!=null) con.close();
}catch(Exception e){
e.printStackTrace();
}
}
public void closePrepStmt(PreparedStatement prepStmt){
try{
if(prepStmt!=null) prepStmt.close();
}catch(Exception e){
e.printStackTrace();
}
}
public void closeResultSet(ResultSet rs){
try{
if(rs!=null) rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
public int getNumberOfBooks() throws Exception {
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs=null;
int count=0;
try {
con=getConnection();
String selectStatement = "select count(*) " + "from BOOKS";
prepStmt = con.prepareStatement(selectStatement);
rs = prepStmt.executeQuery();
if (rs.next())
count = rs.getInt(1);
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
return count;
}
public Collection getBooks()throws Exception{
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs =null;
ArrayList<BookDetails> books = new ArrayList<BookDetails>();
try {
con=getConnection();
String selectStatement = "select * " + "from BOOKS";
prepStmt = con.prepareStatement(selectStatement);
rs = prepStmt.executeQuery();
while (rs.next()) {
BookDetails bd = new BookDetails(rs.getString(1), rs.getString(2), rs.getString(3),
rs.getFloat(4), rs.getInt(5), rs.getString(6),rs.getInt(7));
books.add(bd);
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
return books;
}
public BookDetails getBookDetails(String bookId) throws Exception {
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs =null;
try {
con=getConnection();
String selectStatement = "select * " + "from BOOKS where ID = ? ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, bookId);
rs = prepStmt.executeQuery();
if (rs.next()) {
BookDetails bd = new BookDetails(rs.getString(1), rs.getString(2), rs.getString(3),
rs.getFloat(4), rs.getInt(5), rs.getString(6),rs.getInt(7));
prepStmt.close();
return bd;
}
else {
return null;
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
}
public void buyBooks(ShoppingCart cart)throws Exception {
Connection con=null;
Collection items = cart.getItems();
Iterator i = items.iterator();
try {
con=getConnection();
con.setAutoCommit(false);
while (i.hasNext()) {
ShoppingCartItem sci = (ShoppingCartItem)i.next();
BookDetails bd = (BookDetails)sci.getItem();
String id = bd.getBookId();
int quantity = sci.getQuantity();
buyBook(id, quantity,con);
}
con.commit();
con.setAutoCommit(true);
} catch (Exception ex) {
con.rollback();
throw ex;
}finally{
closeConnection(con);
}
}
public void buyBook(String bookId, int quantity,Connection con) throws Exception {
PreparedStatement prepStmt=null;
ResultSet rs=null;
try{
String selectStatement = "select * " + "from BOOKS where ID = ? ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, bookId);
rs = prepStmt.executeQuery();
if (rs.next()) {
prepStmt.close();
String updateStatement =
"update BOOKS set SALE_AMOUNT = SALE_AMOUNT + ? where ID = ?";
prepStmt = con.prepareStatement(updateStatement);
prepStmt.setInt(1, quantity);
prepStmt.setString(2, bookId);
prepStmt.executeUpdate();
prepStmt.close();
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
}
}
}
1.4 在 Web 应用中访问 EJB 组件
在原来的 bookstore 应用的 common.jsp 中定义了如下 JavaBean:
<jsp:useBean id="bookDB" scope="application" class="mypack.BookDB"/>
现在BookDBEJB 替换原来的 BookDB JavaBean。BookDBEJB 组件运行在 EJB 容器中,是一种 JNDI 资源。而 common.jsp 运行在 Servlet 容器中。common.jsp 无法直接创建或引用BookDBEJB 组件。而应该通过javax.naming.InitialContext类的 lookup() 方法来查找位于 EJB 容器中的BookDBEJB JNDI 资源,获得该资源的引用:
<%@ page import="mypack.*" %>
<%@ page import="java.util.Properties" %>
<%@ page errorPage="errorpage.jsp" %>
<%@ page import="javax.naming.*" %>
<%!
private BookDBEJB bookDB;
public void jspInit() {
bookDB =
(BookDBEJB)getServletContext().getAttribute("bookDB");
if (bookDB == null) {
try {
Properties pro = new Properties();
pro.setProperty("java.naming.factory.initial","org.wildfly.naming.client.WildFlyInitialContextFactory");
pro.setProperty("java.naming.provider.url","http-remoting://localhost:8080");
pro.setProperty("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
Context context =new InitialContext(pro);
bookDB=(BookDBEJB) context.lookup("ejb:bookstore/bookstore/bookdb!mypack.BookDBEJB");
/**
以上代码先通过一个 Properties 对象来为 InitialContext 设置访问 WildFly 服务器的一些属性,接下来再调用 lookup()方法。lookup()方法中的参数是 BookDBEJB 的 JNDI 名字。
在编程时,如何检查BookDBEJB 的 JNDI 名字是否正确呢?当 bookstore 应用发布到 WildFly 服务器中时,WildFly 服务器会在 DOS 控制台显示 BookDBEJB 的 JNDI 名字信息。
**/
getServletContext().setAttribute("bookDB", bookDB);
/**
common.jsp 将 BookDBEJB 组件保存到 Web 应用范围内,当其他的JSP组件调用 BookDBEJB 的业务方法时,可以直接在 ServletContext中获取。
**/
} catch (Exception ex) {
System.out.println("Couldn't create database bean." + ex.getMessage());
}
}
}
public void jspDestroy() {
bookDB = null;
}
%>
common.jsp 将 BookDBEJB 组件保存到 Web 应用范围内,当其他的JSP组件调用 BookDBEJB 的业务方法时,可以直接在 ServletContext中获取。
<%
String bookId = request.getParameter("bookId");
if(bookId == null)bookId="201";
BookDetails book = bookDB.getBookDetails(bookId);
%>
对于原来的Bookstore Web应用。bookDB 代表的是 JavaBean 组件。它运行在Servlet 容器中,所以getBookDetails() 方法在 Servlet 容器执行。在本例中,bookDB 代表 BookDBEJB 组件,它运行在 EJB 容器中,所以 getBookDetails()方法在 EJB 容器中执行。
1.5 发布 JavaEE 应用
如果发布一个 Web 应用时,可以把它包为 WAR 文件;如果单独发布一个 EJB 组件,应该把它打包为 JAR 文件。对于一个完整的 JavaEE 应用,在发布时,应该把它打包为 EAR 文件。
1.5.1 在WildFly上发布 EJB 组件
-
EJB 组件相关文件
需要将 JavaEE API 的类库文件加入到 classpath 中。在<WILDFLY_HOME>/modules目录的子目录下有一个 jboss-ejb-api_X_spec-X.Final.jar 文件,它就是 JavaEE API 的类库文件。
-
给 EJB 组件打包
在DOS窗口中,转到组件根目录下,运行如下命令:
jar cvf bookdbejb.jar *
将在根目录下生成 bookdbejb.jar 文件。如果单独发布该 EJB 组件,只要把该 jar 文件复制到<WILDFLY_HOME>/standalone/deployments 下即可。
1.5.2 在WildFly上发布 Web 组件
-
给 Web 应用打包
在DOS窗口中,转到应用根目录下,运行如下命令:
jar cvf bookstore.war *
发布方式同上EJB组件。
1.5.3 在WildFly上发布 JavaEE 组件
一个 JAVAEE 应用由 EJB 组件、Web应用、存放第三方类库的 lib 目录以及发布描述文件构成。他的目录结构如图1-3所示。
-
application.xml文件
在这个文件中声明JavaEE应用所包含的Web 应用以及EJB组件。以下是application.xml文件的代码:
<?xml version="1.0" encoding="UTF-8"?> <application> <display-name>Bookstore JavaEE Application</display-name> <module> <web> <web-uri>bookstore.war</web-uri> <context-root>/bookstore</context-root> </web> </module> <module> <ejb>bookdbejb.jar</ejb> </module> <library-directory>lib</library-directory> </application>
以上代码指明在bookstore JavaEE 应用中包含一个 bookstore Web 应用,对应的 WAR 文件为 bookstore.war,它的URL路径为 /bookstore; 此外还声明了一个 EJB 组件,这个组件的JAR文件为bookdbejb.jar。
通过
<library-directory>
元素指定了类库文件所在的目录为"lib"。 -
给 JavaEE 应用打包
在DOS窗口中,转到 JavaEE 应用根目录下,运行如下命令:
jar cvf bookstore.ear *
发布方式同上EJB组件。