Java 企业 101
JEE 堆栈
应用程序容器
JEE框架
过滤链
表示层
服务层
存储库层
数据模型
JEE 中的线程
结束语
开发软件的方法有很多种。事实上,开发优秀软件的方法也有很多。说到应用服务器开发,其中一种经受住了时间的考验,而且理由充分:Java 企业版。
Java EE 不仅仅是一个软件库。它更是一种架构和一种哲学。JEE 并不适合胆小的人;如果你只是在构建一个一次性的原型,那你就错了。但如果你来这里是为了了解一个能够承载大型企业并支持大规模应用程序的架构,那么你走对了路。JEE 是软件开发的重炮。而且它真的非常棒。
本文将探讨 JEE 的架构层面。后续将发布一篇关于其实现的文章。
补充一点。本文中我提到的 Java Enterprise 并非特指 Java 库,而是指 Java 架构和 Java 哲学。官方 Java EE 库只是实现该技术栈的一种方式。
JEE 堆栈
那么,你准备好进行严肃的软件开发了吗?抛开那些无类型语言、花哨的脚本、炒作驱动的开发和时髦的技术,让我们认真起来,编写能够在未来 20 多年运行的软件。Java EE 架构的概要如下:
好吧,当然,你需要一个概括性的概括。让我们先从一些初步的观察开始:
- 与外界的每一次通信都是严格基于请求-响应的。
- 传入的请求在到达应用程序代码之前会经过多个层级。每一层都可以拒绝、重定向或更改请求。这些层级背后的动机是关注点分离。
- 许多层已经实现,您只需要利用它们。
- JEE 的宗旨是让开发人员只关注业务功能。其余 99% 的事情都已为您处理好了。
应用程序容器
当请求通过网络到达我们的服务器时,它首先会被传递给操作系统。操作系统会根据请求的端口,决定将请求转发到哪个应用程序。在本例中,应用程序就是Java 虚拟机。JVM 内部运行一个应用程序容器(例如Apache Tomcat或Glassfish)。应用程序容器实现了Java Servlet API。应用程序容器有以下几项职责:
- Tomcat 管理一个或多个以servlet形式交付的应用程序。实际上,大多数容器只包含一个 servlet,但理论上一个 Tomcat 可以包含任意多个 servlet。
- 它提供了 servlet API 的实现。这使得容器内的应用程序能够与容器通信。该特性最突出的用途是建立过滤器链(稍后会详细介绍)。
- 它提供与操作系统服务API 的集成。这样,容器内运行的应用程序可以作为操作系统级别的服务启动、终止和重启。因此,即使 Java 支持多平台,许多应用程序容器也包含特定于平台的代码(因此容器内的应用程序仍然与平台无关)。
- 它根据路径映射将传入的请求重定向到正确的应用程序。经常会看到一个注册的 servlet 用于静态内容(绑定到/static),另一个注册的 servlet 用于应用服务器的动态 API(绑定到/api)。
- 它管理请求的线程池,并将每个请求绑定到一个线程。由于线程保存着请求上下文,因此不建议在 JEE 环境中手动启动新线程(除非您确切知道自己在做什么)。
传统上,Java EE 应用程序是通过归档文件(称为WAR文件(用于Web应用)或EAR文件(用于企业归档))部署的。这些归档文件的内部文件结构是标准化的。应用程序容器在启动时会提取其中包含的文件,并启动其中的 servlet。在此过程中,容器会将 servlet 绑定到指定的端口(在代码或配置文件中指定)。
JEE框架
通常,在 JEE 架构下工作时,你不会从头开始实现所有功能。许多任务在不同的 JEE 应用程序中完全相同,因此使用合适的框架非常有意义。主要有实际的 JEE 参考实现和 Spring 框架。由于我到目前为止只使用了 Spring 框架,因此我无法谈论“原生”JEE。我们将在下一篇文章中更详细地讨论它。
过滤链
每个传入请求在交给应用程序之前都必须经过一系列所谓的Servlet 过滤器,这些过滤器构成了过滤器链。请求通过第一个过滤器后,第二个过滤器就会开始执行,依此类推。每个过滤器都可以选择阻止请求。应用程序容器允许通过 Servlet API 自定义过滤器链。JEE 框架实现使用过滤器链执行许多任务,包括会话管理和安全。过滤器也可能有副作用;如果您想每个请求都执行一项任务,那么您经常会看到以 servlet 过滤器形式出现的实现。此外,如果您需要将某些信息绑定到请求本身,那么 servlet 过滤器是执行此操作的常用场所。
表示层
表示层是您的实际应用程序代码首次接收传入请求的地方。此请求已通过 Servlet 过滤器链,因此用户会话已建立并准备就绪,并且所有身份验证都已完成。在 JEE 的早期阶段,表示层是服务器端生成 HTML 页面的地方。如今,表示层由一系列 REST 控制器组成,这些控制器提供构成 REST API 的各种端点。如果您面对的是较旧的应用程序,您还会在表示层遇到 XML Web 服务。表示层中常见的操作是服务器端验证用户输入和常规请求。正如您永远不应在 GUI 代码中编写 SQL 查询一样,表示层也不能尝试直接访问数据库。表示层中的类只能与另一个表示层类、服务层类或服务层返回的数据模型元素进行通信。
服务层
服务层是您实际的应用程序代码所在。在这里,您可以将业务规则写入代码。在服务层,您可以在数据模型中移动数据、创建新元素、删除旧元素等等。根据您的用例,服务层可能小到“将此调用转发到存储库层”,也可能是一个极其复杂的过程。服务层的类可能只与其他服务或存储库层的类通信。
存储库层
这是代码中修改数据的最后一层,在数据到达数据库之前。此层的主要元素是存储库(也称为数据访问对象,或DAO*)。这些类仅提供了一些方法,允许您在数据库中*持久化、加载、删除和查询数据。这里重要的是,您绝不能让数据存储的任何细节脱离存储库层——其主要目的是确保您可以将数据存储与其他数据存储(甚至可能是带有 NoSQL 存储的 SQL 数据库!)进行交换。在内部,您的存储库方法将包含实际的查询语句。如果您使用的是标准 JEE 堆栈,那么您将拥有一个Java 持久性 API (JPA) 提供程序,例如 Hibernate。JPA 允许您相对轻松地将域模型转换为 SQL 表并转换回来。它仍然存在许多缺陷,值得专门写一篇文章来讨论。您可能已经猜到了,存储库层类不会调用其自身层之外的任何其他类(JPA 类除外)。
数据模型
数据模型表示域中的数据。它是应用程序所有三个层都将使用的唯一架构元素。因此,至关重要的是,域模型类不能引用任何其他类,除了驻留在域模型本身中的类。与表示层、服务层和持久层类相比,域模型是有状态的。通常,您不会希望在域模型中包含大量逻辑;它主要用于保存数据并提供干净的 API,实际的复杂修改是在业务层完成的。域模型虽然在 JEE 中没有明确要求,但它几乎总是遵循 Java Bean 模式。如果您想利用标准框架轻松处理域模型(例如 Bean Validation 和 JPA(稍后会详细介绍)),那么适当的 getter 和 setter 在这里是没有商量的余地的。域模型元素是典型的 POJO - 私有字段、构造函数以及 getter 和 setter。通常,像 JPA、Jackson 和 JAXB 这样的框架还会强制你为每个类提供一个默认构造函数,因为这些类需要通过 Java 反射进行实例化。与 JEE 架构中几乎所有其他类不同,对于领域模型 POJO 来说,拥有一个清晰的equals()
和实现至关重要hashCode()
。通常,每个领域模型元素都有一个唯一的 ID 用于此目的,该 ID 也与其在数据库表中的 ID 一致。
JEE 中的线程
请求始终绑定到JEE 中的线程,该线程由应用程序容器(通常在线程池中)实例化和管理。这意味着 JEE 服务器应用程序本质上始终是并发的,这是无法避免的。众所周知,正确处理并发很难。值得庆幸的是,JEE 架构在并发方面已经为您解决了问题。如果您查看上图,您会看到四个用户并行使用该应用程序,每个用户都由绑定到线程的请求/响应表示。有一个特别值得注意的细节:线程永不相交。应用程序不执行同步,而是将其留给擅长执行同步的组件:数据库。
这怎么可能呢?我们怎样才能在不考虑多线程的情况下将所有这些层都置于数据库之上?回想一下并发成为问题的时候:当多个线程访问相同的数据时。您希望在 JEE 应用程序中不惜一切代价避免这种情况(存在例外,例如应用程序级缓存)。为此,属于存储库层和服务层的所有类在 JEE 中都是无状态的。它们没有保存可变状态的字段,无论是私有的还是公共的。那么数据怎么办?数据是按用户和按请求加载的。当请求到达服务层(表示层在这里有点例外)时,将打开一个新的数据库事务供该用户独占使用。然后,服务收集请求的数据和/或执行请求的修改,所有这些都在这个事务内进行。在结果传递到表示层之前,事务被提交并关闭。
这种架构有两大优点:
- 服务器是无状态的,这是一个很好的特性,例如用于测试。它有助于保持业务逻辑非常简单,并且与更具函数式编程风格兼容。
- 并发修改唯一会遇到的地方是数据库,但它们是专门为处理这个问题而设计的。
当然,代价是每个线程都要构建自己的数据模型(部分)视图。因此,如果两个用户请求同一条数据,它将在内存中保存两次。
结束语
关于 JEE,还有很多话要说。我常常觉得它遭受了很多不应有的批评,仅仅是因为它被误解了。它与现代编程风格和语言完美兼容,并且有助于构建非常稳定的应用程序。从某种程度上来说,JEE 的重点并非它为程序员提供了什么,而是它如何保护你免受并发问题、数据完整性问题等等的影响。在这方面,JEE 架构是防御性编程的典范——它始终将安全放在首位。该架构已被证明非常适合大型项目和团队。
在下一篇文章中,我们将通过一个具体的例子仔细研究这个架构的实际实现——我们只需要编写比你想象的少得多的代码就可以实现这一切。
文章来源:https://dev.to/martinhaeusler/java-enterprise-101-3djl