Skip to content

7. 第 3 版:将 PAM 应用程序移植到 Glassfish 应用服务器

我们计划将OpenEJB架构中的[metier]和[DAO]层,以及 EclipseLink 架构中的 [metier] 和 [DAO] 层,部署到 Glassfish 应用服务器的容器中。

当前的实现使用 OpenEJB / EclipseLink

如上所述,[ui] 层使用 [metier] 层的远程接口。

我们测试了两个运行环境:localdistant。 在后一种模式下,[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 服务器会在控制台记录一些有用的信息:

INFO: Realm admin-realm of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created.
....
INFO: Portable JNDI names for EJB IndemniteDao : [java:global/pam-serveur-metier-dao-jpa-eclipselink/IndemniteDao!DAO.IIndemniteDaoLocal, java:global/pam-serveur-metier-dao-jpa-eclipselink/IndemniteDao!DAO.IIndemniteDaoRemote]
INFO: Glassfish-specific (Non-portable) JNDI names for EJB IndemniteDao : [DAO.IIndemniteDaoRemote#DAO.IIndemniteDaoRemote, DAO.IIndemniteDaoRemote]
...
INFO: Portable JNDI names for EJB CotisationDao : [java:global/pam-serveur-metier-dao-jpa-eclipselink/CotisationDao!DAO.ICotisationDaoLocal, java:global/pam-serveur-metier-dao-jpa-eclipselink/CotisationDao!DAO.ICotisationDaoRemote]
INFO: Glassfish-specific (Non-portable) JNDI names for EJB CotisationDao : [DAO.ICotisationDaoRemote, DAO.ICotisationDaoRemote#DAO.ICotisationDaoRemote]
INFO: Portable JNDI names for EJB Metier : [java:global/pam-serveur-metier-dao-jpa-eclipselink/Metier!metier.IMetierRemote, java:global/pam-serveur-metier-dao-jpa-eclipselink/Metier!metier.IMetierLocal]
INFO: Glassfish-specific (Non-portable) JNDI names for EJB Metier : [metier.IMetierRemote#metier.IMetierRemote, metier.IMetierRemote]
...
INFO: Portable JNDI names for EJB EmployeDao : [java:global/pam-serveur-metier-dao-jpa-eclipselink/EmployeDao!DAO.IEmployeDaoLocal, java:global/pam-serveur-metier-dao-jpa-eclipselink/EmployeDao!DAO.IEmployeDaoRemote]
INFO: Glassfish-specific (Non-portable) JNDI names for EJB EmployeDao : [DAO.IEmployeDaoRemote#DAO.IEmployeDaoRemote, DAO.IEmployeDaoRemote]
INFO: pam-serveur-metier-dao-jpa-eclipselink was successfully deployed in 12 891 milliseconds.

请注意第

  • 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] 层的远程接口有两个可能的名称:
Infos: EJB5181:Portable JNDI names for EJB Metier: [java:global/istia.st_mv-pam-ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierLocal, java:global/istia.st_mv-pam-ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB Metier: [metier.IMetierRemote#metier.IMetierRemote, metier.IMetierRemote]

第 1 行,名称 JNDI 可与所有应用服务器配合使用:JAVA、EE、6。第 2 行,名称 JNDI 专用于 Glassfish。 在代码第 9 行,我们使用可移植名称 JNDI。

  • 其余代码保持不变

在 [1] 中,我们配置项目使其带参数执行类 [MainRemote]。如果一切正常,项目执行将产生以下结果:

run:
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 2 seconds)

如果在属性中输入错误的社会保险号,将得到以下结果:

1
2
3
run:
L'erreur suivante s'est produite : L'employé de n°[254104940426058x] est introuvable
BUILD SUCCESSFUL (total time: 2 seconds)

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] 的演变如下:

...
     // 没问题——可以申请工资单
    FeuilleSalaire feuilleSalaire = null;
    IMetierRemote metier=null;
    try {
       // [metier] 层实例化
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-client.xml");
      metier = (IMetierRemote) ctx.getBean("metier");
       // 工资单计算
      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;
    }
    ...

第 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] 层(该层现已变为远程)的引用。