7. 第 3 版:将 PAM 应用程序移植到 Glassfish 应用服务器
我们计划将OpenEJB架构中的[metier]和[DAO]层,以及 EclipseLink 架构中的 [metier] 和 [DAO] 层,部署到 Glassfish 应用服务器的容器中。
当前的实现使用 OpenEJB / EclipseLink
![]() |
如上所述,[ui] 层使用 [metier] 层的远程接口。
我们测试了两个运行环境:local 和 distant。 在后一种模式下,[ui] 层是 [metier] 层的客户端,而 [metier] 层由 EJB 层实现。 为了在客户端/服务器模式下运行(即客户端和服务器分别在两个不同的 JVM 中运行),我们将把 [metier, DAO, jpa] 层部署到 Java Glassfish 服务器 EE 上。该服务器随 NetBeans 一起提供。
需在 Glassfish 服务器上构建的实现
![]() |
- [ui] 层将在 Java SE(标准版)环境中运行
- [metier, DAO, JPA] 层将在 Glassfish v3 服务器上的 Java EE(企业版)环境中运行
- 客户端将通过 TCP/IP 网络与服务器进行通信。 网络通信对开发人员而言是透明的,但开发人员仍需意识到,客户端和服务器之间通过交换序列化对象(而非对象引用)来实现通信。用于此类通信的网络协议称为 RMI(远程方法调用),该协议仅适用于两个 Java 应用程序之间。
- Glassfish 服务器上使用的 JPA 实现将命名为 EclipseLink。
7.1. PAM 客户端/服务器应用程序的服务器端
7.1.1. 应用程序架构
本文将重点探讨由 Glassfish 服务器上的 EJB3 容器托管的服务器端部分:
![]() |
我们的目标是将已在 OpenEJB 容器中完成并经过测试的内容移植到 Glassfish 服务器上。 这正是 OpenEJB 以及通常所说的 EJB 嵌入式容器的优势所在:它们使我们能够在简化的运行环境中测试应用程序。当应用程序测试完成后,只需将其移植到目标服务器上,即本例中的 Glassfish 服务器。
7.1.1.1. NetBeans 项目
首先创建一个新的 NetBeans 项目:
![]() |
- 在 [1] 中,新建项目
- 在 [2] 中,选择类别 Maven,并在 [3] 中选择类型 EJB 模块。 实际上,这是要构建一个将由 EJB 容器(即 Glassfish 服务器)托管并运行的项目。
![]() |
- 使用按钮 [4a],选择项目文件夹的父文件夹,或直接在 [4b] 中输入其名称。
- 在 [5] 中,为项目命名
- 在 [6] 中,选择将要运行该项目的应用服务器。此处选择的是 [Runtime / Servers] 选项卡中可见的服务器之一,即 Glassfish v3。
- 在 [7] 中,选择 Java 版本 EE。
![]() |
- 在 [1] 中,新建项目。它与传统 Java 项目有以下几点不同:
- 会自动创建一个分支 [Other Sources] [2]。该分支将包含配置 JPA 层的文件 [persistence.xml],
- 如果构建该项目(Build),会看到出现 [3] 以及一个依赖项 [javaee-api-6.0]。 该依赖的类型为 provided,因为它由 Glassfish 的 EJB 容器在运行时提供。
7.1.1.2. 持久层配置
所谓持久层配置,是指编写 [persistence.xml] 文件,该文件定义:
- 要使用的 JPA 实现
- JPA 层所使用的数据源定义。该数据源将由 Glassfish 服务器管理的 JDBC 数据源提供。
![]() |
操作步骤如下。首先,在 [Runtime / Databases] 选项卡中,创建 [1] 连接至 MySQL5 / dbpam_eclipselink 数据库:
![]() |
完成上述操作后,即可开始创建由模块 EJB 使用的资源 JDBC:
![]() |
- 在 [1] 中,创建一个新文件——在执行此操作前,请确保已选中项目 EJB
- 在 [2] 中,将项目 EJB
- 在 [3] 中,选择类别 [Glassfish]
- 在 [4] 中,需要创建资源 JDBC
![]() |
- 在 [5] 中,指定资源 JDBC 将使用一个新的连接池。需要说明的是,连接池是一组已建立的连接,用于加速应用程序与数据库之间的数据交换。
- 在 [6] 中,为已创建的资源 JDBC 指定名称 JNDI。该名称可以是任意名称,但通常采用 jdbc/nom 的形式。 该名称 JNDI 将用于文件 [persistence.xml] 中,以指定实现 JPA 必须使用的数据源。
- 在 [7] 中,为即将创建的连接池指定任意名称
- 在 [8] 下拉列表中,选择之前基于 MySQL / dbpam_eclipselink 创建的 JDBC 连接。
- 在 [9] 中,显示连接池属性的摘要——无需进行任何修改
![]() |
- 生成 [10],可在此指定连接池的若干属性——保持默认值
- 在 [11] 中,完成为模块 EJB 创建资源 JDBC 的向导后, 在分支 [Other Sources] 中创建了一个名为 [glassfish-resources.xml] 的文件。该文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-resource enabled="true" jndi-name="jdbc/dbpam_eclipselink" object-type="user" pool-name="dbpamEclipselinkConnectionPool">
<description/>
</jdbc-resource>
<jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="dbpamEclipselinkConnectionPool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
<property name="URL" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
<property name="User" value="root"/>
<property name="Password" value=""/>
</jdbc-connection-pool>
</resources>
文件 [glassfish-resources.xml] 是一个 XML 文件,其中包含向导收集的所有数据。 NetBeans将在将模块EJB部署到GlassFish服务器时使用该文件,以请求创建该模块所需的资源JDBC。
现在可以创建文件 [persistence.xml],该文件将配置模块 EJB 的 JPA 层:
![]() |
- 在 [1] 中,创建一个新文件——执行此操作前,请确保已选中项目 EJB
- 在 [2] 中,项目 EJB
- 在 [3] 中,选择类别 [Persistence]
- 在 [4] 中,需要创建一个持久化单元
![]() |
- ,将其命名为
- 在 [6] 中,提供了多个实现 JPA。此处选择 [EclipseLink]。其他实现也可使用,但需将实现它们的库与 Glassfish 服务器的库一同放置。
- 在 [7] 下拉列表中,选择刚刚创建的数据源 JDBC [jdbc/dbpam_eclipselink]。
- 在 [8] 中,指定事务由容器 EJB 管理
- 在 [9] 中,指定在将模块 EJB 部署到服务器时,不应对数据源执行任何操作。 实际上,EJB 模块将使用已创建的 [dbpam_eclipselink] 数据库。
- 向导结束时,已创建文件 [persistence.xml] [10]。其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-ejb-metier-dao-eclipselinkPU" transaction-type="JTA">
<jta-data-source>jdbc/dbpam_eclipselink</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>
</persistence-unit>
</persistence>
- 第 3 行:持久化单元名称 [mv-pam-ejb-metier-dao-eclipselinkPU] 及事务类型(JTA 对应容器 EJB)
- 第 5 行:持久层使用的数据源名称 JNDI:jdbc/dbpam_eclipselink
- 第 6 行:实体 JPA 未指定。它们将在模块 EJB 的类路径中查找。
- 未指定所使用的实现名称(如 Hibernate、EclipseLink 等)。在此情况下,Glassfish v3 将默认使用 EclipseLink。
7.1.1.3. 插入 [jpa, DAO, metier] 层
现在 [persistence.xml] 文件已定义,我们可以继续将企业应用程序 [pam] 的 [metier, dao, jpa] 层插入到项目中:
![]() |
这三个图层与 OpenEJB 中的完全相同。可以在两个项目之间进行简单的复制/粘贴操作。现在我们就来执行此操作:
![]() |
- 在 [1] 中,这是将 [mv-pam-openejb-eclipselink] 项目中的 [jpa, dao, metier, exception] 包复制到 EJB 模块中的结果[mv-pam-ejb-metier-dao-jpa-eclipselink]
7.1.1.4. Glassfish 服务器配置
接下来我们需要对 Glassfish 服务器进行两项配置:
- JPA层由EclipseLink实现。我们需要确保Glassfish服务器已安装该实现(JPA)所需的库。
- 数据源为 MySQL 数据库。必须确保 Glassfish 服务器已安装该 SGBD 数据库对应的 JDBC 驱动程序。
在部署 EJB 模块时,可能会发现缺少这些库。以下是向 Glassfish 服务器添加缺失库的多种方法之一:
![]() |
- 在 [1] 中,查看 Glassfish 服务器的属性
- 在 [2] 中,记下服务器的域文件夹。我们将其记为 <domains>
- 在 <domains>\domain1\lib 文件夹中放入缺失的库。在本例中,已添加了 Hibernate 库(lib / hibernate-tools)以及 MySQL 的 JDBC 驱动程序(lib / divers)。 默认情况下,Glassfish 服务器已包含 EclipseLink 的库。因此,我们只需添加 MySQL 的驱动程序 JDBC。
![]() |
- 在 [1] 中,于 [Services] 选项卡中,我们启动 Glassfish v3 服务器
- 在 [2] 中,它已处于活动状态
7.1.1.5. 部署模块 EJB
现在,我们将模块 EJB 部署到 Glassfish 服务器上:
![]() |
- 在 [1] 中,EJB 模块已部署
- 在 [2] 中,Glassfish 服务器的目录结构已刷新
- 在 [3],部署完成后,模块 EJB 出现在 Glassfish 服务器的 [Applications] 分支中
- 在 [4] 中,资源 JDBC [jdbc / dbpam_eclipselink] 已在 Glassfish 服务器上创建。需要提醒的是,我们在第 7.1.1.2 节中已对其进行了定义。
部署过程中,Glassfish 服务器会在控制台记录一些有用的信息:
请注意第
- 3、6、8 和 11 行中,可以看到已部署的 JNDI 和 EJB 的可移植名称。Java EE 6 引入了可移植名称的概念。 这表示该名称被所有 Java 6 服务器所识别。而在 Java 5 中,名称是特定于所用服务器的。
- 4、7、9、12:在 Glassfish v3 特定形式下部署的 EJB 的名称。
这些名称将有助于我们编写控制台应用程序,以便使用已部署的 EJB 模块。
7.2. 控制台客户端 - 版本 1
现在我们已经部署了客户端/服务器应用程序的服务器端,接下来我们将研究客户端部分 [1]:
![]() |
7.2.1. 客户端项目
我们创建一个名为 [mv-pam-client-ejb-metier-dao-eclipselink] 的新 Maven 项目,类型为 [Java Application]:
![]() |
- 在 [1] 中,即客户项目
在文件 [pom.xml] 中,我们添加了以下依赖项:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-client-ejb-metier-dao-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-client-ejb-metier-dao-eclipselink</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.appclient</groupId>
<artifactId>gf-client</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- 第31-35行:对库[gf-client]的依赖,该库使Classfish客户端能够与远程服务器通信,
- 第 36-41 行:对 EJB 模块的 Maven 项目的依赖。我们希望在此获取 JPA 实体的定义、各种接口的定义以及异常类 [PamException] 的定义,
从 [mv-pam-openejb-eclipselink] 项目中,我们复制 [MainRemote] 类:
![]() |
类 [MainRemote] 必须获取 [metier] 层中 EJB 的引用。类 [MainRemote] 的代码如下所示:
// 没问题——可以申请工资单了
FeuilleSalaire feuilleSalaire = null;
IMetierRemote metier = null;
try {
// Glassfish服务器的JNDI上下文
InitialContext initialContext = new InitialContext();
// 业务层实例化
metier = (IMetierRemote) initialContext.lookup("java:global/istia.st_mv-pam-ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote");
// 计算工资单
feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
} catch (PamException ex) {
System.err.println("L'erreur suivante s'est produite : "
+ ex.getMessage());
return;
} catch (Exception ex) {
System.err.println("L'erreur suivante s'est produite : "
+ ex.toString());
return;
}
- 第 6 行:初始化 Glassfish 服务器的 JNDI 上下文。
- 第 8 行:向该上下文 JNDI 请求 [metier] 层的远程接口引用。根据 Glassfish 日志,我们得知 [metier] 层的远程接口有两个可能的名称:
第 1 行,名称 JNDI 可与所有应用服务器配合使用:JAVA、EE、6。第 2 行,名称 JNDI 专用于 Glassfish。 在代码第 9 行,我们使用可移植名称 JNDI。
- 其余代码保持不变
![]() |
在 [1] 中,我们配置项目使其带参数执行类 [MainRemote]。如果一切正常,项目执行将产生以下结果:
如果在属性中输入错误的社会保险号,将得到以下结果:
7.3. 控制台客户端 - 版本 2
在之前的版本中,Glassfish 服务器的 JNDI 环境是通过项目存档中的某个 [jndi.properties] 文件配置的。其默认内容如下:
# 访问 Sun Application Server
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
java.naming.factory.url.pkgs=com.sun.enterprise.naming
# 需要为 CosNaming 添加一个 javax.naming.spi.StateFactory,该
# 支持动态 RMI-IIOP。
java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
org.omg.CORBA.ORBInitialHost=localhost
org.omg.CORBA.ORBInitialPort=3700
第 7 行和第 8 行指定了 JNDI 服务的机器及其监听端口。 该文件不支持查询除 localhost 以外的 JNDI 服务器,也不支持查询使用除 3700 以外端口的 JNDI 服务器。 若需更改这两个参数,可以构建自己的 [jndi.properties] 文件,或使用 Spring 配置。本文将演示第二种方法。
首先,我们基于初始项目 [pam-client-metier-dao-jpa-eclipselink] 创建一个新项目。
![]() |
- 为 [1],新项目
- 为 [2],Spring 配置文件为 [spring-config-client.xml]。其内容如下:
Spring 配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
<!-- 职业 -->
<jee:jndi-lookup id="metier" jndi-name="java:global/istia.st_mv-pam-ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote">
<jee:environment>
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
java.naming.factory.url.pkgs=com.sun.enterprise.naming
java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
org.omg.CORBA.ORBInitialHost=localhost
org.omg.CORBA.ORBInitialPort=3700
</jee:environment>
</jee:jndi-lookup>
</beans>
此处使用了 Spring 2.0 引入的 <jee> 标签(第 14 行)。使用该标签需要定义其所属的架构,见第 4、10 和 11 行。
- 第 14 行:<jee:jndi-lookup> 标签用于从 JNDI 服务中获取对象的引用。 此处,我们将名为“metier”的 Bean 与资源 JNDI 关联,该资源关联于 EJB 和 [Metier]。 此处使用的名称 JNDI 是 JNDI 的可移植名称(Java EE 6)。
- 文件 [jndi.properties] 的内容将成为 <jee:environment> 标签(第 15 行)的内容,该标签用于定义连接 JNDI 服务的参数。
主类 [MainRemote] 的演变如下:
第 7-8 行,向 Spring 请求 [IMetierRemote] 类型的引用,该引用位于 [metier] 层。此方案为我们的架构带来了灵活性。 事实上,如果 [metier] 层中的 EJB 变为本地化,即 c.a.d, 在与我们的客户端 [MainRemote] 相同的 JVM 中运行,后者的代码将保持不变。 只有文件 [spring-config-client.xml] 的内容会发生变化。此时,我们将看到一种与第 5.11 节中研究的 Spring / JPA 架构类似的配置。
欢迎读者测试此新版本。
7.4. Swing客户端
现在,我们将构建基于客户端/服务器应用程序 EJB 的客户端 swing。
![]() |
文件 [pom.xml] 必须具备对 Swing 应用程序的必要依赖:
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
上文中的类 [PamJFrame] 最初是为在 Spring 环境中运行而编写的 / JPA:
![]() |
现在,该类需要成为部署在 Glassfish 服务器上的 EJB 的远程客户端。
![]() |
实践任务:参照项目中的 [ui.console.MainRemote] 控制台客户端示例,修改 [doMyInit] (参见第 5.12.4 节)获取 [metier] 层(该层现已变为远程)的引用。

























