4. 示例应用程序 – 02:rdvmedecins-jsf2-spring
现在,我们将把前一个应用程序移植到 Spring/Tomcat 环境中:
![]() |
这确实是一个移植项目。我们将以之前的应用程序为基础,对其进行调整以适应新环境。本文仅对所做的修改进行说明。主要有三处改动:
- 服务器不再是 GlassFish,而是 Tomcat——一款不带 EJB 容器的轻量级服务器,
- 为替代 EJB,我们将采用 Spring——EJB 的主要竞争对手 [http://www.springsource.com/],
- 所使用的JPA实现将采用Hibernate,而非EclipseLink。
由于我们需要在新旧项目之间进行大量复制粘贴操作,因此将在 NetBeans 中保持旧项目处于打开状态:
![]() |
使用 Spring 框架需要具备一定的知识,相关内容可参见 [ref7](见第 166 页)。
4.1. [DAO] 和 [JPA] 层
![]() |
4.1.1. NetBeans 项目
我们正在构建一个类型为 [Java 应用程序] 的 Maven 项目:
![]() | ![]() | ![]() |
![]() |
- 在 [1] 中,生成的项目,
- 在 [2] 中,是同一项目,但删除了 [Source Packages] 和 [Test Packages],同时也移除了 [junit-3.8.1] 依赖项。
Maven 项目中最困难的部分是找到正确的依赖项。对于这个 Spring / JPA / Hibernate 项目,它们如下所示:
<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-rdvmedecins-spring-dao-jpa</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-rdvmedecins-spring-dao-jpa</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate.java-persistence</groupId>
<artifactId>jpa-api</artifactId>
<version>2.0.Beta-20090815</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
</dependencies>
</project>
- 第 18–29 行:针对 Hibernate,
- 第 30–34 行:针对 MySQL JDBC 驱动程序,
- 第 35–41 行:用于 JUnit 测试,
- 第 42–51 行:用于 Apache Commons DBCP 连接池。连接池是一组已打开的连接。当应用程序需要连接时,它会从池中请求一个;当不再需要时,则将其归还。 连接在应用程序启动时建立,并在应用程序生命周期内保持打开状态。这避免了反复打开和关闭连接带来的开销。GlassFish 中也存在此类连接池,但其使用对我们而言是透明的。此处情况亦然,但我们需要进行安装和配置,
- 第 52–75 行:针对 Spring。
让我们添加这些依赖项并构建项目:
![]() |
- 在 [1] 中,我们构建项目,这将迫使 Maven 下载依赖项;
- 在 [2] 中,这些依赖项随后会出现在 [Dependencies] 分支中。由于 Hibernate 和 Spring 框架本身就包含大量依赖项,因此数量相当庞大。同样,得益于 Maven,我们无需为此操心,它们会自动下载。
现在依赖项已准备就绪,我们将 [dao] 层中的 EJB 项目代码粘贴到 Spring 项目的 [dao] 层中:
![]() |
- 在 [1] 中,我们将代码复制到源项目,
- 在 [2] 中,我们将内容粘贴到目标项目中,
- 在 [3] 中,显示结果。
复制完成后,您需要修复错误。
4.1.2. [exceptions] 包
![]() |
[RdvMedecinsExceptions] 类 [1] 存在错误,因为第 4 行中的 [javax] 包已不存在。这是一个 EJB 专用的包。第 6 行的错误源于第 4 行的错误。我们删除这两行代码。这样就解决了这些错误 [2]。
4.1.3. [jpa] 包
![]() |
- 在 [1] 中,由于第 [5] 行缺少 validation 包,[Creneau] 类存在错误。我们本可以将该包添加到项目依赖中。但在测试过程中,Hibernate 因此抛出了异常。鉴于它对我们的应用程序并非必需,我们将其移除了。要修复该类,只需删除所有错误的行 [2]。我们对所有存在错误的类都进行此操作。
4.1.4. [dao] 包
目前我们已到达以下阶段:
![]() |
- 在 [1] 中,两个已修正的包,
- 在[2]中,[dao]包。由于不再存在任何EJB,EJB远程和本地接口的概念已不再适用。我们将它们移除[3]。
![]() |
- 在 [1] 中,[DaoJpa] 类中的错误有两个原因:
- 导入了与 EJB 相关的包(第 6–8 行);
- 使用了我们刚刚移除的本地和远程接口。
我们删除了有问题的代码行,并使用 [IDao] 接口代替了本地和远程接口 [2]。
![]() |
在 EJB 项目中,[DaoJpa] 类是一个单例,其方法在事务内运行。我们将看到,[DaoJpa] 类将成为由 Spring 管理的 Bean。默认情况下,每个 Spring Bean 都是单例。这满足了第一个属性。第二个属性则是通过 Spring 的 @Transactional 注解实现的 [3]:
![]() |
完成这些操作后,项目中不再存在任何错误 [4]。
4.1.5. 配置 [JPA] 层
在 EJB 项目中,我们通过 [persistence.xml] 文件配置了 [JPA] 层。这里我们也有一个 [JPA] 层,因此需要创建该文件。在 EJB 项目中,我们使用 GlassFish 生成了该文件。而在这里,我们将手动构建它。主要原因是 [persistence.xml] 文件中的部分配置已迁移到 Spring 配置文件本身中。
我们创建 [persistence.xml] 文件:
![]() |
内容如下:
<?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="spring-dao-jpa-hibernate-mysqlPU" transaction-type="RESOURCE_LOCAL">
<class>rdvmedecins.jpa.Client</class>
<class>rdvmedecins.jpa.Creneau</class>
<class>rdvmedecins.jpa.Medecin</class>
<class>rdvmedecins.jpa.Rv</class>
</persistence-unit>
</persistence>
- 第 3 行:我们为持久化单元命名,
- 第 3 行:事务类型为 RESOURCE_LOCAL。在 EJB 项目中,该值是 JTA,表示事务由 EJB 容器管理。值 RESOURCE_LOCAL 表示应用程序自行管理事务。在此处,我们将通过 Spring 实现这一点,
- 第4–7行:四个JPA实体的完整名称。此项为可选,因为Hibernate会自动在项目的ClassPath中搜索它们。
就这样。JPA 提供程序的名称、其属性以及数据源的 JDBC 特性现已写入 Spring 配置文件。
4.1.6. Spring 配置文件
我们提到过,[DaoJpa] 类是 Spring 管理的 Bean。这是通过配置文件实现的。该文件还将包含数据库访问配置以及事务管理配置。它必须位于项目的 ClassPath 中。我们将它放在 [Other sources] 文件夹中:
![]() |
[spring-config-dao.xml] 文件内容如下:
<?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"
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">
<!-- application layers -->
<bean id="dao" class=" " />
<!-- EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!--
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
-->
</bean>
</property>
</bean>
<!-- data source DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!-- transaction manager -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- translation of exceptions -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- persistence -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
这是一个与 Spring 2.x 兼容的文件。我们尚未尝试使用 3.x 版本的新功能。
- 第 2–4 行:配置文件的根 <beans> 标签。我们在此不详细说明该标签的各项属性。请务必仔细复制粘贴,因为这些属性中的任何错误都可能导致难以理解的错误。
- 第 7 行:"dao" Bean 引用了 [rdvmedecins.dao.DaoJpa] 类的实例。将创建一个单例(singleton),并实现应用程序的 [dao] 层,
- 第 24–29 行:定义了一个数据源。它提供了我们之前提到的“连接池”服务。此处使用了来自 Apache Commons DBCP 项目 [http://jakarta.apache.org/commons/dbcp/] 的 [DBCP],
- 第 25–28 行:为了与目标数据库建立连接,数据源需要知道所使用的 JDBC 驱动程序(第 25 行)、数据库 URL(第 26 行)、连接用户名及其密码(第 27–28 行),
- 第 10–21 行:配置 JPA 层,
- 第 10 行:定义了一个 [EntityManagerFactory] Bean,该 Bean 能够创建 [EntityManager] 对象来管理持久化上下文。实例化的类 [LocalContainerEntityManagerFactoryBean] 由 Spring 提供。它需要若干参数才能实例化自身,这些参数在第 11–20 行中定义,
- 第 11 行:用于获取 DBMS 连接的数据源。即第 24–29 行中定义的 [DBCP] 源,
- 第 12–20 行:要使用的 JPA 实现,
- 第 13 行:定义使用 Hibernate 作为 JPA 实现,
- 第 14 行:Hibernate 必须与目标 DBMS 配合使用的 SQL 方言,此处为 MySQL5,
- 第 16 行(已注释):要求将 Hibernate 执行的 SQL 语句记录到控制台,
- 第 17 行(已注释):要求在应用程序启动时生成数据库(删除并创建),
- 第 32 行:指定使用 Java 注解管理事务(也可以在 spring-config.xml 中声明)。具体来说,这是 [DaoJpa] 类中使用的 @Transactional 注解,
- 第 33–35 行:定义要使用的事务管理器,
- 第 33 行:事务管理器是 Spring 提供的类,
- 第 34 行:Spring 的事务管理器需要知道管理 JPA 层的 EntityManagerFactory。这就是第 10–21 行中定义的那个,
- 第 41 行:定义了管理 Spring 持久化注解的类,
- 第 38 行:定义了 Spring 类,该类负责处理 @Repository 注解等功能,使得被此注解标注的类能够将数据库管理系统(DBMS)中的原生 JDBC 驱动程序异常转换为 [DataAccessException] 类型的通用 Spring 异常。这种转换将原生 JDBC 异常封装在 [DataAccessException] 类型中,该类型包含多个子类:

这种转换使客户端程序能够以通用方式处理异常,而无需考虑目标数据库管理系统。我们在 Java 代码中并未使用 @Repository 注解。因此,第 38 行是多余的。我们保留它仅供参考。
至此,Spring 配置文件的配置已完成。该配置摘自 Spring 文档。将其适配到各种场景中,通常只需进行两处修改:
- 目标数据库:第24–29行,
- JPA 实现:第 12–20 行。
代码运行时,配置文件中的所有 Bean 都会被实例化。我们接下来将了解具体实现方式。
4.1.7. JUnit 测试类
![]() |
我们使用 JUnit 测试对 EJB 项目的 [DAO] 层进行了测试。接下来,我们将对 Spring 项目的 [DAO] 层进行同样的测试:
- 在[1]和[2]中,将JUnit测试在两个项目之间复制粘贴,
- 在[3]中,导入的测试在新环境中出现了错误。
![]() |
[1] 中报告的错误是 EJB 的远程接口已不存在。此外,第 19 行 [dao] 字段的初始化代码原本是一个 EJB 专用的 JNDI 调用(第 25–28 行)。要实例化第 19 行中的 [dao] 字段,我们需要使用 Spring 配置文件。具体操作如下:
![]() |
- 第 21 行:接口类型已变为 [IDao],
- 第 28 行:实例化 [spring-config-dao.xml] 文件中声明的所有 Bean,具体而言是这个:
<bean id="dao" class="rdvmedecins.dao.DaoJpa" />
- 第 29 行从第 28 行的 Spring 上下文中请求 id="dao" 的 Bean 引用。随后,我们获取了 Spring 已实例化的 [DaoJpa] 单例(即上文所述的类)的引用。
第 28–29 行构建了以下代码块(粉色虚线):
![]() |
当 JUnit 客户端测试运行时,[DAO] 层已被实例化。因此,我们可以测试其方法。请注意,与需要 Glassfish 服务器的 EJB [DAO] 测试不同,运行此测试无需服务器。在此,所有操作均在同一 JVM 内运行。
现在我们可以运行 JUnit 测试了。MySQL 服务器必须处于运行状态。测试结果如下:
![]() |
JUnit 测试通过了。让我们像在 EJB 测试中那样查看测试日志:
- 第1-4行:Spring日志,
- 第5-10行:Hibernate日志,
- 第 11 行:Spring 报告了所有已实例化的 Bean。首先,我们看到 [dao] Bean,
- 第 12 行及后续:JUnit 测试日志,
- 第 60–65 行:我们清楚地看到因向数据库添加已存在的预约而引发的异常。回想一下,在 EJB 中,由于序列化问题,我们并未遇到此异常。
[dao] 层已正常运行。接下来我们将构建 [business] 层。
4.2. [业务]层
![]() |
我们采用与[DAO]层相同的方法,将EJB项目中的内容复制并粘贴到Spring项目中。
4.2.1. NetBeans 项目
我们正在创建一个类型为 [Java 应用程序] 的新 Maven 项目,并删除了所有不需要保留的内容 [1]:
![]() |
4.2.2. 项目依赖项
在架构中:
![]() |
[业务]层依赖于[DAO]层。因此,我们需要在当前项目中添加对前一个项目的依赖:
![]() |
- 在 [1] 和 [2] 中,我们添加了对 [DAO] 层项目的依赖;
- 在[3]中,这一依赖引入了其他依赖,即[DAO]层项目的依赖。
![]() |
- 在[1]和[2]中,我们将Java源代码从EJB项目复制到Spring项目,
- 而在[3]中,导入的源代码在新环境中出现了错误。
首先,我们将 [4] 中已不存在的远程和本地接口从 [业务] 层中移除:
![]() |
- 在 [5] 中,[Business] 类中的错误有多种原因:
- 使用了已不存在的 [javax.ejb] 包;
- 使用了已不存在的 [IDaoLocal] 接口;
- 使用了已不存在的 [IMetierRemote] 和 [IMetierLocal] 接口。
我们将
- 删除所有与 [javax.ejb] 包相关的错误行,
- 将 [IDaoLocal] 接口替换为 [IDao] 接口,
- 将 [IMetierRemote] 和 [IMetierLocal] 接口替换为 [IMetier] 接口。
![]() |
- 在 [6] 中,按照上述方法修正类后,
- 在[7]中,不再出现错误。
我们已移除了对 EJB 的引用,但现在需要获取它们的属性:
![]() |
- 第 22 行:我们原本使用了一个单例。通过将该类设为 Spring 管理的 Bean,即可实现此行为,
- 第 23 行:每个方法都在事务中运行。这将通过使用 Spring 的 @Transactional 注解来实现,
- 第 27–28 行:通过 EJB 容器注入获取了对 [DAO] 层的引用。我们将使用 Spring 注入。
因此,Spring 项目中 [Metier] 类的代码演变如下:
![]() |
Java 代码部分到此结束。其余内容将在 Spring 配置文件中完成。
4.2.3. Spring 配置文件
我们将 Spring 配置文件从 [DAO] 层项目复制到 [Business] 层项目中。首先,如果 [Business] 层项目中不存在 [Other Resources] 分支,则创建该分支:
![]() |
- 在 [1] 的 [Files] 选项卡中,在 [main] 文件夹下创建一个子文件夹,
- 在 [2] 中,该子文件夹应命名为 [resources],
- 在 [3] 中,[Projects] 选项卡下已创建 [Other Sources] 分支。
现在我们可以复制并粘贴 Spring 配置文件:
![]() |
- 在 [1] 中,将文件从 [DAO] 项目复制到 [business] 项目 [2] 中,
- 在 [3] 中,即该已复制的文件。
已复制的配置文件用于配置 [DAO] 层。我们向其中添加一个 Bean 来配置 [business] 层:
- 第 2 行:[DAO] 层 Bean,
- 第 3–5 行:[业务] 层 Bean,
- 第 3 行:该 Bean 名为 métier(id 属性),是 [rdvmedecins.metier.service.Metier] 类的实例(class 属性)。该 Bean 将与其他 Bean 一样,在应用程序启动时被实例化。
让我们回顾一下 [rdvmedecins.metier.service.Metier] Bean 的代码:
package rdvmedecins.metier.service;
...
public class Metier implements IMetier, Serializable {
// dao layer
private IDao dao;
public Metier() {
}
- 第 8 行:[dao] 字段将由 Spring 在业务 Bean 实例化时一并实例化。让我们回到 Spring 配置文件中该 Bean 的定义:
- 第 4 行:使用 <property> 标签来初始化已实例化的 Bean 的字段。字段名称由 name 属性指定。 因此,[rdvmedecins.metier.service.Metier] 类的 dao 字段将被实例化。这通过 setDao 方法实现,该方法必须存在。分配给它的值即为 ref 属性的值。此处,该值是第 2 行中 dao Bean 的引用。
简而言之,在代码中:
package rdvmedecins.metier.service;
...
public class Metier implements IMetier, Serializable {
// dao layer
private IDao dao;
public Metier() {
}
第 19 行中的 dao 字段将由 Spring 通过引用 [dao] 层进行初始化。这正是我们所期望的。dao 字段将由 Spring 通过一个我们需要添加的 setter 方法进行初始化:
// setter
public void setDao(IDao dao) {
this.dao = dao;
}
我们将 Spring 配置文件重命名为以反映这些更改:
![]() |
现在我们可以进行测试了。我们将使用之前用于测试 [Business] EJB 的控制台测试。
4.2.4. 测试 [业务] 层
该测试将采用以下架构运行:
![]() |
我们将 EJB 项目中的控制台测试复制到 Spring 项目中:
![]() |
- 在[1]和[2]中,在两个项目之间进行复制粘贴;
- 在 [3] 中,导入的代码存在错误。
![]() |
导入的代码包含两种类型的错误:
- 第 13 行:[IMetierRemote] 接口已被 [IMetier] 接口取代,
- 第 24–27 行:[business] 层不再通过 JNDI 调用进行实例化,而是通过从 Spring 配置文件中实例化 Bean 来实现。
我们修正了这两个问题:
![]() |
- 第 22 行:使用了 [spring-config-metier-dao.xml] 文件。该文件中的所有 Bean 随后都会被实例化。其中包括以下内容:
<!-- application layers -->
<bean id="dao" class="rdvmedecins.dao.DaoJpa" />
<bean id="metier" class="rdvmedecins.metier.service.Metier">
<property name="dao" ref="dao"/>
</bean>
这两个 Bean 分别代表测试架构中的 [DAO] 层和 [业务] 层:
![]() |
完成上述步骤后,即可运行测试:
![]() |
测试日志如下:
- 第 1–4 行:Spring 和 Hibernate 日志,
- 第 5 行:由 Spring 实例化的 Bean。请注意 DAO 和业务 Bean,
- 第 6–53 行:测试日志。这些日志与 EJB 项目测试中获得的结果一致。请参阅该测试的相关注释(第 3.5.3 节)。
我们已经构建了[业务]层。现在我们将转向最后一个层,即[Web]层。
4.3. [Web] 层
![]() |
要构建 [web] 层,我们将采用与其他两层相同的方法,即从 EJB 项目的 [web] 层进行复制和粘贴。
4.3.1. NetBeans 项目
首先,我们创建一个 Web 项目:
![]() |
- 在 [1] 中,创建一个新项目,
- 在 [2] 中,选择 [Web Application] 类型的 Maven 项目,
- 在 [3] 中,为其命名,
![]() |
- 在 [4] 中,这次我们选择 Tomcat 服务器,而不是之前用于 EJB 项目的 GlassFish,
- 在 [5] 中,生成的项目,
- 在[6]中,是移除了[index.jsp]和[Source Packages]包后的项目。
4.3.2. 项目依赖关系
让我们来看看项目架构:
![]() |
[Web] 层依赖于 [业务]、[DAO] 和 [JPA] 层。这些层属于我们刚刚构建的两个项目。因此,需要对这两个项目分别进行依赖声明:
![]() |
- 在 [1] 中,我们添加了对 Spring / business 项目的依赖,
- 在 [2] 中,已添加 Spring / business 项目。由于该项目本身依赖于 Spring / DAO / JPA 项目,因此后者已自动添加到依赖项中 [3]。
让我们回到应用程序的结构:
![]() |
Web 层是 JSF 层。因此,我们需要 Java Server Faces 库。Tomcat 服务器中没有这些库。因此,该依赖项不会像在 GlassFish 服务器中那样具有 [provided] 作用域,而是具有 [compile] 作用域——这是未指定作用域时的默认作用域。
我们直接在 [pom.xml] 文件中添加这些依赖项:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-spring-metier</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 已在 [pom.xml] 文件中添加了第 7–16 行。这些是 JSF 的依赖项,也是 EJB/GlassFish 项目中使用的依赖项。请注意,这些依赖项没有 <scope> 标签,因此默认具有 [compile] 作用域。这样,JSF 库就会被嵌入到 Web 项目的 [war] 归档中。
将这些依赖项添加到 [pom.xml] 文件后,我们编译项目以下载这些依赖项。
4.3.3. 将 JSF/GlassFish 项目移植到 JSF/Tomcat 项目
我们将 JSF/Glassfish 项目中的所有代码复制到 JSF/Tomcat 项目中:
![]() |
- [1, 2, 3]:将旧项目中的网页复制到新项目中,
![]() |
- [1, 2, 3]: 将旧项目的 Java 代码复制到新项目中。出现了错误。这是正常的。我们会修复它们,
![]() |
- 在 [1] 中,于 NetBeans 的 [文件] 选项卡下,在 [main] 文件夹内创建一个 [resources] 子文件夹,
- 这将在 [项目] 选项卡 [3] 中生成 [其他源] 分支,
![]() |
- [1, 2, 3]:将消息文件从旧项目复制到新项目。
4.3.4. 对导入项目的修改
我们注意到导入的 Java 代码中存在错误。让我们来检查一下:
![]() |
- 在 [1] 中,仅 [Application] Bean 存在错误;
- 而在 [2] 中,错误完全源于 [IMetierLocal] 接口,该接口已不复存在。这里可能令人惊讶的是,第 20 行并未被标记为错误。@EJB 注解明确指向 EJB,且在此处被识别。这是由于存在 [javaee-web-api-6.0] 依赖项 [3] 所致。 Java EE 6 引入了一种架构,允许在不具备 EJB 容器的服务器上部署基于 EJB 且不包含远程接口的 Web 应用程序。服务器只需提供 [javaee-web-api-6.0] 依赖项即可。我们可以看到,该依赖项具有 [provided] 作用域 [3]。
在此,我们将不使用 [javaee-web-api-6.0] 依赖项。我们将其移除 [1]:
![]() |
![]() |
这会导致新的错误[2]。我们先从[Form] Bean中的错误开始:
![]() |
- 在 [1] 中,出错的代码行与丢失 [javax] 包有关。 我们将它们全部删除 [2]。这些错误行使得 [Form] 类成为了一个会话范围的 Bean([1] 的第 18–20 行)。此外,[Application] Bean 是在第 25 行注入的。这些信息将被移至 JSF 配置文件 [faces-config.xml] 中。
接下来我们来看 [Application] Bean:
![]() |
我们从 [1] 中删除了所有错误的代码行,并将第 13 行和第 21 行中的 [IMetierLocal] 接口改为 [IMetier]。在 [2] 中,已无错误。在 [1] 中,我们删除了第 15–16 行,这些代码曾将 [Application] 类定义为应用程序作用域的 Bean。 这些信息将被移至 JSF 配置文件 [faces-config.xml] 中。我们 还删除了第 20 行,该行曾将 [business] 层的引用注入到 Bean 中。现在,该 Bean 将由 Spring 进行初始化。我们已经拥有必要的配置文件,即来自 Spring / Business 项目的那个。我们将它复制过来:
![]() |
- 在 [1, 2] 中,我们将 Spring / Business 项目中的 Spring 配置文件复制到 Spring / JSF 项目中,
![]() |
在 [3] 中,结果如下。
在 [Application] Bean 中,我们需要使用此配置文件来获取 [business] 层的引用。这在它的 [init] 方法中完成:
package beans;
...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
// business layer
private IMetier metier;
...
public Application() {
}
@PostConstruct
public void init() {
try {
// instantiation layer [business]
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
metier = (IMetier) ctx.getBean("metier");
// caching doctors and customers
...
} catch (Throwable th) {
...
}
...
}
- 第 20 行:实例化来自 Spring 配置文件的 Bean,
- 第 21 行:请求业务 Bean 的引用,即位于 [business] 层中的 Bean。
一般而言,Spring Bean 应在应用范围 Bean 的 init 方法中进行实例化。还有另一种方法,即通过 Spring Servlet 来实例化 Bean。这需要修改 [web.xml] 文件,并添加对 [spring-web] 构建产品的依赖。为了与之前的代码保持一致,我们在此并未采用这种做法。
我们已移除了 [Application] 和 [Form] 类中使其成为 JSF Bean 的注解。这些类必须保持为 JSF Bean。现在,我们不再使用注解,而是通过 JSF 配置文件 [WEB-INF/faces.config.xml] 来声明这些 Bean。
![]() |
该文件现在如下所示:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<!-- message file -->
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
<!-- the applicationBean bean -->
<managed-bean>
<managed-bean-name>applicationBean</managed-bean-name>
<managed-bean-class>beans.Application</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<!-- the bean form -->
<managed-bean>
<managed-bean-name>form</managed-bean-name>
<managed-bean-class>beans.Form</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>application</property-name>
<value>#{applicationBean}</value>
</managed-property>
</managed-bean>
</faces-config>
- 第 10–19 行配置了消息文件。这是我们在 JSF/EJB 项目中唯一的配置,
- 第 21–35 行声明了 JSF 应用程序 Bean。这是 JSF 1.x 中的标准做法。JSF 2 引入了注解,但 JSF 1.x 的方法仍然受支持,
- 第 21–25 行:声明 applicationBean,
- 第 22 行:Bean 的名称。您可能会想使用“application”这个名称,但请避免这样做,因为这是 JSF 预定义 Bean 的名称,
- 第 23 行:Bean 类的全名,
- 第 24 行:其作用域,
- 第 27–35 行:定义表单 Bean,
- 第 28 行:Bean 的名称,
- 第 29 行:Bean 类的全名,
- 第 30 行:其作用域,
- 第 31–34 行:定义 [beans.Form] 类的属性,
- 第 32 行:属性名称。[beans.Form] 类必须包含一个同名字段及相应的 setter 方法,
- 第 33 行:字段的值。此处是第 21 行定义的 applicationBean 的引用。因此,我们将应用范围的 Bean 注入到会话范围的 Bean 中,以便后者能够访问应用范围的数据。
我们之前提到,[beans.Form] Bean 的 [application] 字段将通过设置器进行初始化。因此,如果 [beans.Form] 类中尚未存在该字段,我们必须将其添加进去:
public void setApplication(Application application) {
this.application = application;
}
4.3.5. 测试应用程序
我们的应用程序现已无错误,可以进行测试了:
![]() |
- 在 [1] 中,修正后的项目,
- 在 [2] 中,我们进行构建,
- 在 [3] 中,我们运行它。MySQL 数据库管理系统必须正在运行。如果 Tomcat 服务器尚未启动,它将随后启动 [4],并显示应用程序的首页 [5]:
![]() |
此时,我们看到的就是我们一直在研究的应用程序。我们留给读者自行验证其是否正常运行。现在,让我们停止该应用程序:
![]() |
- 在 [1] 中,我们卸载了该应用程序,
- 在 [2] 中,它已不复存在。
第 2 行和第 4 行表明应用程序关闭时出现故障。 第 4 行表明可能存在内存泄漏的风险。事实上,这种情况确实发生了,过了一会儿 NetBeans 就无法使用了。这个问题特别令人沮丧,因为每次运行项目时都必须重启 NetBeans。在文档《通过示例了解 Struts 2》[http://tahe.developpez.com/java/struts2] 中已经遇到过这个问题。
网上关于此错误的信息很多。当从 Tomcat 中反复加载和卸载应用程序时,就会发生这种情况。过了一段时间后,就会出现 java.lang.OutOfMemoryError: PermGen space 错误。如果错误源于第三方库(JAR),就像本例中一样,似乎没有办法预防。此时必须重启 Tomcat 才能解决。
不过,您可以延缓该错误的发生。首先,增加已溢出的内存空间。
![]() |
- 在 [1] 中,进入 Tomcat 服务器属性,
- 在 [2] 中,进入 [Platform] 选项卡,设置内存溢出阈值。这里我们将其设为 1 GB,因为我们总共拥有 8 GB 内存。如果内存较少,您可以将其设为 512 MB(512 兆字节)。
接下来,将 MySQL JDBC 驱动程序放置在 <tomcat>/lib 目录下,其中 <tomcat> 是 Tomcat 的安装目录。
![]() |
- 在 [1] 的 Tomcat 属性中,记下其安装目录 <tomcat>,
- 在 <tomcat>/lib [2] 目录中,放置一个最新的 MySQL JDBC 驱动程序 [3]。
接下来,移除项目对 MySQL JDBC 驱动程序的依赖 [4]。
![]() |
完成此操作后,请测试应用程序。你会发现可以反复加载和卸载应用程序。然而,内存泄漏问题并未解决,只是推迟到了稍后才出现。
4.4. 结论
我们将 JSF/EJB/GlassFish 应用程序迁移到了 JSF/Spring/Tomcat 环境中。迁移主要通过在两个项目之间进行复制粘贴来完成。之所以能够实现这一点,是因为 Spring 和 EJB3 具有许多相似之处。事实上,EJB3 的诞生正是因为 Spring 被证明比 EJB2 更高效,随后 EJB3 便融合了 Spring 的最佳特性。
4.5. 使用 Eclipse 进行测试
![]() |
- 在 [1] 中,导入三个 Spring 项目,
- 在 [2] 中,从 [DAO] 层选择 JUnit 测试,并在 [3] 中运行它,
![]() |
![]() |
- 在 [4] 中,测试通过,
- 在 [5] 中,控制台会输出日志。
![]() |
- 在 [6A] [6B] 中,运行 [业务] 层的控制台客户端,
- 在 [7] 中,显示生成的控制台输出,
![]() |
- 在[8]和[9]中,我们在Tomcat 7服务器[10]上运行了该Web项目,
![]() |
- 在[11]中,应用程序的首页在Eclipse的内置浏览器中显示。





































































