2. 分层Java应用程序架构
Java应用程序通常被划分为多个层,每层都有明确的职责。让我们来看一种常见的架构——三层架构:
![]() |
- [1]层(此处称为[ui],即用户界面层)是通过Swing图形界面、控制台界面或Web界面与用户交互的层。 其作用是将来自用户的数据提供给 [2] 层,或者将 [2] 层提供的数据呈现给用户。
- [2] 层(此处称为 [metier])是应用所谓业务规则(c.a.d)的层。 应用程序的特定逻辑,无需关心所接收数据的来源,也无需关注所生成结果的去向。
- [3]层(此处称为[DAO],即数据访问对象)负责向[2]层提供预先存储的数据(文件、数据库等), ……),并记录 [2] 层提供的部分结果。
实现 [DAO] 层有多种方案。让我们探讨其中几种:
![]() |
上述 [JDBC] 层是 Java 中用于访问数据库的标准层。它将 [DAO] 层与管理数据库的 SGBD 层进行了隔离。 理论上,无需修改 [DAO] 层的代码,即可更换 SGBD。尽管具有这一优势,API 和 JDBC 仍存在某些缺点:
- SGBD上的所有操作都可能触发受控异常(checked)SQLException。这迫使调用方代码(此处为[DAO]层)必须使用try/catch进行包裹,从而导致代码变得相当臃肿。
- [DAO]层并非完全不受SGBD层的影响。例如,后者在自动生成主键值方面拥有专有方法,而[DAO]层无法忽略这些方法。 因此,在插入记录时:
- 在 Oracle 环境中,[DAO] 层必须先获取该记录的主键值,然后才能插入该记录。
- 而在 SQL Server 中,[DAO] 层插入记录时,SGBD 会自动为该记录分配主键值,并将该值传递给 [DAO] 层。
这些差异可通过使用存储过程来消除。在前面的示例中,[DAO]层将调用Oracle或SQL Server中的存储过程,该过程将考虑SGBD的特殊性。 这些差异将被 [DAO] 层隐藏。 然而,尽管更改 SGBD 并不意味着需要重写 [DAO] 层,但这仍然需要重写存储过程。这可能并不被视为不可逾越的障碍。
人们已付出诸多努力,试图将 [DAO] 层与 SGBD 的专有部分隔离。近年来,Hibernate 在这方面提供了一个非常成功的解决方案:
![]() |
[Hibernate]层位于开发者编写的[DAO]层与[JDBC]层之间。 Hibernate 是一种 ORM(对象关系映射器),它作为一座桥梁,连接了关系型数据库的世界与 Java 操作的对象世界。 [DAO]层的开发人员不再直接接触[JDBC]层,也不再直接操作其想要利用内容的数据库表。他所看到的只是数据库的对象映射,该映射由[Hibernate]层提供。 数据库表与 [DAO] 层所操作的对象之间的桥梁主要通过两种方式建立:
- 通过 XML 类型的配置文件
- 通过代码中的 Java 注解,该技术仅在 JDK 1.5 及更高版本中可用
[Hibernate] 层是一个抽象层,旨在实现尽可能高的透明度。 理想状态是,[DAO] 层的开发人员可以完全忽略自己正在与数据库打交道。如果配置关系型世界与对象世界之间桥梁的任务并非由他负责,那么这种理想状态是可行的。该桥梁的配置相当复杂,需要一定的经验积累。
对象层 [4](即 BD 的映射)被称为“持久化上下文”。 基于 Hibernate 的 [DAO] 层对持久化上下文中的对象执行持久化操作(CRUD,即创建、读取、更新、删除), 这些操作由Hibernate转换为SQL命令,并由JDBC层执行。对于数据库查询操作(即SQL Select), Hibernate 为开发者提供了一种 HQL 语言(Hibernate 查询语言),用于查询持久化上下文 [4],而非直接操作 BD 本身。
Hibernate 虽然广受欢迎,但掌握起来却颇具难度。人们常说它的学习曲线平缓,但实际上相当陡峭。一旦数据库中包含具有“一对多”或“多对多”关系的表,关系型数据库与对象之间的映射配置就绝非初学者所能轻易胜任。配置错误可能会导致应用程序性能不佳。
鉴于 ORM 系列产品的成功, Java的创建者Sun决定通过一项名为JPA(Java Persistence API)的规范,将ORM层标准化,该规范与Java 5同期发布。 JPA规范已被多种产品实现:Hibernate、Toplink、EclipseLink、OpenJpa等。随着JPA的推出,之前的架构变为如下:
![]() |
[DAO] 层现与 JPA 规范(一组接口)进行交互。 这为开发人员带来了标准化优势。此前,如果他修改了 ORM 层,还必须修改 [DAO] 层——该层原本是为与特定的 ORM 层交互而编写的。 现在,他将编写一个 [DAO] 层,该层将与 JPA 层进行交互。 无论由何种产品实现该层,JPA层向[DAO]层展示的接口保持不变。
在本文档中,我们将使用基于 JPA/Hibernate 或 JPA/EclipseLink 的 [DAO] 层。 此外,我们将使用 Spring 2.8 框架来将这些层相互关联。
![]() |
Spring 的主要优势在于它允许通过配置而非代码来连接各层。因此,如果 JPA / Hibernate 实现需要替换为不包含 JPA 的 Hibernate 实现,例如因为应用程序运行在 JDK 1.4 环境中,而该环境不支持 JPA, [DAO] 层的实现变更不会影响 [métier] 层的代码。只需修改将各层相互关联的 Spring 配置文件即可。
在 Java EE 5 中,还有另一种解决方案:使用 EJB3(企业级 Java Bean 3.0 版本)来实现 [metier] 和 [DAO] 层:
![]() |
我们将看到,该方案与使用 Spring 的方案并无太大差异。Java 环境可在所谓的应用服务器中使用,例如 Sun Application Server(Glassfish)、 JBoss Application Server、Oracle Container for Java(OC4J)等。应用服务器本质上即Web应用服务器。此外,还存在所谓的“独立”环境(EE 5), 这类环境可在应用服务器之外使用,例如 JBoss、EJB3 或 OpenEJB。
在 EE5 环境中,各层由名为 EJB(企业 Java Bean)的对象实现。 在 EE 的早期版本中,EJB(EJB 2.x)被认为难以实现、难以测试,有时性能也不佳。 EJB2.x“实体”与EJB2.x“会话”有所区别。 简而言之,EJB2.x“实体”是数据库表中某行数据的映射,而EJB2.x“会话”则是用于实现多层架构中[metier]、 [DAO] 层。 针对使用 EJB 实现的层所提出的主要批评之一是,它们仅可在 EJB 容器内使用,而该容器是由 EE 环境提供的服务。 该环境的部署比 SE(标准版)环境更为复杂,这可能会 使开发人员不愿频繁进行测试。 不过,也有一些Java开发环境通过自动将EJB部署到服务器上,从而简化了应用服务器的使用: Eclipse、NetBeans、JDeveloper、IntelliJ、IDEA。本文将使用 NetBeans 6.8 和 GlassFish v3 应用服务器。
Spring框架的诞生正是为了应对EJB2的复杂性。Spring在SE环境中提供了大量通常由EE环境提供的服务。 例如在“数据持久化”部分,Spring 提供了应用程序所需的连接池和事务管理器。Spring 的出现促进了单元测试文化的兴起,在 SE 环境中,单元测试的实施比在 EE 环境中更为简便。 Spring 允许通过经典的 Java 对象(POJO,即 Plain Old/Ordinary Java Object)来实现应用程序的各层,从而支持在其他场景中复用这些对象。 最后,它能够相当透明地集成许多第三方工具,特别是持久化工具,如 Hibernate、EclipseLink、Ibatis 等。
Java EE5 的设计旨在弥补 EJB2 规范中的不足。 EJB 和 2.x 已演变为 EJB3。 这些是带有注释标记的 POJOs,当它们位于 EJB3 容器内时,这些注释会使其成为特殊对象。 在此容器中,EJB3 将能够利用容器的服务(连接池、事务管理器等)。 在 EJB3 容器之外,EJB3 便成为一个普通的 Java 对象。其 EJB 注解将被忽略。
上文中,我们将 Spring 和 EJB3 容器作为多层架构的潜在基础设施(框架)进行了展示。正是这一基础设施将提供我们所需的服务:连接池和事务管理器。
- 在 Spring 框架下,各层将通过 POJOs 实现。这些 POJOs 将通过依赖注入访问 Spring 的服务(连接池、事务管理器):在构建这些 POJOs 时,Spring 会向其中注入所需服务的引用。
- 在 EJB3 容器中,各层将通过 EJB 实现。 使用 EJB3 实现的分层架构与使用 Spring 实例化的 POJO 实现的分层架构差异不大。两者之间存在许多相似之处。
- 最后,我们将展示一个多层Web应用程序的示例:
![]() |






