这个结构是一个虚构的设置,但一个现实的。我编写并描述这些部分是为了让您了解如何在大型企业中看到混合技术。我在这里描述的是一个非常简单的设置。有些公司的系统中有一千多个软件模块,使用不同的技术和完全不同的接口,所有这些模块都相互连接。这并不是因为他们喜欢这种混乱,而是因为经过 30 年的持续 it 发展,这种混乱才变得如此。新技术来了,旧技术也消失了。业务发生了变化,如果你想保持竞争力,就不能固守旧技术。同时,您无法立即替换整个基础结构。其结果是,我们看到相当老的技术仍然在运行,而且主要是新技术。旧技术得到及时推广。它们不会永远呆在这里,而且,当恐龙出现在我们面前时,我们有时会感到惊讶。
在我们的应用程序中,我们将使用`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 更广泛。
@@ -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 上下文行为由接口`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 容器之外使用代码,建议使用标准注释。在功能上,它们是等价的。
弹簧`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 启动应用程序:
应用程序应该为它所拥有的每个类都进行单元测试,可能除了不包含任何功能的 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 以非常可读的方式测试返回值。
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 实现通过为目标对象生成代理对象来工作,处
在本章中,我们构建了一个支持企业对企业事务的简单业务应用程序。我们使用事实上的标准企业框架 Spring 提供的特性,在 microservices(几乎)架构中实现了 REST 服务。回顾这一章,令人惊讶的是,我们编写的代码很少,实现了所有的功能,这是很好的。开发所需的代码越少越好。这证明了框架的威力。
在本章中,我们构建了一个支持企业对企业事务的简单业务应用程序。我们使用事实上的标准企业框架 Spring 提供的特性,在微服务(几乎)架构中实现了 REST 服务。回顾这一章,令人惊讶的是,我们编写的代码很少,实现了所有的功能,这是很好的。开发所需的代码越少越好。这证明了框架的威力。
我们讨论了微服务、HTTP、REST、JSON,以及如何使用 MVC 设计模式使用它们。我们学习了 Spring 是如何构建的,有哪些模块,依赖注入在 Spring 中是如何工作的,甚至还涉及了 AOP。这一点非常重要,因为与 AOP 一起,我们发现了 Spring 是如何使用动态代理对象工作的,当您需要调试 Spring 或其他使用类似解决方案的框架时,这一点非常有价值(还有一些是经常使用的)。