Skip to content

2. Java应用程序的分层架构

Java 应用程序通常被划分为多个层,每一层都有明确的职责。让我们来看一种常见的架构——三层架构:

  • 第 [1] 层,此处称为 [ui](用户界面),是通过 Swing 图形用户界面、控制台界面或 Web 界面与用户交互的层。其作用是将用户提供的数据传递给第 [2] 层,或将第 [2] 层提供的数据呈现给用户。
  • 第 [2] 层,此处称为 [business],是应用所谓业务规则(即应用程序的特定逻辑)的层,而不关心其接收的数据来自何处,也不关心其生成的结果将去向何方。
  • 第[3]层,此处称为[DAO](数据访问对象),是向第[2]层提供预存数据(文件、数据库等)并存储第[2]层提供的一部分结果的层。

实现 [DAO] 层的方法多种多样。让我们来探讨其中几种:

上文提到的 [JDBC] 层是 Java 中用于访问数据库的标准层。它将 [DAO] 层与管理数据库的 DBMS 进行了隔离。理论上,无需修改 [DAO] 层的代码即可切换 DBMS。尽管具有这一优势,但 JDBC API 仍存在某些缺点:

  • 对 DBMS 的所有操作都可能抛出受检查的 SQLException。这迫使调用代码(本例中即 [DAO] 层)必须将其包裹在 try/catch 块中,从而导致代码变得相当冗长。
  • [DAO]层并非完全独立于DBMS。例如,DBMS拥有用于自动生成主键值的专有方法,[DAO]层无法忽略这些方法。因此,在插入记录时:
    • 在 Oracle 中,[DAO] 层必须先获取该记录的主键值,然后才能插入记录。
    • 对于 SQL Server,[DAO] 层插入记录后,DBMS 会自动为其分配主键值,并将该值返回给 [DAO] 层。

通过使用存储过程可以消除这些差异。在前面的示例中,[DAO]层将调用Oracle或SQL Server中的存储过程,该过程会处理DBMS的特定特性。这些细节将被隐藏在[DAO]层之外。尽管如此,虽然更换DBMS无需重写[DAO]层,但仍需重写存储过程。这可能并不被视为致命缺陷。

人们已做出诸多努力,试图将 [DAO] 层与 DBMS 的专有特性隔离。近年来,Hibernate 便是这一领域中非常成功的解决方案之一:

[Hibernate] 层位于开发人员编写的 [DAO] 层与 [JDBC] 层之间。Hibernate 是一种 ORM(对象关系映射器),它作为一种工具,将数据库的关系型世界与 Java 操作的对象世界连接起来。 [DAO]层的开发者不再直接接触[JDBC]层或其希望利用的数据库表。他们仅看到由[Hibernate]层提供的数据库对象表示形式。数据库表与[DAO]层操作的对象之间的桥梁主要通过两种方式建立:

  • 通过 XML 风格的配置文件
  • 通过代码中的 Java 注解,该技术自 JDK 1.5 起才可用

[Hibernate] 层是一个旨在尽可能透明的抽象层。理想情况下,[DAO] 层开发者应完全无需意识到自己正在操作数据库。如果他们并非负责编写连接关系型与面向对象世界的配置文件,这一目标便可实现。配置这一桥梁相当精细,需要一定的经验。

与数据库相对应的 [4] 对象层被称为“持久化上下文”。 基于 Hibernate 的 [DAO] 层会对持久化上下文中的对象执行持久化操作(CRUD:创建、读取、更新、删除);这些操作会被 Hibernate 转换为由 JDBC 层执行的 SQL 语句。对于数据库查询操作(SQL SELECT),Hibernate 为开发者提供了 HQL(Hibernate 查询语言),用于查询持久化上下文 [4] 而非数据库本身。

Hibernate 虽广受欢迎,但掌握起来相当复杂。其学习曲线常被描述为平缓,实际上却相当陡峭。一旦数据库中出现包含一对多或多对多关系的表,配置关系型/对象桥梁便超出了普通初学者的能力范围。配置错误可能导致应用程序性能不佳。

鉴于 ORM 产品的成功,Java 的创建者 Sun 决定通过名为 JPA(Java Persistence API)的规范来标准化 ORM 层,该规范随 Java 5 一起发布。JPA 规范已被多种产品实现:HibernateTopLink、EclipseLink、OpenJPA 等。借助 JPA,之前的架构变为如下所示:

[DAO] 层现在与 JPA 规范(一组接口)进行交互。 开发人员在标准化方面获益匪浅。此前,如果开发人员更换了 ORM 层,他们还必须修改 [DAO] 层,因为该层原本是为与特定的 ORM 进行交互而编写的。现在,他们将编写一个与 JPA 层交互的 [DAO] 层。无论哪个产品实现了 JPA 层,向 [DAO] 层展示的接口都保持不变。

在本文档中,我们将使用构建在 JPA/Hibernate 或 JPA/EclipseLink 层之上的 [DAO] 层。同时,我们将使用 Spring 2.8 框架将这些层连接起来。

Spring 的主要优势在于,它允许您通过配置而非代码来连接各层。因此,如果需要将 JPA/Hibernate 实现替换为不带 JPA 的 Hibernate 实现——例如,因为应用程序运行在不支持 JPA 的 JDK 1.4 环境中——[DAO] 层实现的这一变更不会对 [业务] 层的代码产生任何影响。 只需修改将各层连接在一起的 Spring 配置文件即可。

在 Java EE 5 中,还有另一种解决方案:使用 EJB3(企业级 JavaBeans 第 3 版)来实现 [业务] 和 [DAO] 层:

我们将看到,该方案与使用 Spring 的方案并无太大差异。Java EE 5 环境可在所谓的应用服务器中使用,例如 Sun Application Server 9.x(Glassfish)、JBoss Application Server、Oracle Container for Java(OC4J)等。应用服务器本质上就是 Web 应用服务器。 此外,还有所谓的“独立”EE 5环境,即可以在应用服务器之外使用的环境。例如JBoss EJB3或OpenEJB便是如此。

在 EE 5 环境中,各层由称为 EJB(企业级 Java Bean)的对象实现。在 EE 的早期版本中,EJB(EJB 2.x)以难以实现和测试著称,且有时性能欠佳。EJB 2.x 通常分为“实体”Bean 和“会话”Bean 两类。 简而言之,EJB 2.x“实体”对应于数据库表中的一行,而EJB 2.x“会话”则是用于实现多层架构中[业务逻辑]和[DAO]层的对象。针对使用EJB实现的分层架构的主要批评之一是,它们只能在EJB容器内使用,而EJB容器是EE(企业版)环境提供的一项服务。 该环境的配置比 SE(标准版)环境更为复杂,这可能会阻碍开发人员频繁进行测试。尽管如此,仍有一些 Java 开发环境通过自动化将 EJB 部署到应用服务器,从而简化了应用服务器的使用:Eclipse、NetBeans、JDeveloper、IntelliJ IDEA。在此,我们将使用 NetBeans 6.8GlassFish v3 应用服务器。

Spring框架的诞生正是为了应对EJB2的复杂性。在SE环境中,Spring提供了大量通常由EE环境提供的服务。例如,在“数据持久化”部分,Spring提供了应用程序所需的连接池和事务管理器。Spring的出现促进了单元测试文化的兴起,而在SE环境中,单元测试的实现比在EE环境中更为简便。 Spring 允许使用标准 Java 对象(POJO,即普通 Java 对象)来实现应用层,从而使其能够在其他场景中被复用。最后,它能够相当透明地集成众多第三方工具,尤其是持久化工具,如 Hibernate、EclipseLink、iBatis 等……

Java EE 5 的设计旨在解决 EJB 2 规范的不足。EJB 2.x 已演进为 EJB 3。这些是带有注解的 POJO,当它们位于 EJB 3 容器内时,这些注解会使其成为特殊对象。在容器内,EJB 3 可以利用容器提供的服务(连接池、事务管理器等)。 在 EJB 3 容器之外,EJB 3 便成为一个标准的 Java 对象。其 EJB 注解将被忽略。

上文中,我们已将 Spring 和 EJB3 容器描绘为多层架构的一种可能的基础架构(框架)。正是这一基础架构将提供我们所需的服务:连接池和事务管理器。

  • 在 Spring 中,各层将通过 POJO 实现。这些 POJO 将通过依赖注入访问 Spring 的服务(连接池、事务管理器):在构建它们时,Spring 会注入它们所需服务的引用。
  • 在 EJB3 容器中,各层将通过 EJB 实现。基于 EJB3 实现的分层架构与基于 Spring 实例化的 POJO 实现的架构并无太大差异。我们会发现两者存在许多相似之处。
  • 最后,我们将展示一个多层Web应用程序的示例: