提交 4ce6c446 编写于 作者: W wizardforcel

2021-05-07 23:21:13

上级 c75e1ec0
......@@ -18,7 +18,7 @@
在我们这边,正如我们想象的例子,我们最近用 microservice 架构替换了我们的单片应用程序,尽管系统中仍然有一些基于 SOAP 的解决方案,但是大多数后端模块使用 HTTPS 和 REST 协议进行通信。一些模块仍然依赖于每天使用 FTP 进行的异步文件传输,FTP 是从 Unix 作业开始的。总账系统是用 COBOL 语言编写的。幸运的是,我们不需要对付这些恐龙。
这个结构是一个虚构的设置,但一个现实的。我编写并描述这些部分是为了让您了解如何在大型企业中看到混合技术。我在这里描述的是一个非常简单的设置。有些公司的系统中有一千多个软件模块,使用不同的技术和完全不同的接口,所有这些模块都相互连接。这并不是因为他们喜欢这种混乱,而是因为经过 30 年的持续 it 发展,这种混乱才变得如此。新技术来了,旧技术也消失了。业务发生了变化,如果你想保持竞争力,就不能固守旧技术。同时,您无法立即替换整个基础结构。其结果是,我们看到相当老的技术仍然在运行,而且主要是新技术。旧技术得到及时推广。它们不会永远呆在这里,而且,当恐龙出现在我们面前时,我们有时会感到惊讶。
这个结构是一个虚构的设置,但一个现实的。我编写并描述这些部分是为了让您了解如何在大型企业中看到混合技术。我在这里描述的是一个非常简单的设置。有些公司的系统中有一千多个软件模块,使用不同的技术和完全不同的接口,所有这些模块都相互连接。这并不是因为他们喜欢这种混乱,而是因为经过 30 年的持续发展,这种混乱才变得如此。新技术来了,旧技术也消失了。业务发生了变化,如果你想保持竞争力,就不能固守旧技术。同时,您无法立即替换整个基础结构。其结果是,我们看到相当老的技术仍然在运行,而且主要是新技术。旧技术得到及时推广。它们不会永远呆在这里,而且,当恐龙出现在我们面前时,我们有时会感到惊讶。
我们必须处理我们将要开发的两个前端组件。具体如下:
......@@ -49,9 +49,9 @@
微服务架构并不是所有架构中的圣杯。它对单片架构的一些问题给出了不同的答案,在大多数情况下,这些答案在使用现代工具时效果更好。这些应用程序还需要测试和调试。性能必须得到管理,错误和问题必须得到解决。不同之处在于,各个组件之间没有强耦合,这样,开发、部署和测试就可以沿着不同的技术进行分离。由于微服务架构在实践中沿着网络协议将模块分开,调试可能需要更多与网络相关的工作。这可能是好的,也可能是坏的,或者两者兼而有之。然而,对于开发商来说,优势是显而易见的。他们可以独立地在较小的单元上工作,并且可以更快地看到工作的结果。
在开发单片应用程序的单个模块时,只有在部署整个应用程序时才能看到结果。在大型应用程序的情况下,这可能很少见。在开发 monolithics 的大型公司中,一个典型的部署周期是每隔几个月,比如说三个月,但是一年只发布两次甚至一次的情况并不少见。开发微服务时,只要新模块没有破坏它提供给我们的网络接口和其他模块使用的网络接口,只要它准备好并经过测试,就可以部署它。
在开发单片应用程序的单个模块时,只有在部署整个应用程序时才能看到结果。在大型应用程序的情况下,这可能很少见。在开发单片电路的大型公司中,一个典型的部署周期是每隔几个月,比如说三个月,但是一年只发布两次甚至一次的情况并不少见。开发微服务时,只要新模块没有破坏它提供给我们的网络接口和其他模块使用的网络接口,只要它准备好并经过测试,就可以部署它。
如果你想阅读更多关于微服务的文章,第一个也是最真实的来源是 [martinfowler 的文章](http://www.martinfowler.com/articles/microservices.html)。请注意,此页面引用了 Microservices 资源指南,其中列出了许多 Microservices 信息资源。
如果你想阅读更多关于微服务的文章,第一个也是最真实的来源是 [martinfowler 的文章](http://www.martinfowler.com/articles/microservices.html)。请注意,此页面引用了微服务资源指南,其中列出了许多微服务信息资源。
# 服务接口设计
......@@ -74,7 +74,7 @@ ProductInformation byId(ProductId id);
OrderId placeOrder(Order order);
```
我们通过 Web 服务接口在应用程序中提供这些函数;更具体地说,REST 使用 JSON。我们将更详细地讨论这些技术,以及 Spring 框架和 Model-View-Controller 设计模式,但首先,让我们看看产品信息控制器,以了解我们的程序将是什么样子:
我们通过 Web 服务接口在应用程序中提供这些函数;更具体地说,REST 使用 JSON。我们将更详细地讨论这些技术,以及 Spring 框架和模型-视图-控制器设计模式,但首先,让我们看看产品信息控制器,以了解我们的程序将是什么样子:
```java
package packt.java11.mybusiness.productinformation;
......@@ -133,13 +133,13 @@ public class ProductInformationController {
# 休息
**REST** 协议没有确切的定义。它代表**表述性状态转移**,对于一个从未听说过它的人来说,这可能并不意味着什么。当我们编写 restAPI 时,我们使用 HTTP(S)协议。我们向服务器发送简单的请求,然后得到我们编写的简单答案。这样,Web 服务器的客户端也是一个程序(顺便说一下,浏览器也是一个程序),它使用来自服务器的响应。因此,响应的格式不是使用 CSS 的 HTML 格式,也不是通过 **JavaScript** 的客户端函数来丰富的,而是一些数据描述格式,比如 JSON。REST 没有对实际的格式设置限制,但是现在,JSON 是使用最广泛的格式。
**REST** 协议没有确切的定义。它代表**表述性状态转移**,对于一个从未听说过它的人来说,这可能并不意味着什么。当我们编写 RestAPI 时,我们使用 HTTP(S)协议。我们向服务器发送简单的请求,然后得到我们编写的简单答案。这样,Web 服务器的客户端也是一个程序(顺便说一下,浏览器也是一个程序),它使用来自服务器的响应。因此,响应的格式不是使用 CSS 的 HTML 格式,也不是通过 **JavaScript** 的客户端函数来丰富的,而是一些数据描述格式,比如 JSON。REST 没有对实际的格式设置限制,但是现在,JSON 是使用最广泛的格式。
描述 REST 的 wiki 页面位于[这个页面](https://en.wikipedia.org/wiki/Representational_state_transfer)
REST 接口通常很简单。HTTP 请求几乎总是使用`GET`方法。它还使 REST 服务的测试变得简单,因为没有什么比从浏览器发出一个`GET`请求更容易的了。幼儿能做到。`POST`只有当服务在服务器上执行某些事务或更改时,才使用请求,这样,请求是向服务器发送数据,而不是获取一些数据。
在我们的应用程序中,我们将使用`GET`方法来查询产品列表并获取有关产品的信息,并且我们将只使用`POST`来订购产品。为这些请求提供服务的应用程序将在 Servlet 容器中运行。您已经学习了如何在不使用框架的情况下创建裸 Servlet。在本章中,我们将使用 Spring 框架,它从开发人员那里卸载了许多任务。Servlet 编程中有许多程序构造在大多数情况下都是相同的。它们被称为样板代码。Spring 框架使用 Model-View-Controller 设计模式来开发 Web 应用程序;因此,在讨论 Spring 之前,我们将对其进行简要介绍。
在我们的应用程序中,我们将使用`GET`方法来查询产品列表并获取有关产品的信息,并且我们将只使用`POST`来订购产品。为这些请求提供服务的应用程序将在 Servlet 容器中运行。您已经学习了如何在不使用框架的情况下创建裸 Servlet。在本章中,我们将使用 Spring 框架,它从开发人员那里卸载了许多任务。Servlet 编程中有许多程序构造在大多数情况下都是相同的。它们被称为样板代码。Spring 框架使用模型-视图-控制器设计模式来开发 Web 应用程序;因此,在讨论 Spring 之前,我们将对其进行简要介绍。
# 模型视图控制器
......@@ -157,11 +157,11 @@ MVC 是一种广泛使用的设计模式,它直接由 Spring 支持。当您
另一方面,这正是我们喜欢做的,关注业务代码,避免框架提供的所有样板文件。
既然我们知道了什么是 **JSON****REST**,以及通用的 Model-View-Controller 设计模式,那么让我们看看 Spring 是如何管理它们的,以及如何将这些技术付诸实施。
既然我们知道了什么是 **JSON****REST**,以及通用的模型-视图-控制器设计模式,那么让我们看看 Spring 是如何管理它们的,以及如何将这些技术付诸实施。
# 弹簧骨架
Spring 框架是一个包含多个模块的巨大框架。该框架的第一个版本是在 2003 年发布的,从那时起,已经有四个主要版本提供了新的和增强的特性。目前,Spring 是实际使用的企业框架,可能比法律标准 ejb3.0 更广泛。
Spring 框架是一个包含多个模块的巨大框架。该框架的第一个版本是在 2003 年发布的,从那时起,已经有四个主要版本提供了新的和增强的特性。目前,Spring 是实际使用的企业框架,可能比法律标准 EJB3.0 更广泛。
Spring 支持依赖注入、**面向切面编程****AOP**)、对 **SQL****NoSQL** 数据库的持久化等传统方式和对象关系映射方式。它具有事务支持、消息传递、Web 编程和许多其他特性。您可以使用 **XML** 配置文件、注释或 Java 类来配置它。
......@@ -175,17 +175,17 @@ Spring 支持依赖注入、**面向切面编程**(**AOP**)、对 **SQL**
Spring 自第一次发布以来一直在不断发展,它仍然被认为是一个现代框架。框架的核心是一个依赖注入容器,类似于我们在前面一章中看到的容器。随着框架的发展,它还支持 AOP 和许多其他企业功能,例如面向消息的模式和通过模型视图控制器实现的 Web 编程,不仅支持 Servlet,还支持 Portlet 和 WebSocket。由于 Spring 针对企业应用程序领域,因此它还支持以多种不同的方式处理数据库。支持 JDBC 使用模板、**对象关系映射****ORM**),以及事务管理。
在这个示例程序中,我们将使用一个相当新的模块 Spring boot。这个模块使得编写和运行应用程序非常容易,假设许多程序的配置通常是相同的。它包含一个嵌入的 Servlet 容器,它为默认设置进行配置,并在可能的情况下配置 Spring,以便我们可以关注编程方面,而不是 Spring 配置。
在这个示例程序中,我们将使用一个相当新的模块 SpringBoot。这个模块使得编写和运行应用程序非常容易,假设许多程序的配置通常是相同的。它包含一个嵌入的 Servlet 容器,它为默认设置进行配置,并在可能的情况下配置 Spring,以便我们可以关注编程方面,而不是 Spring 配置。
# 弹簧芯
核心模块的中心元素是上下文。当 Spring 应用程序启动时,容器需要一个上下文,容器可以在其中创建不同的 bean。这对于任何依赖注入容器来说都是非常普遍和正确的。如果我们以编程方式创建两个不同的上下文,它们可能在同一个 JVM 中彼此独立地存在。如果有一个 bean 被声明为 singleton,因此它应该只有一个实例,那么当我们需要它时,容器将为上下文创建一个实例。表示上下文的对象引用了我们已经创建的对象。但是,如果有多个上下文,他们将不知道 JVM 中有另一个已经有实例的上下文,容器将为另一个上下文创建一个新的 singletonbean 实例。
核心模块的中心元素是上下文。当 Spring 应用程序启动时,容器需要一个上下文,容器可以在其中创建不同的 bean。这对于任何依赖注入容器来说都是非常普遍和正确的。如果我们以编程方式创建两个不同的上下文,它们可能在同一个 JVM 中彼此独立地存在。如果有一个 bean 被声明为单例,因此它应该只有一个实例,那么当我们需要它时,容器将为上下文创建一个实例。表示上下文的对象引用了我们已经创建的对象。但是,如果有多个上下文,他们将不知道 JVM 中有另一个已经有实例的上下文,容器将为另一个上下文创建一个新的单例 bean 实例。
通常,我们不会在一个程序中使用多个上下文,但是在一个 JVM 中存在多个上下文的例子有很多。当不同的 Servlet 运行在同一个 Servlet 容器中时,它们运行在同一个 JVM 中,由类加载器分隔,并且它们可以各自使用 Spring。在这种情况下,上下文将属于 Servlet,并且每个 Servlet 都有一个新的上下文。
在上一章中,我们使用了 Guice。Spring 上下文类似于 Guice 注入器。在上一章中,我有点作弊,因为我正在编程 Guice 为每个请求创建一个新的注入器。这远不是最佳的,Guice 提供了一个可以处理 Servlet 环境的注入器实现。作弊的原因是我想把更多的精力放在 DI 架构的基础上,我不想通过引入一个复杂的(更复杂的)注入器实现来使代码复杂化。
Spring 上下文行为由接口`ApplicationContext`定义。这个接口有两个扩展和许多实现。`ConfigurableApplicationContext`扩展`ApplicationContext`,定义 setter`ConfigurableWebApplicationContext`定义 Web 环境中需要的方法。当我们编写 Web 应用程序时,通常不需要直接干扰上下文。该框架以编程方式配置 Servlet 容器,它包含用于创建上下文和调用方法的 Servlet。这是为我们创建的所有样板代码。
Spring 上下文行为由接口`ApplicationContext`定义。这个接口有两个扩展和许多实现。`ConfigurableApplicationContext`扩展`ApplicationContext`,定义设置器`ConfigurableWebApplicationContext`定义 Web 环境中需要的方法。当我们编写 Web 应用程序时,通常不需要直接干扰上下文。该框架以编程方式配置 Servlet 容器,它包含用于创建上下文和调用方法的 Servlet。这是为我们创建的所有样板代码。
上下文跟踪已创建的 bean,但不创建它们。要创建 bean,我们需要 bean 工厂或至少一个工厂。Spring 中的 bean 工厂是实现接口`BeanFactory`的类。这是 Spring 中 bean 工厂类型层次结构的最顶层接口。bean 只是一个对象,所以 bean 工厂只是创建一个类的新实例。但是,它还必须将这个新对象注册到上下文中,bean 还应该有一个名称,即`String`。这样,程序和其中的 Spring 就可以通过名称引用 bean。
......@@ -203,15 +203,15 @@ Spring 上下文行为由接口`ApplicationContext`定义。这个接口有两
为了表示类可以用作 bean,并可能提供名称,我们可以使用`@Component`注释。我们不需要提供名称作为参数。在这种情况下,名称将是一个空字符串,但是如果我们不引用它,为什么还要有一个名称呢?Spring 扫描类路径上的所有类并识别已注释的类,它知道这些类是用于 bean 创建的候选类。当一个组件需要注入另一个 bean 时,可以使用`@Autowired``@Inject`对该字段进行注释。`@Autowired`注释是弹簧注释,在`@Inject`注释标准化之前就已经存在。如果要在 Spring 容器之外使用代码,建议使用标准注释。在功能上,它们是等价的。
在我们的代码中,当 Spring 创建一个`ProductInformationController`组件的实例时,它似乎需要一个`ProductLookup`的实例。这是一个接口,因此,Spring 开始寻找实现这个接口的类,然后创建它的一个实例,可能首先创建其他 bean,然后容器注入它,设置字段。您可以决定注释字段的 setter 而不是字段本身。在这种情况下,Spring 将调用 setter,即使 setter 是`private`。可以通过构造器参数注入依赖项。setter、字段注入和构造器注入之间的主要区别在于,在使用构造器注入的情况下,不能创建没有依赖关系的 bean。当 bean 被实例化时,它应该并且将要注入所有其他 bean,以便它依赖于使用构造器注入。同时,需要通过 setter 注入或直接注入到字段中的依赖项可以稍后由容器在实例化类和准备 bean 之间的某个时间实例化。
在我们的代码中,当 Spring 创建一个`ProductInformationController`组件的实例时,它似乎需要一个`ProductLookup`的实例。这是一个接口,因此,Spring 开始寻找实现这个接口的类,然后创建它的一个实例,可能首先创建其他 bean,然后容器注入它,设置字段。您可以决定注释字段的设置器而不是字段本身。在这种情况下,Spring 将调用设置器,即使设置器是`private`。可以通过构造器参数注入依赖项。设置器、字段注入和构造器注入之间的主要区别在于,在使用构造器注入的情况下,不能创建没有依赖关系的 bean。当 bean 被实例化时,它应该并且将要注入所有其他 bean,以便它依赖于使用构造器注入。同时,需要通过设置器注入或直接注入到字段中的依赖项可以稍后由容器在实例化类和准备 bean 之间的某个时间实例化。
在构造器代码变得比简单的依赖项设置更复杂之前,或者在依赖项变得更复杂之前,这种细微的差异可能看起来并不有趣或重要。对于复杂的构造器,代码应该注意对象没有完全创建。这通常适用于任何构造器代码,但对于依赖项注入容器创建的 bean,通过直接字段访问或通过 setter 注入注入依赖项,这一点更为重要。建议使用构造器注入来确保存在依赖项。如果程序员犯了一个错误,忘记了对象没有完全初始化,并在构造器或方法中使用它,而方法本身是从构造器中调用的,那么依赖关系就已经存在了。此外,使用构造器初始化依赖项并声明那些字段`final`更简洁、结构更完善。
在构造器代码变得比简单的依赖项设置更复杂之前,或者在依赖项变得更复杂之前,这种细微的差异可能看起来并不有趣或重要。对于复杂的构造器,代码应该注意对象没有完全创建。这通常适用于任何构造器代码,但对于依赖项注入容器创建的 bean,通过直接字段访问或通过设置器注入注入依赖项,这一点更为重要。建议使用构造器注入来确保存在依赖项。如果程序员犯了一个错误,忘记了对象没有完全初始化,并在构造器或方法中使用它,而方法本身是从构造器中调用的,那么依赖关系就已经存在了。此外,使用构造器初始化依赖项并声明那些字段`final`更简洁、结构更完善。
另一方面,构造器注入也有其缺点。
如果不同的对象相互依赖,并且依赖关系图中有一个环,那么如果使用构造器依赖关系,Spring 将很困难。当类`A`需要类`B`反过来作为最简单的圆时,如果依赖注入是构造器依赖,那么`A``B`都不能没有他者而创建。在这样的情况下,不能使用构造器注入,应该将循环分解为至少一个依赖项。在这种情况下,塞特注射是不可避免的。
当存在可选依赖项时,Setter 注入也可能更好。在大多数情况下,一个类可能不需要同时使用它的所有依赖项。有些类可以使用数据库连接或 NoSQL 数据库句柄,但不能同时使用两者。尽管这也可能是一种代码味道,可能是 OO 设计糟糕的标志,但它可能会发生。这可能是一个深思熟虑的决定,因为纯 OO 设计会导致太深的对象层次结构和太多的类,超出可维护的限制。如果是这种情况,那么使用 setter 注入可以更好地处理可选的依赖关系。有的配置设置,有的留有默认值,通常是`null`
当存在可选依赖项时,设置器注入也可能更好。在大多数情况下,一个类可能不需要同时使用它的所有依赖项。有些类可以使用数据库连接或 NoSQL 数据库句柄,但不能同时使用两者。尽管这也可能是一种代码味道,可能是 OO 设计糟糕的标志,但它可能会发生。这可能是一个深思熟虑的决定,因为纯 OO 设计会导致太深的对象层次结构和太多的类,超出可维护的限制。如果是这种情况,那么使用设置器注入可以更好地处理可选的依赖关系。有的配置设置,有的留有默认值,通常是`null`
最后但同样重要的是,我们可以使用 Java 类来配置容器,以防注释不够。例如,在我们的代码库中,`ProductLookup`接口有多种实现。(如果您不知道,请不要担心;我还没有告诉您)有一个`ResourceBasedProductLookup`类从包中读取属性文件,主要用于测试应用程序,还有一个`RestClientProductLookup`,它是一个类似于产品的接口实现。如果我除了用`@Autowired`注释`lookup`字段外,没有其他配置,Spring 将不知道使用哪个实现,并在启动时向用户奖励以下错误消息:
......@@ -238,11 +238,11 @@ Consider marking one of the beans as @Primary, updating the consumer to accept m
许多开发人员并不是第一次明白这一点。我也不明白。整个 XML 配置是将配置与代码分开。它创造了这样一种可能性:系统管理员更改配置,可以自由选择某个接口的一个或其他实现,将应用程序连接在一起。现在,Spring 告诉我最好还是回到编程方式?
同时,多年来我都听到有人担心 XML 实际上并不比 Java 代码好。XML 编写本质上是编程,除了工具和 IDE 支持对 XML 的支持不如对 Java 代码的支持(后者近年来开发了很多,尽管这是针对 springxml 配置的)。
同时,多年来我都听到有人担心 XML 实际上并不比 Java 代码好。XML 编写本质上是编程,除了工具和 IDE 支持对 XML 的支持不如对 Java 代码的支持(后者近年来开发了很多,尽管这是针对 SpringXML 配置的)。
要理解从 XML 返回 Java 代码的概念,我们必须回到 XML 配置方式的纯粹原因和目的。
xmlspring 配置的主要优点不是格式不是编程的,而是配置代码与应用程序代码分离。如果我们用 Java 编写配置,并将这些配置类保持在最低限度,并且它们保持原样,那么应用程序与配置代码的分离仍然存在。我们只是将配置的格式从 XML 更改为 Java。优点很多。其中一个是,在编辑时,IDE 可以识别类的名称,我们可以用 Java 自动完成(注意,在一些 IDE 中使用 XML 来利用插件的一些扩展时,这也起作用)。对于 Java,IDE 支持无处不在。Java 比 XML 更具可读性。好吧,这是一个品味的问题,但是我们大多数人更喜欢 Java 而不是 XML。
SpringXML 配置的主要优点不是格式不是编程的,而是配置代码与应用程序代码分离。如果我们用 Java 编写配置,并将这些配置类保持在最低限度,并且它们保持原样,那么应用程序与配置代码的分离仍然存在。我们只是将配置的格式从 XML 更改为 Java。优点很多。其中一个是,在编辑时,IDE 可以识别类的名称,我们可以用 Java 自动完成(注意,在一些 IDE 中使用 XML 来利用插件的一些扩展时,这也起作用)。对于 Java,IDE 支持无处不在。Java 比 XML 更具可读性。好吧,这是一个品味的问题,但是我们大多数人更喜欢 Java 而不是 XML。
系统管理员还可以编辑 Java 代码。当他们编辑 XML 配置时,通常必须从 JAR 或 WAR 文件中提取它,编辑它,然后再次打包存档。在 Java 编辑的情况下,他们还必须发出一个`gradle war`命令或类似的命令。对于在服务器上运行 Java 应用程序的系统管理员来说,这不应该是一个阻碍。再说一遍,这不是 Java 编程。它只是编辑一些 Java 代码文件并替换一些类名文本和字符串常量。
......@@ -360,7 +360,7 @@ public interface ProductLookup {
}
```
`ResourceBasedProductLookup`将整个数据库存储在一个名为 products 的地图中。当调用其中一个服务方法时,它将从属性文件中填充。`private`方法`loadProducts`在每个服务方法启动时都会被调用,但只有在尚未加载的情况下才会加载数据:
`ResourceBasedProductLookup`将整个数据库存储在一个名为`products`的地图中。当调用其中一个服务方法时,它将从属性文件中填充。`private`方法`loadProducts`在每个服务方法启动时都会被调用,但只有在尚未加载的情况下才会加载数据:
```java
package packt.java11.mybusiness.productinformation.lookup;
......@@ -411,7 +411,7 @@ height=2
depth=18
```
`properties`文件用于存储简单的配置值,并且几乎只用于包含特定于语言的常量。这是一个非常扭曲的用法,因为`properties`文件是 **ISO Latin-1** 编码的文件,如果您需要使用一些特殊的 UTF-8 字符,您必须使用`uXXXX`格式或使用 native2ascii converter 程序来键入它们。不能简单地将它们保存为 UTF-8。不过,这是该格式用于程序国际化的特定于语言的字符串的文件(也缩写为 i18n,因为国际化一词的起始 i 和最后 n 之间有 18 个字符)。
`properties`文件用于存储简单的配置值,并且几乎只用于包含特定于语言的常量。这是一个非常扭曲的用法,因为`properties`文件是 **ISO Latin-1** 编码的文件,如果您需要使用一些特殊的 UTF-8 字符,您必须使用`uXXXX`格式或使用 native2ascii 转换器程序来键入它们。不能简单地将它们保存为 UTF-8。不过,这是该格式用于程序国际化的特定于语言的字符串的文件(也缩写为 i18n,因为国际化一词的起始 i 和最后 n 之间有 18 个字符)。
为了得到`Properties`对象,我们必须读取项目中的文件,并将它们打包成 JAR 文件。春季班`PathMatchingResourcePatternResolver`帮助我们这样做。
......@@ -534,7 +534,7 @@ public class RestClientProductLookup implements ProductLookup {
}
```
构造器用于注入 URL builder bean,这是该类的所有辅助代码。其余为`byId()``byQuery()`两种服务方式。首先,我们看一下`byId()`方法:
构造器用于注入 URL 构建器 bean,这是该类的所有辅助代码。其余为`byId()``byQuery()`两种服务方式。首先,我们看一下`byId()`方法:
```java
@Override
......@@ -559,7 +559,7 @@ public ProductInformation byId(String id) {
}
```
`byId()`方法首先调用 inventory 服务,查看库存中是否有产品。这个 REST 服务返回一个格式为`{ amount : nnn }`的 JSON;因此,我们需要一个具有`int amount`字段、一个 setter 和一个 getter 的类(非常简单,这里不列出它)。
`byId()`方法首先调用库存服务,查看库存中是否有产品。这个 REST 服务返回一个格式为`{ amount : nnn }`的 JSON;因此,我们需要一个具有`int amount`字段、一个设置器和一个获取器的类(非常简单,这里不列出它)。
弹簧`RestTemplate`提供了一种方便的方式访问休息服务。它所需要的只是 URL 模板,一种用于转换结果的类型,以及一个包含参数的`Map`对象。URL 模板字符串可以以与 Spring 控制器中的请求映射相同的方式包含参数,参数的名称介于`{``}`字符之间。模板类提供了访问 REST 服务的简单方法。它自动执行封送、发送参数和取消封送,接收响应。如果是`GET`请求,则不需要封送。数据位于请求 URL 中,并且`{xxx}`占位符被映射中的值替换,这些值作为第三个参数提供。大多数格式都可以随时使用联合国封送。在我们的应用程序中,REST 服务发送 JSON 数据,并在响应`Content-Type`HTTP 头中指示。`RestTemplate`将 JSON 转换为作为参数提供的类型。如果服务器决定以 XML 发送响应,也会在 HTTP 头`RestTemplate`中显示,该消息头将自动处理这种情况。事实上,看看代码,我们无法分辨响应是如何编码的。这也是一个好的,因为它使客户灵活,同时,我们不需要处理这样的技术细节。我们可以集中精力于业务逻辑。
......@@ -581,7 +581,7 @@ public List<String> byQuery(String query) {
# 编译和运行应用程序
我们使用`gradle`编译并运行应用程序。由于应用程序没有任何特定的配置,这些配置不会出现在大多数类似的应用程序中,因此使用 Spring 引导是明智的。springboot 使创建和运行 Web 应用程序变得非常简单。我们需要一个 Java 标准的`public static void main`方法,通过 Spring 启动应用程序:
我们使用`gradle`编译并运行应用程序。由于应用程序没有任何特定的配置,这些配置不会出现在大多数类似的应用程序中,因此使用 Spring 引导是明智的。SpringBoot 使创建和运行 Web 应用程序变得非常简单。我们需要一个 Java 标准的`public static void main`方法,通过 Spring 启动应用程序:
```java
package packt.java11.mybusiness.productinformation;
......@@ -647,7 +647,7 @@ dependencies {
我们依赖于 Spring 引导包、一些测试包、AOP 支持(我们很快就会看到这些),以及 Spring 引导开发工具。
springbootdevtools 使 Web 应用程序在重新编译时可以重新启动,而无需重新启动内置的 Tomcat 服务器。假设我们使用以下命令行启动应用程序:
SpringBootDevTools 使 Web 应用程序在重新编译时可以重新启动,而无需重新启动内置的 Tomcat 服务器。假设我们使用以下命令行启动应用程序:
```java
gradle -Dspring.profiles.active=production bootRun
......@@ -659,7 +659,7 @@ Gradle 启动应用程序。每当它看到它运行的类被修改时,就会
# 测试应用程序
应用程序应该为它所拥有的每个类都进行单元测试,可能除了不包含任何功能的 DTO 类。setter 和 getter 是由 IDE 创建的,而不是由程序员输入的,因此不太可能出现任何错误。如果存在与这些类相关的错误,则更可能是无法通过使用单元测试发现的集成问题。由于我们在前面的章节中详细讨论了单元测试,因此我们将在这里更多地关注集成测试和应用程序测试。
应用程序应该为它所拥有的每个类都进行单元测试,可能除了不包含任何功能的 DTO 类。设置器和获取器是由 IDE 创建的,而不是由程序员输入的,因此不太可能出现任何错误。如果存在与这些类相关的错误,则更可能是无法通过使用单元测试发现的集成问题。由于我们在前面的章节中详细讨论了单元测试,因此我们将在这里更多地关注集成测试和应用程序测试。
# 集成测试
......@@ -703,15 +703,15 @@ public class ProductInformationControllerTest {
}
```
这远不是一个完整和成熟的集成测试。有很多情况还没有经过测试,但在这里,这是一个很好的例子。为了获得对 Spring 环境的所有支持,我们必须使用`SpringRunner`类。`@RunWith`注释由 JUnit 框架处理;所有其他注释都是针对 Spring 的。当 JUnit 框架看到有一个`@RunWith`注释和一个指定的 runner 类时,它将启动该类而不是标准的 runner`SpringRunner`为测试设置 Spring 上下文并处理注释。
这远不是一个完整和成熟的集成测试。有很多情况还没有经过测试,但在这里,这是一个很好的例子。为了获得对 Spring 环境的所有支持,我们必须使用`SpringRunner`类。`@RunWith`注释由 JUnit 框架处理;所有其他注释都是针对 Spring 的。当 JUnit 框架看到有一个`@RunWith`注释和一个指定的运行器类时,它将启动该类而不是标准的运行器`SpringRunner`为测试设置 Spring 上下文并处理注释。
`@SpringBootTest`指定我们需要测试的应用程序。这有助于 Spring 读取该类和该类上的注释,识别要扫描的包。
`@AutoConfigureMockMvc`告诉 Spring 配置 Model-View-Controller 框架的一个模拟版本,它可以在没有 Servlet 容器和 Web 协议的情况下执行。使用它,我们可以测试我们的 REST 服务,而不必真正进入网络。
`@AutoConfigureMockMvc`告诉 Spring 配置模型-视图-控制器框架的一个模拟版本,它可以在没有 Servlet 容器和 Web 协议的情况下执行。使用它,我们可以测试我们的 REST 服务,而不必真正进入网络。
`@ActiveProfiles`告诉 Spring 活动的配置文件是本地的,Spring 必须使用注释`@Profile("local")`所表示的配置。这是一个使用`.properties`文件而不是外部 HTTP 服务的版本;因此,这适合于集成测试。
测试在 mocking 框架内执行`GET`请求,在控制器中执行代码,并使用 mocking 框架和 Fluent API 以非常可读的方式测试返回值。
测试在模拟框架内执行`GET`请求,在控制器中执行代码,并使用模拟框架和 Fluent API 以非常可读的方式测试返回值。
请注意,使用属性文件并基于属性文件实现服务有点过分。我创建它是为了能够在没有任何真正的备份服务的情况下以交互方式启动应用程序。考虑以下命令-`gradle -Dspring.profiles.active=local bootRun`。如果我们发出前面的命令,那么服务器将使用此本地实现启动。如果我们只以集成测试为目标,那么服务类的本地实现应该在`test`目录下,并且应该简单得多,主要是对任何预期的请求只返回常量响应,如果出现任何非预期的请求则抛出错误。
......@@ -723,13 +723,13 @@ public class ProductInformationControllerTest {
gradle -Dspring.profiles.active=production bootRun
```
如果我们启动应用程序,发出前面的命令并启动浏览器到 URL`http://localhost:8080/pi/123`,我们将在浏览器屏幕上得到一条 fat 错误消息。哎哟。。。
如果我们启动应用程序,发出前面的命令并启动浏览器到 URL`http://localhost:8080/pi/123`,我们将在浏览器屏幕上得到一条庞大的错误消息。哎哟。。。
上面写着类似的东西。这是因为我们的代码想连接到备份服务,但我们还没有。要在这个级别上测试应用程序,我们应该创建备份服务,或者至少创建一些模拟它们的东西。最简单的方法是使用 SoapUI 程序。
SoapUI 是一个 Java 程序,可从[这个页面](https://www.soapui.org/)获得。有一个开源版本和免费版本,还有一个商业版本。就我们而言,免费版本就足够了。我们可以用最简单的单击转发方式安装它,因为它有一个安装向导。之后,我们可以启动它并使用图形用户界面。
我们将创建一个新的测试项目 Catalog and inventory,并在其中设置两个 REST 模拟服务 Catalog and inventory,如下面的屏幕截图所示:
我们将创建一个新的测试项目 CatalogAndInventory,并在其中设置两个 REST 模拟服务 CatalogAndInventory,如下面的屏幕截图所示:
![](img/b22c4714-a394-43ff-85bd-1691e0f356ff.png)
......@@ -741,13 +741,13 @@ SoapUI 是一个 Java 程序,可从[这个页面](https://www.soapui.org/)获
soapUI 将项目保存在一个 XML 文件中,您可以在 GitHub 的`project`目录中使用它。
启动模拟服务后,按 refresh 时,浏览器屏幕上的错误消息将消失:
启动模拟服务后,按“刷新”时,浏览器屏幕上的错误消息将消失:
![](img/9685b294-4d1e-4a29-be6b-728d3b021bb2.png)
我们看到的正是我们在 SoapUI 中输入的内容。
现在,如果我将 inventory mock 服务更改为返回 0 而不是 100,就像在原始版本中一样,我得到的是以下空记录:
现在,如果我将库存模拟服务更改为返回 0 而不是 100,就像在原始版本中一样,我得到的是以下空记录:
```java
{"id":"","title":"","description":"","size":[0.0,0.0,0.0],"weight":0.0}
......@@ -781,13 +781,13 @@ soapUI 将项目保存在一个 XML 文件中,您可以在 GitHub 的`project`
GET 参数是 URL 的一部分,浏览器历史会记住这一点。将这些信息输入浏览器位置窗口、复制/粘贴并通过聊天频道或电子邮件发送也非常容易。这样,应用程序的用户如果没有受过这样的教育,也不关心安全性,可能会泄露机密信息。尽管对 HTTP 标头中发送的信息进行同样的处理并非不可能,但这种情况不太可能发生。如果信息在邮件头中,并且有人通过电子邮件发送了这些信息,他们可能知道自己在做什么;他们是自愿跨越安全边界的,而不是简单的疏忽。
为了沿着 HTTP 请求发送认证信息,Spring 提供了一个安全模块,可以使用注释和配置 xml 和/或类轻松配置该模块。这一次,我们将以不同的方式引入 Servlet 过滤器。
为了沿着 HTTP 请求发送认证信息,Spring 提供了一个安全模块,可以使用注释和配置 XML 和/或类轻松配置该模块。这一次,我们将以不同的方式引入 Servlet 过滤器。
我们将要求供应商将`X-PartnerSecret`标题插入请求。这是一个非标准头,因此必须有`X-`前缀。遵循此方法还提供了额外的安全特性。这样,我们可以防止用户使用简单的浏览器访问服务。至少,需要额外的插件,可以插入自定义头或其他程序,如 SoapUI。这样,它将确保我们的合作伙伴将以编程方式使用接口,或者如果他们需要临时测试接口,只有具有一定技术水平的用户才能这样做。这对于控制支持成本非常重要。
由于每个服务都必须检查这个秘密,所以最好不要在每个服务控制器中插入检查代码。即使我们正确地创建代码,并将对机密的检查考虑到一个单独的类中,断言机密存在并且正确的方法调用也必须插入到每个控制器中。控制器执行服务;检查客户端的真实性是一个基础设施问题。它们是不同的关注点,因此,它们必须分开。
Servlet 标准为我们提供的最好的方法是通过 Servlet 过滤器。如果配置了过滤器,Servlet 过滤器是由 Servlet 容器在 Servlet 自身之前调用的类。过滤器可以在 Servlet 容器的`web.xml`配置文件中配置,也可以在使用 Spring boot 时使用注释进行配置。过滤器不仅获取作为参数的请求和响应,而且还获取第三个`FilterChain`类型的参数,该参数应用于调用 Servlet 或链中的下一个过滤器。
Servlet 标准为我们提供的最好的方法是通过 Servlet 过滤器。如果配置了过滤器,Servlet 过滤器是由 Servlet 容器在 Servlet 自身之前调用的类。过滤器可以在 Servlet 容器的`web.xml`配置文件中配置,也可以在使用 SpringBoot 时使用注释进行配置。过滤器不仅获取作为参数的请求和响应,而且还获取第三个`FilterChain`类型的参数,该参数应用于调用 Servlet 或链中的下一个过滤器。
可以定义多个过滤器,它们会被链接起来。过滤器可自行决定是否调用链中的下一个过滤器:
......@@ -857,19 +857,19 @@ public class AuthFilter implements Filter {
首先也是最重要的是方面。这是我们想要实现的功能,在我们的示例中是审计日志记录。
Joinpoint 是调用方面时的执行点。在 Java 中使用全面方面解决方案修改生成的类的字节码时,连接点几乎可以是任何东西。它可以是对字段的访问,读或写;它可以是对方法的调用或异常抛出。在 Spring 的情况下,不会修改类字节码;因此,Spring 无法识别对字段的访问或抛出的异常。使用 Spring,调用方法时总是使用连接点。
连接点是调用方面时的执行点。在 Java 中使用全面方面解决方案修改生成的类的字节码时,连接点几乎可以是任何东西。它可以是对字段的访问,读或写;它可以是对方法的调用或异常抛出。在 Spring 的情况下,不会修改类字节码;因此,Spring 无法识别对字段的访问或抛出的异常。使用 Spring,调用方法时总是使用连接点。
一条建议是如何在连接点调用方面。它可以在建议前,建议后,或周围的建议。如果通知在前面,则在调用方法之前调用方面。当通知在之后时,在调用方法之后调用方面。Around 意味着在方法调用之前调用方面,方面也有一个参数来调用方法,并且在方法调用之后仍然执行一些操作。这样,around 建议与 Servlet 过滤器非常相似。
一条建议是如何在连接点调用方面。它可以在建议前,建议后,或周围的建议。如果通知在前面,则在调用方法之前调用方面。当通知在之后时,在调用方法之后调用方面。Around 意味着在方法调用之前调用方面,方面也有一个参数来调用方法,并且在方法调用之后仍然执行一些操作。这样,环绕建议与 Servlet 过滤器非常相似。
在方法调用之前调用 before 通知,在它返回之后,框架将调用该方法。方面无法阻止调用原始方法。唯一的例外是当方面抛出异常时。
在方法调用之前调用事先通知,在它返回之后,框架将调用该方法。方面无法阻止调用原始方法。唯一的例外是当方面抛出异常时。
事后通知也受例外情况的影响。返回后的通知可以在方法返回时调用。只有当方法抛出异常时才调用 after 抛出。最后,在异常或返回的情况下调用 after
事后通知也受例外情况的影响。返回后的通知可以在方法返回时调用。只有当方法抛出异常时才调用抛出后通知。最后,在异常或返回的情况下调用事后通知
切入点是一个特殊的字符串表达式,用于标识连接点。切入点表达式可以匹配零个、一个或多个连接点。当方面与切入点表达式相关联时,框架将知道连接点以及何时何地调用方面。换句话说,切入点是一个字符串,它告诉您何时以及为哪个方法调用方面。
尽管 AOP 的 Spring 实现不使用 AspectJ,也不修改为类创建的字节码,但它支持切入点表达式语言。尽管这种表达式语言提供了比 Spring 实现的更多的特性,但它是一种成熟的、广泛使用和接受的用于描述切入点的表达式语言,发明新的东西是没有意义的。
*序言*是向已经存在的类型添加方法或字段,并在运行时添加。Spring 允许此 AOP 功能向现有类型添加接口,并以 advice 类的形式添加接口的实现。在我们的示例中,我们不使用此功能。
*序言*是向已经存在的类型添加方法或字段,并在运行时添加。Spring 允许此 AOP 功能向现有类型添加接口,并以建议类的形式添加接口的实现。在我们的示例中,我们不使用此功能。
*目标对象*是方面建议的对象。这是包含关于方面的方法的 bean,即在调用方面之前或之后。
......@@ -906,7 +906,7 @@ public class SpringConfigurationAspect {
在我们的示例中,我们使用`@Around`方面来演示最复杂的场景。我们记录了目标方法在方法执行前后的执行情况,还通过`ProceedingJoinPoint`对象调用了原始方法。因为这两个对象返回了不同的类型,并且我们希望以不同的方式记录,所以我们定义了两个方面方法。
advice 注释的参数是切入点字符串。在这种情况下,它是一个简单的。第一个`execution(* byId(..))`表示,对于任何名为 byId 且具有任何参数的方法的任何执行,都应该调用方面。第二种方法非常相似,只是方法的名称不同。这些是简单的切入点表达式,但在大量使用 AOP 的大型应用程序中,它们可能非常复杂。
建议注释的参数是切入点字符串。在这种情况下,它是一个简单的。第一个`execution(* byId(..))`表示,对于任何名为`byId`且具有任何参数的方法的任何执行,都应该调用方面。第二种方法非常相似,只是方法的名称不同。这些是简单的切入点表达式,但在大量使用 AOP 的大型应用程序中,它们可能非常复杂。
Spring 中的切入点表达式语法主要遵循 AspectJ 使用的语法。该表达式采用**切点指示符****PCD**)的概念,通常执行。后面是定义要截取的方法的模式。一般格式如下:
......@@ -951,9 +951,9 @@ Spring 使用的 PCD 在 AspectJ 中并不存在。这是一颗豆子。您可
# 基于动态代理的 AOP
springaop 第一次出现在 Java 程序员面前时,它看起来很神奇。我们如何有一个变量`classX`并调用该对象上的方法?相反,它在方法执行之前或之后执行某些方面,甚至在其周围执行某些方面,以拦截调用。
SpringAOP 第一次出现在 Java 程序员面前时,它看起来很神奇。我们如何有一个变量`classX`并调用该对象上的方法?相反,它在方法执行之前或之后执行某些方面,甚至在其周围执行某些方面,以拦截调用。
Spring 使用的技术称为动态代理。当我们有一个实现接口的对象时,我们可以创建另一个对象——代理对象——也实现该接口,但是每个方法实现都调用一个名为 handler 的不同对象,实现 JDK 接口`InvocationHandler`。当代理对象上调用接口方法时,它将在处理程序对象上调用以下方法:
Spring 使用的技术称为动态代理。当我们有一个实现接口的对象时,我们可以创建另一个对象——代理对象——也实现该接口,但是每个方法实现都调用一个名为处理器的不同对象,实现 JDK 接口`InvocationHandler`。当代理对象上调用接口方法时,它将在处理程序对象上调用以下方法:
```java
public Object invoke(Object target, Method m, Object[] args)
......@@ -967,7 +967,7 @@ public Object invoke(Object target, Method m, Object[] args)
这些技术在运行时创建类并将其加载到 Java 内存中,它们是非常深入的技术工具。它们是高级主题。我并不是说当我还是一个 Java 程序员新手的时候不要玩它们。毕竟,会发生什么?Java不是一把上膛的枪。然而,重要的是,当你不了解一些细节或者一开始有些东西不起作用时,不要失去兴趣。或者第二个。或者第三。。。继续游泳。
Spring 中的 AOP 实现通过为目标对象生成代理对象来工作,处理程序调用我们在 Spring 配置中定义的方面。这就是您不能将方面放在`final`类或`final`方法上的原因。此外,您不能在`private``protected`方法上配置方面。原则上,`protected`方法可以被代理,但这不是一个好的实践,因此 Spring AOP 不支持它。类似地,不能将方面放在不是 springbean 的类上。它们是由代码直接创建的,而不是通过 Spring 创建的,并且在创建对象时没有机会返回代理而不是原始对象。简单地说,如果不要求 Spring 创建对象,它就不能创建自定义对象。我们最不想做的就是执行这个程序,看看方面是如何执行的。审计日志的实现非常简单。我们使用标准日志,这对于审计日志的实际应用来说是不够的。我们所做的唯一特殊的事情是使用一个由名称`AUDIT_LOG`而不是类名称标识的记录器。在大多数日志框架中,这是对日志记录器的合法使用。尽管我们通常使用类来标识记录器,但是使用字符串来标识记录器是绝对可能的。在我们的日志记录中,这个字符串也将被打印在控制台的日志行中,并且它将在视觉上突出。
Spring 中的 AOP 实现通过为目标对象生成代理对象来工作,处理程序调用我们在 Spring 配置中定义的方面。这就是您不能将方面放在`final`类或`final`方法上的原因。此外,您不能在`private``protected`方法上配置方面。原则上,`protected`方法可以被代理,但这不是一个好的实践,因此 Spring AOP 不支持它。类似地,不能将方面放在不是 SpringBean 的类上。它们是由代码直接创建的,而不是通过 Spring 创建的,并且在创建对象时没有机会返回代理而不是原始对象。简单地说,如果不要求 Spring 创建对象,它就不能创建自定义对象。我们最不想做的就是执行这个程序,看看方面是如何执行的。审计日志的实现非常简单。我们使用标准日志,这对于审计日志的实际应用来说是不够的。我们所做的唯一特殊的事情是使用一个由名称`AUDIT_LOG`而不是类名称标识的记录器。在大多数日志框架中,这是对日志记录器的合法使用。尽管我们通常使用类来标识记录器,但是使用字符串来标识记录器是绝对可能的。在我们的日志记录中,这个字符串也将被打印在控制台的日志行中,并且它将在视觉上突出。
考虑以下命令:
......@@ -975,7 +975,7 @@ Spring 中的 AOP 实现通过为目标对象生成代理对象来工作,处
gradle -Dspring.profiles.active=production bootRun
```
如果我们用前面的命令启动应用程序,为项目启动 SoapUI,启动 mock 服务,并执行测试,我们将看到 aspects 在控制台上打印的以下日志行:
如果我们用前面的命令启动应用程序,为项目启动 SoapUI,启动模拟服务,并执行测试,我们将看到 Aspects 在控制台上打印的以下日志行:
```java
2023-10-07 23:42:07.559 INFO 74643 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
......@@ -994,7 +994,7 @@ gradle -Dspring.profiles.active=production bootRun
# 摘要
在本章中,我们构建了一个支持企业对企业事务的简单业务应用程序。我们使用事实上的标准企业框架 Spring 提供的特性,在 microservices(几乎)架构中实现了 REST 服务。回顾这一章,令人惊讶的是,我们编写的代码很少,实现了所有的功能,这是很好的。开发所需的代码越少越好。这证明了框架的威力。
在本章中,我们构建了一个支持企业对企业事务的简单业务应用程序。我们使用事实上的标准企业框架 Spring 提供的特性,在微服务(几乎)架构中实现了 REST 服务。回顾这一章,令人惊讶的是,我们编写的代码很少,实现了所有的功能,这是很好的。开发所需的代码越少越好。这证明了框架的威力。
我们讨论了微服务、HTTP、REST、JSON,以及如何使用 MVC 设计模式使用它们。我们学习了 Spring 是如何构建的,有哪些模块,依赖注入在 Spring 中是如何工作的,甚至还涉及了 AOP。这一点非常重要,因为与 AOP 一起,我们发现了 Spring 是如何使用动态代理对象工作的,当您需要调试 Spring 或其他使用类似解决方案的框架时,这一点非常有价值(还有一些是经常使用的)。
......
......@@ -23,7 +23,7 @@
# 设置项目
由于我们仍在使用 springboot,构建文件不需要任何修改;我们将使用与上一章相同的文件。然而,包的结构有点不同。这一次,我们做的事情比获取请求和响应后端服务提供给我们的任何内容都要复杂。现在,我们必须实现复杂的业务逻辑,正如我们将看到的,它需要许多类。当我们在一个特定的包中有 10 个以上的类时,是时候考虑把它们放在不同的包中了。相互关联并具有类似功能的类应该放在一个包中。这样,我们就有了以下产品的包装:
由于我们仍在使用 SpringBoot,构建文件不需要任何修改;我们将使用与上一章相同的文件。然而,包的结构有点不同。这一次,我们做的事情比获取请求和响应后端服务提供给我们的任何内容都要复杂。现在,我们必须实现复杂的业务逻辑,正如我们将看到的,它需要许多类。当我们在一个特定的包中有 10 个以上的类时,是时候考虑把它们放在不同的包中了。相互关联并具有类似功能的类应该放在一个包中。这样,我们就有了以下产品的包装:
* 控制器(虽然在本例中我们只有一个,但通常有更多)
* 数据存储 bean,除了存储数据之外没有其他功能,因此是字段、设置器和获取器
......
......@@ -1198,14 +1198,14 @@ ServiceLoader API 的 LoggerFinder 实现
# XML 目录
现代 Java 平台包括一个标准的 xmlcatalogapi,以支持 oasisxmlcatalogs 标准 v1.1。新的 API 定义了 catalog 和 catalog 解析抽象,以便 JAXP 处理器可以使用它们。在本节中,我们将了解以下内容:
现代 Java 平台包括一个标准的 XMLcatalogapi,以支持 oasisxmlcatalogs 标准 v1.1。新的 API 定义了 catalog 和 catalog 解析抽象,以便 JAXP 处理器可以使用它们。在本节中,我们将了解以下内容:
* oasisxml 目录标准
* oasisXML 目录标准
* JAXP 处理器
* 早期的 XML 目录
* 当前 XML 目录
# oasisxml 目录标准
# oasisXML 目录标准
**XML****可扩展标记语言**)目录是由目录项组成的 XML 文档。每个条目将一个标识符与另一个位置配对。OASIS 是一个非盈利的财团,其使命是推进开放标准。他们在 2005 年发布了 XML 目录标准 1.1 版。本标准有两个基本用例:
......@@ -1218,7 +1218,7 @@ ServiceLoader API 的 LoggerFinder 实现
<public publicId="-//Packt Publishing Limited//Mastering Java9//EN" uri="https://www.packtpub.com/application-development/mastering-java-9"/>
```
[完整的 oasisxml 目录标准可以在官方网站上找到](https://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html)
[完整的 oasisXML 目录标准可以在官方网站上找到](https://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html)
# JAXP 处理器
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册