7. 版本 3:将 PAM 应用程序移植到 GlassFish 应用服务器
我们建议将 OpenEJB/EclipseLink 架构中 [业务] 和 [DAO] 层的 EJB 部署到 GlassFish 应用服务器的容器中。
当前基于 OpenEJB / EclipseLink 的实现
![]() |
如上所述,[ui] 层使用 [business] 层的远程接口。
我们测试了两种执行环境:本地和远程。在远程模式下,[ui]层作为[business]层的客户端,而[business]层由EJB实现。为了在客户端/服务器模式下运行(即客户端和服务器分别在两个独立的JVM中运行),我们将把[business、DAO、JPA]层部署到GlassFish Java EE服务器上。该服务器已包含在NetBeans中。
将在 GlassFish 服务器上构建的实现
![]() |
- [UI] 层将在 Java SE(标准版)环境中运行
- [业务逻辑、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] 中,选择项目将运行的应用服务器。此处选择的服务器是 [运行时 / 服务器] 选项卡中可见的选项之一,本例中为 GlassFish v3。
- 在 [7] 中,选择 Java EE 版本。
![]() |
- 在 [1] 中,新建项目。它与标准 Java 项目有以下几点不同:
- 会自动创建一个 [Other Sources] 分支 [2]。该分支将特别包含用于配置 JPA 层的 [persistence.xml] 文件,
- 若您构建该项目(Build),将出现一个 [javaee-api-6.0] 依赖项 [3]。该依赖项属于“provided”类型,因为它由 Glassfish EJB 容器在运行时提供。
7.1.1.2. 配置持久化层
所谓“持久层配置”,是指编写 [persistence.xml] 文件,该文件定义:
- 要使用的 JPA 实现
- JPA 层所用数据源的定义。这将是一个由 GlassFish 服务器管理的 JDBC 数据源。
![]() |
我们可以按以下步骤操作。首先,在 [运行时 / 数据库] 选项卡中,我们将创建 [1] 一个连接到 MySQL5 / dbpam_eclipselink 数据库的连接:
![]() |
完成此操作后,我们可以继续创建 EJB 模块所使用的 JDBC 资源 :
![]() |
- 在 [1] 中,创建一个新文件——执行此操作前请确保已选中 EJB 项目
- 在 [2] 中,选择 EJB 项目
- 在 [3] 中,选择 [Glassfish] 类别
- 在 [4] 中,选择“创建 JDBC 资源”
![]() |
- 在 [5] 中,指定该 JDBC 资源将使用一个新的连接池。请注意,连接池是一组已打开的连接,用于加快应用程序与数据库之间的交互。
- 在 [6] 中,为创建的 JDBC 资源指定一个 JNDI 名称。该名称可以是任意内容,但通常采用 jdbc/name 的形式。此 JNDI 名称将在 [persistence.xml] 文件中用于指定 JPA 实现必须使用的数据源。
- 在 [7] 中,为即将创建的连接池指定任意名称
- 在下拉列表 [8] 中,选择之前为 MySQL 数据库 / dbpam_eclipselink 创建的 JDBC 连接。
- 在 [9] 中,显示连接池属性的摘要——保持所有设置不变
![]() |
- 在 [10] 中,您可以指定连接池的若干属性——保留默认值
- 在 [11] 中,完成为 EJB 模块创建 JDBC 资源的向导后,[Other Sources] 分支中生成了一个 [glassfish-resources.xml] 文件。该文件的内容如下:
[glassfish-resources.xml] 文件是一个 XML 文件,其中包含向导收集的所有数据。当将 EJB 模块部署到 GlassFish 服务器时,NetBeans 将使用该文件请求创建此模块所需的 JDBC 资源。
现在您可以创建 [persistence.xml] 文件,该文件将配置 EJB 模块的 JPA 层:
![]() |
- 在 [1] 中,创建一个新文件——执行此操作前请确保已选中 EJB 项目
- 在 [2] 中,选择 EJB 项目
- 在 [3] 中,选择 [持久化] 类别
- 在 [4] 中,创建一个持久化单元
![]() |
- 在 [5] 中,为持久化单元命名
- 在 [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"?>
<!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>
- 第 3 行:持久化单元的名称 [mv-pam-ejb-metier-dao-eclipselinkPU] 以及事务类型(EJB 容器的 JTA)
- 第 5 行:持久化层使用的数据源的 JNDI 名称:jdbc/dbpam_eclipselink
- 第 6 行:未指定 JPA 实体。系统将在 EJB 模块的类路径中搜索这些实体。
- 未指定所使用的 JPA 实现(Hibernate、EclipseLink 等)的名称。在此情况下,GlassFish v3 默认使用 EclipseLink。
7.1.1.3. 插入 [JPA、DAO、业务] 层
现在 [persistence.xml] 文件已定义完毕,我们可以继续将 [pam] 企业应用程序的 [业务、DAO、JPA] 层插入到项目中:
![]() |
这三个层与 OpenEJB 中的完全一致。我们只需在两个项目之间进行复制粘贴即可。这就是我们目前正在做的事情:
![]() |
- 在 [1] 中,将 [mv-pam-openejb-eclipselink] 项目中的 [jpa, dao, business, exception] 包复制到 EJB 模块 [mv-pam-ejb-business-dao-jpa-eclipselink] 中的结果
7.1.1.4. 配置 GlassFish 服务器
我们还需要在两个方面配置 GlassFish 服务器:
- JPA 层由 EclipseLink 实现。我们需要确保 GlassFish 服务器已安装该 JPA 实现所需的库。
- 数据源为 MySQL 数据库。我们需要确保 GlassFish 服务器已安装该数据库管理系统的 JDBC 驱动程序。
在部署 EJB 模块时,您可能会发现这些库缺失。以下是向 GlassFish 服务器添加缺失库的几种方法之一:
![]() |
- 在 [1] 中,查看 GlassFish 服务器的属性
- 在 [2] 中,记下服务器的 domains 文件夹。我们将它称为 <domains>
- 在 <domains>\domain1\lib 文件夹中放置缺失的库文件。在本例中,添加了 Hibernate 库(lib/hibernate-tools)和 MySQL JDBC 驱动程序(lib/misc)。默认情况下,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] 中,已在 GlassFish 服务器上创建了 JDBC 资源 [jdbc / dbpam_eclipselink]。请注意,我们在第 7.1.1.2 节中定义了该资源。
部署过程中,GlassFish 服务器会在控制台记录以下信息:
<?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、6、8 和 11 行:部署的 EJB 的可移植 JNDI 名称。Java EE 6 引入了可移植 JNDI 名称的概念。这表示一个被所有 Java EE 6 服务器识别的 JNDI 名称。在 Java EE 5 中,JNDI 名称是特定于所使用的服务器的。
- 第 4、7、9 和 12 行:部署的 EJB 的 JNDI 名称,采用 GlassFish v3 专有的格式。
这些名称对于我们即将编写、用于调用已部署 EJB 模块的控制台应用程序将非常有用。
7.2. 控制台客户端 - 版本 1
既然我们已经部署了客户端/服务器应用程序的服务器端,接下来我们将探讨客户端 [1]:
![]() |
7.2.1. 客户端项目
我们创建一个名为 [mv-pam-client-ejb-metier-dao-eclipselink] 的 [Java 应用程序] 类型的新 Maven 项目:
![]() |
- 在 [1] 中,客户端项目
在 [pom.xml] 文件中,我们添加以下依赖项:
- 第 31–35 行:对 [gf-client] 库的依赖,该库允许 GlassFish 客户端与远程服务器通信,
- 第 36–41 行:对 EJB 模块 Maven 项目的依赖。此处,我们需要获取 JPA 实体和各种接口的定义,以及异常类 [PamException] 的定义,
从 [mv-pam-openejb-eclipselink] 项目中,我们复制 [MainRemote] 类:
![]() |
[MainRemote] 类必须获取 [business] 层中 EJB 的引用。 [MainRemote] 类的代码修改如下:
<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>
- 第 6 行:初始化 GlassFish 服务器的 JNDI 上下文。
- 第 8 行:查询此 JNDI 上下文以获取 [business] 层的远程接口引用。根据 GlassFish 日志,我们知道 [business] 层的远程接口有两个可能的名称:
// it's OK - we can ask for the payslip
FeuilleSalaire feuilleSalaire = null;
IMetierRemote metier = null;
try {
// context JNDI of Glassfish server
InitialContext initialContext = new InitialContext();
// business layer instantiation
metier = (IMetierRemote) initialContext.lookup("java:global/istia.st_mv-pam-ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote");
// wage sheet calculation
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;
}
第 1 行:可在任何 Java EE 6 应用服务器上使用的 JNDI 名称。第 2 行:GlassFish 专用的 JNDI 名称。在代码的第 9 行中,我们使用了可移植的 JNDI 名称。
- 其余代码保持不变
![]() |
在[1]中,我们配置了项目以带参数运行[MainRemote]类。如果一切顺利,运行该项目将得到以下结果:
如果在属性中输入了错误的社会保障号码,将得到以下结果:
7.3. 客户端控制台 - 版本 2
在以前的版本中,Glassfish 服务器的 JNDI 环境是通过位于项目归档文件中的 [jndi.properties] 文件进行配置的。其默认内容如下:
第 7 行和第 8 行指定了 JNDI 服务机及其监听端口。此文件不允许您查询除 localhost 以外的 JNDI 服务器,或查询运行在 3700 端口以外的 JNDI 服务器。如果您想更改这两个设置,可以创建自己的 [jndi.properties] 文件,或者使用 Spring 配置。我们将演示后一种方法。
首先,我们基于初始的 [pam-client-metier-dao-jpa-eclipselink] 项目创建一个新项目。
![]() |
- 在[1]中,新项目
- 在 [2] 中,是 Spring 配置文件 [spring-config-client.xml]。其内容如下:
Spring 配置文件如下:
# accès JNDI à Sun Application Server
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
java.naming.factory.url.pkgs=com.sun.enterprise.naming
# Required to add a javax.naming.spi.StateFactory for CosNaming that
# supports dynamic RMI-IIOP.
java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
org.omg.CORBA.ORBInitialHost=localhost
org.omg.CORBA.ORBInitialPort=3700
这里我们使用了 Spring 2.0 中引入的 <jee> 标签(第 14 行)。使用该标签需要定义其所属的模式,即第 4、10 和 11 行。
- 第 14 行:<jee:jndi-lookup> 标签用于从 JNDI 服务获取对象引用。在此,我们将名为“metier”的 Bean 与关联 [Metier] EJB 的 JNDI 资源相关联。此处使用的 JNDI 名称是该 EJB 的可移植名称(Java EE 6)。
- [jndi.properties] 文件的内容将成为 <jee:environment> 标签(第 15 行)的内容,该标签用于定义 JNDI 服务的连接参数。
主类 [MainRemote] 发生如下变化:
<?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">
<!-- business -->
<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>
第 7–8 行:向 Spring 请求 [business] 层中的 [IMetierRemote] 类型引用。该方案为我们的架构带来了灵活性。事实上,如果 [business] 层中的 EJB 变为本地化——即与我们的 [MainRemote] 客户端在同一 JVM 中执行——客户端的代码将保持不变。 只需修改 [spring-config-client.xml] 文件的内容即可。届时,我们的配置将与第 5.11 节中讨论的 Spring/JPA 架构类似。
欢迎读者测试此新版本。
7.4. Swing 客户端
接下来,我们将为我们的 EJB 客户端/服务器应用程序构建 Swing 客户端。
![]() |
[pom.xml] 文件必须包含 Swing 应用程序所需的依赖项:
上文中的 [PamJFrame] 类最初是为在 Spring/JPA 环境中运行而编写的:
![]() |
现在,该类必须成为部署在 Glassfish 服务器上的 EJB 的远程客户端。
![]() |
实践练习:参照项目中 [ui.console.MainRemote] 控制台客户端的示例,修改 [PamJFrame] 类中 [doMyInit] 方法(参见第 5.12.4 节)的调用方式,以获取对 [business] 层的引用——该层现已变为远程层。

























