让我们分享基于Java的Web应用程序架构!
有很多不同的架构可以用Java实现Web应用程序。这个问题的答案可以作为各种Web应用程序设计的库,包括它们的优缺点。虽然我意识到答案会是主观的,但让我们尽可能客观,并激励我们列出的优缺点。
您可以使用您喜欢的详细级别来描述您的架构。为了使您的答案具有任何价值,您至少需要描述所描述的架构中使用的主要技术和思想。最后但并非最不重要的,何时应该使用您的架构?
我来开始...
架构概述
我们采用基于Sun的开放标准,如Java EE、Java Persistence API、Servlet和Java Server Pages的3层架构。
- 持久化层
- 业务层
- 表示层
层之间可能的通信流程如下所示:
Persistence <-> Business <-> Presentation
例如,这意味着演示层从不调用或执行持久性操作,它始终通过业务层进行操作。这种架构旨在满足高可用性Web应用程序的需求。
持久性
执行创建、读取、更新和删除(CRUD)持久性操作。在我们的情况下,我们使用(Java Persistence API)JPA,目前我们使用Hibernate作为持久性提供程序并使用其EntityManager。
此层分为多个类,每个类处理某种类型的实体(即与购物车相关的实体可能由单个持久性类处理),并且仅由一个管理器使用。
此外,此层还存储JPA实体,这些实体是诸如Account
、ShoppingCart
等的东西。
业务
所有与Web应用程序功能相关联的逻辑都位于此层中。该功能可以是为希望使用信用卡在线支付产品的客户启动资金转移。也可以是创建新用户、删除用户或计算基于Web的游戏中的战斗结果。
这个层被分成多个类,并且每个类都用@Stateless
进行注释,以成为一个无状态会话Bean(SLSB)。每个SLSB被称为一个管理器,例如,一个管理器可以是一个已经注释为AccountManager
的类。当
AccountManager
需要执行CRUD操作时,它会对一个持久化层中的类AccountManagerPersistence
进行适当的调用。 AccountManager
中两个方法的大致草图可能如下:...
public void makeExpiredAccountsInactive() {
AccountManagerPersistence amp = new AccountManagerPersistence(...)
// Calls persistence layer
List<Account> expiredAccounts = amp.getAllExpiredAccounts();
for(Account account : expiredAccounts) {
this.makeAccountInactive(account)
}
}
public void makeAccountInactive(Account account) {
AccountManagerPersistence amp = new AccountManagerPersistence(...)
account.deactivate();
amp.storeUpdatedAccount(account); // Calls persistence layer
}
我们使用容器管理事务,这样我们就不必自己进行事务标记。在幕后发生的基本情况是,在进入SLSB方法时启动事务,并在退出方法之前立即提交它(或回滚它)。这是约定优于配置的例子,但我们还没有需要除默认值Required之外的任何内容。
以下是Sun的Java EE 5教程如何解释企业级JavaBean(EJB)的Required事务属性:
如果客户端正在运行事务并调用企业bean的方法,则该方法在客户端的事务中执行。如果客户端未与事务相关联,则容器在运行方法之前启动新事务。
对于所有使用容器管理的事务划分运行的企业bean方法,Required属性是隐式的事务属性。通常情况下,您不需要设置Required属性,除非您需要覆盖另一个事务属性。因为事务属性是声明性的,所以您可以很容易地稍后更改它们。
演示
我们的展示层负责展示内容!它负责用户界面,并通过构建HTML页面和通过GET和POST请求接收用户输入向用户显示信息。我们目前正在使用旧的Servlet和Java Server Pages (JSP)组合。该层调用业务层中的管理器方法来执行用户请求的操作并接收要在Web页面中显示的信息。有时,从业务层接收到的信息是较简单的类型,如
String
和int
,而其他时候则是JPA entities。
架构的优缺点
优点
- 将与持久化方式相关的所有内容都放在此层中,意味着我们可以轻松地从使用JPA切换到其他方式,而无需在业务层中重新编写任何内容。
- 我们可以轻松地将演示层切换到其他方式,并且如果我们找到更好的东西,很可能会这样做。
- 让EJB容器管理事务边界很不错。
- 使用Servlet + JPA很容易(起步)并且这些技术在许多服务器上得到广泛使用和实现。
- 使用Java EE应该使我们更容易创建具有负载平衡和故障转移的高可用性系统。我们认为这两个方面都是必须的。
缺点
- 使用JPA,您可以使用
@NamedQuery
注解在JPA实体类上将经常使用的查询存储为命名查询。如果您将尽可能多的与持久性相关的内容放置于持久性类中(就像我们的架构一样),这将扩展您可以找到包括JPA实体的查询的位置。这将使持久化操作更难以概述,从而更难以维护。 - 我们的持久层中有JPA实体。但是
Account
和ShoppingCart
,它们不是真正的业务对象吗?之所以这样做,是因为您必须触及这些类并将它们转换为JPA知道如何处理的实体。 - JPA实体,也是我们的业务对象,像数据传输对象(DTO)一样创建,也称为值对象(VO)。这导致了贫血领域模型,因为业务对象除访问器方法外没有自己的逻辑。所有逻辑都由我们的业务层管理器执行,这导致了更多的过程式编程风格。这不是良好的面向对象设计,但也许这不是问题?(毕竟面向对象不是唯一已经取得了成果的编程范例。)
- 使用EJB和Java EE会引入一些复杂性。我们不能纯粹地使用Tomcat(添加EJB微容器并不算是纯粹的Tomcat)。
- 使用Servlet + JPA存在许多问题。使用Google以获得有关这些问题的更多信息。
- 由于事务在退出业务层时关闭,因此我们不能从呈现层中加载配置为从数据库在需要时加载的JPA实体中的任何信息(使用
fetch=FetchType.LAZY
)。它会触发异常。在返回包含这些类型字段的实体之前,我们必须确保调用相关的getter。另一个选项是使用Java Persistence查询语言(JPQL)并执行FETCH JOIN
。但这两个选项都有点繁琐。