1.3 编写 Spring 应用程序

因为才刚刚开始,所以我们将从对 Taco Cloud 应用程序的一个相对较小的更改开始,但是这个更改将展示 Spring 的很多优点。在刚刚开始的时候,添加到 Taco Cloud 应用程序的第一个功能是主页,这似乎是合适的。当你添加主页,你将创建两个代码构件:

  • 一个处理主页请求的控制器类
  • 一个视图模板,定义了主页的外观

因为测试很重要,所以还将编写一个简单的测试类来测试主页。但首先…我们来写这个控制器。

1.3.1 处理 web 请求

Spring 附带了一个强大的 web 框架,称为 Spring MVC。Spring MVC 的核心是控制器的概念,这是一个处理请求并使用某种信息进行响应的类。对于面向浏览器的应用程序,控制器的响应方式是可选地填充模型数据并将请求传递给视图,以生成返回给浏览器的 HTML。

你将在第 2 章学到很多关于 Spring MVC 的知识。但是现在,将编写一个简单的控制器类来处理根路径的请求(例如 /),并将这些请求转发到主页视图,而不填充任何模型数据。下面的清单显示了简单的控制器类。

  1. package tacos;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. @Controller
  5. public class HomeController {
  6. @GetMapping("/")
  7. public String home() {
  8. return "home";
  9. }
  10. }

可以看到,这个类是用 @Controller 注释的。@Controller 本身并没有做多少事情。它的主要目的是将该类识别为组件扫描的组件。由于 HomeController 是用 @Controller 注释的,因此 Spring 的组件扫描会自动发现它,并在 Spring 应用程序上下文中创建一个 HomeController 实例作为 bean。

实际上,其他一些注释(包括 @Component@Service@Repository)的用途与 @Controller 类似。你可以用任何其他的注解来有效地注释 HomeController,它仍然可以工作。但是,选择 @Controller 更能描述该组件在应用程序中的角色。

home() 方法与控制器方法一样简单。它使用 @GetMapping 进行注释,以指示如果接收到根路径 / 的 HTTP GET 请求,则此方法应该处理该请求。除了返回 homeString 值外,它什么也不做。

此值被解释为视图的逻辑名称。如何实现该视图取决于几个因素,但是因为 Thymeleaf 在类路径中,所以可以使用 Thymeleaf 定义该模板。

为什么是 Thymeleaf?

你可能想知道为什么选择 Thymeleaf 作为模板引擎。为什么不是 JSP?为什么不是 FreeMarker?为什么不是其他几个选项中的一个呢?

简单地说,我必须选择一些东西,我喜欢 Thymeleaf,相比其他选项更喜欢。尽管 JSP 看起来是一个不做的选择,但是在使用 JSP 进行 Spring 引导时仍然存在一些需要克服的挑战。我不想在第 1 章中掉进那个陷阱。不要紧,我们将在第 2 章中讨论其他模板选项,包括 JSP。

模板名称由逻辑视图名称派生而来,它的前缀是 /templates/,后缀是 .html。模板的结果路径是 /templates/home.html。因此,需要将模板放在项目的 /src/main/resources/templates/home.html 中。现在让我们创建该模板。

1.3.2 定义视图

为了保持你的主页简洁,它应该做的只是欢迎用户访问网站。下一个清单显示了定义 Taco Cloud 主页的基本 Thymeleaf 模板。

  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. xmlns:th="http://www.thymeleaf.org">
  4. <head>
  5. <title>Taco Cloud</title>
  6. </head>
  7. <body>
  8. <h1>Welcome to...</h1>
  9. <img th:src="@{/images/TacoCloud.png}"/>
  10. </body>
  11. </html>

关于这个模板没有太多要讨论的。唯一值得注意的代码行是显示 Taco Cloud 标志的 <img> 标记。它使用一个 Thymeleaf 的 th:src 属性和一个 @{…} 表达式引用具有上下文相对路径的图片。除去这些,它只是一个 Hello World 页面。

但是让我们再多讨论一下这个图片。我将把它留给你来定义一个你喜欢的 Taco Cloud 标志。你需要将它放在项目中的恰当位置。

该图片是通过上下文相对路径 /images/TacoCloud.png 进行引用的。从我们对项目结构的回顾中可以想起,像图片这样的静态内容保存在 /src/main/resources/static 文件夹中。这意味着 Taco Cloud 标志图片也必须驻留在项目的 /src/main/resources/static/images/TacoCloud.png 中。

现在已经有了处理主页请求的控制器和呈现主页的视图模板,几乎已经准备好启动应用程序并看到它的实际运行效果了。但首先,让我们看看如何针对控制器编写测试。

1.3.3 测试控制器

在对 HTML 页面的内容进行断言时,测试 web 应用程序可能比较棘手。幸运的是,Spring 提供了一些强大的测试支持,使测试 web 应用程序变得很容易。

就主页而言,你将编写一个与主页本身复杂度相当的测试。你的测试将对根路径 / 执行一个 HTTP GET 请求并期望得到一个成功的结果,其中视图名称为 home,结果内容包含短语 “Welcome to…”。下面的方法应该可以达到目的。

  1. package tacos;
  2. import static org.hamcrest.Matchers.containsString;
  3. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  4. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
  5. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  6. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
  7. import org.junit.Test;
  8. import org.junit.runner.RunWith;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
  11. import org.springframework.test.context.junit4.SpringRunner;
  12. import org.springframework.test.web.servlet.MockMvc;
  13. @RunWith(SpringRunner.class)
  14. @WebMvcTest(HomeController.class)
  15. public class HomeControllerTest {
  16. @Autowired
  17. private MockMvc mockMvc;
  18. @Test
  19. public void testHomePage() throws Exception {
  20. mockMvc.perform(get("/"))
  21. .andExpect(status().isOk())
  22. .andExpect(view().name("home"))
  23. .andExpect(content().string(containsString("Welcome to...")));
  24. }
  25. }

关于这个测试,你可能注意到的第一件事是,它与 TacoCloudApplicationTests 类在应用到它的注释方面略有不同。HomeControllerTest 使用 @WebMvcTest 注释,而不是 @SpringBootTest 标记。这是 Spring Boot 提供的一个特殊测试注释,它安排测试在 Spring MVC 应用程序的上下文中运行。更具体地说,在本例中,它安排 HomeController 在 Spring MVC 中注册,这样你就可以对它进行请求。

@WebMvcTest 还为测试 Spring MVC 提供了 Spring 支持。虽然可以让它启动服务器,但模拟 Spring MVC 的机制就足以满足你的目的了。测试类被注入了一个 MockMvc 对象中,以此用来测试来驱动模型。

testHomePage() 方法定义了要对主页执行的测试。它从 MockMvc 对象开始,执行针对 /(根路径)的 HTTP GET 请求。该请求规定了下列期望值:

  • 响应应该有一个HTTP 200(OK)状态。

  • 视图应该有一个合理的主页名称。

  • 呈现的视图应该包含 “Welcome to…”

如果在 MockMvc 对象执行请求之后,这些期望中的任何一个都没有满足,那么测试就会失败。但是控制器和视图模板是为了满足这些期望而编写的,所以测试应该能够通过,或者至少能够通过一些表示测试通过的绿色提示。

控制器写好了,视图模板创建好了,测试通过了。看来你已经成功地实现了主页。但是,即使测试通过了,在浏览器中查看结果也会稍微让人更满意一些。毕竟,Taco Cloud 的客户也将这样看待它。让我们构建应用程序并运行它。

1.3.4 构建并运行应用程序

正如有多种方法可以初始化 Spring 应用程序一样,也有多种方法可以运行 Spring 应用程序。如果愿意,可以翻到附录部分,阅读一些更常见的运行 Spring 引导应用程序的方法。

因为选择使用 Spring Tool Suite 来初始化和处理项目,所以有一个称为 Spring Boot Dashboard 的便利功能可以帮助你在 IDE 中运行应用程序。Spring Boot Dashboard 显示为一个选项卡,通常位于 IDE 窗口的左下方。图 1.7 显示了 Spring Boot Dashboard 的注释截图。

虽然图 1.7 包含了一些最有用的细节,但我不想花太多时间来检查 Spring Boot Dashboard 所做的一切。现在需要知道的重要事情是如何使用它来运行 Taco Cloud 应用程序。确保 taco-cloud 应用程序在项目列表中突出显示(这是图 1.7 中显示的惟一应用程序),然后单击 start 按钮(最左边的按钮,其中有绿色三角形和红色正方形),应用程序应该会立即启动。

图 1.7 Spring Boot Dashboard 高亮

图 1.7 Spring Boot Dashboard 注释

当应用程序启动时,将在控制台中看到一些 Spring ASCII 图飞过,然后看到一些日志条目描述应用程序启动时的步骤。在停止日志记录之前,将看到一个日志条目,其中说 Tomcat 在 port(s): 8080 (http) 上启动,这意味着已经准备好将 web 浏览器指向主页,以查看结果。

等一下,Tomcat 启动?何时将应用程序部署到 Tomcat?

Spring Boot 应用程序倾向于裹挟所有需要的东西,而不需要部署到某个应用服务器。你从未将应用程序部署到 Tomcat… 其实 Tomcat 是应用程序的一部分!(将在 1.3.6 小节中详细描述 Tomcat 如何成为应用程序的一部分的。)

现在应用程序已经启动,将 web 浏览器指向 http://localhost:8080(或单击 Spring Boot Dashboard 中地球仪样子的按钮),应该会看到类似图 1.8 所示的内容。如果你设计了自己的图标,那么结果可能不同,但是它与在图 1.8 中看到的应该相差不大。

图 1.8 Taco Cloud 主页

图 1.8 Taco Cloud 主页

它可能没什么好看的。但这并不是一本关于平面设计的书。主页的简陋外观现在已经足够了。它为你了解 Spring 提供了一个坚实的开端。

到目前为止,忽略了 DevTools。在初始化项目时将其作为依赖项进行选择。它作为一个依赖项出现在生成的 pom.xml 文件中。Spring Boot Dashboard 甚至显示项目已经启用了 DevTools。但是什么是 DevTools,它能为您做什么?让我们快速浏览一下 DevTools 的几个最有用的特性。

1.3.5 了解 Spring Boot DevTools

顾名思义,DevTools 为 Spring 开发人员提供了一些方便的开发同步工具。这些是:

  • 当代码更改时自动重启应用程序

  • 当以浏览器为目标的资源(如模板、JavaScript、样式表等)发生变化时,浏览器会自动刷新

  • 自动禁用模板缓存

  • 如果 H2 数据库正在使用,则在 H2 控制台中构建

理解 DevTools 不是 IDE 插件是很重要的,它也不要求您使用特定的 IDE。它在 Spring Tool Suite、IntelliJ IDEA 和 NetBeans 中工作得同样好。此外,由于它仅用于开发目的,所以在部署生产环境时禁用它本身是非常明智的。(我们将在第 19 章中讨论如何部署应用程序。)现在,让我们关注一下 Spring Boot DevTools 最有用的特性,首先是自动重启应用程序。

自动重启应用程序

使用 DevTools 作为项目的一部分,将能够对项目中的 Java 代码和属性文件进行更改,并在短时间内查看这些更改的应用。DevTools 监视更改,当它看到某些内容发生更改时,它会自动重新启动应用程序。

更准确地说,当 DevTools 起作用时,应用程序被加载到 Java 虚拟机(JVM)中的两个单独的类加载器中。一个类装入器装入 Java 代码、属性文件以及项目的 src/main/path 中的几乎所有东西。这些项目可能会频繁更改。另一个类加载器加载了依赖库,它们不太可能经常更改。

当检测到更改时,DevTools 只重新加载包含项目代码的类加载器,并重新启动 Spring 应用程序上下文,但不影响其他类加载器和 JVM。尽管这一策略很微妙,但它可以略微减少启动应用程序所需的时间。

这种策略的缺点是对依赖项的更改在自动重新启动时不可用。这是因为类装入器包含依赖项库 不是自动重新加载。这意味着,每当在构建规范中添加、更改或删除依赖项时,都需要重新启动应用程序才能使这些更改生效。

自动刷新浏览器和禁用模板缓存

默认情况下,模板选项(如 Thymeleaf 和 FreeMarker)被配置为缓存模板解析的结果,这样模板就不需要对它们所服务的每个请求进行修复。这在生产中非常有用,因为它可以带来一些性能上的好处。

但是,缓存的模板在开发时不是很好。缓存的模板使它不可能在应用程序运行时更改模板,并在刷新浏览器后查看结果。即使做了更改,缓存的模板仍将继续使用,直到重新启动应用程序。

DevTools 通过自动禁用所有模板缓存来解决这个问题。对模板进行尽可能多的修改,并且要知道只有浏览器刷新才能看到结果。

但如果像我一样,甚至不想被点击浏览器的刷新按钮所累,如果能够立即在浏览器中进行更改并查看结果,那就更好了。幸运的是,DevTools 为我们这些懒得点击刷新按钮的人提供了一些特别的功能。

当 DevTools 起作用时,它会自动启用 LiveReload (http://livereload.com/)服务器和应用程序。就其本身而言,LiveReload 服务器并不是很有用。但是,当与相应的 LiveReload 浏览器插件相结合时,它会使得浏览器在对模板、图像、样式表、JavaScript 等进行更改时自动刷新 —— 实际上,几乎所有最终提供给浏览器的更改都会自动刷新。

LiveReload 有针对 Google Chrome、Safari 和 Firefox 浏览器的插件。(对不起,ie 和 Edge 的粉丝们。)请访问 http://livereload.com/extensions/,了解如何为浏览器安装 LiveReload。

在 H2 控制台中构建

虽然项目还没有使用数据库,但这将在第 3 章中进行更改。如果选择使用 H2 数据库进行开发,DevTools 还将自动启用一个 H2 控制台,你可以从 web 浏览器访问该控制台。只需将 web 浏览器指向 http://localhost:8080/h2-console,就可以深入了解应用程序正在处理的数据。

至此,已经编写了一个完整但简单的 Spring 应用程序。你将在本书的整个过程中扩展它。但是现在是回顾已经完成的工作以及 Spring 如何发挥作用的好时机。

1.3.6 回顾

回想一下是如何走到这一步的。简而言之,以下是构建基于 Spring 的 Taco Cloud 应用程序的步骤:

  • 使用 Spring Initializr 创建了一个初始项目结构。
  • 写了一个控制器类来处理主页请求。
  • 定义了一个视图模板来呈现主页。
  • 写了一个简单的测试类来检验上诉工作。

看起来很简单,不是吗?除了启动项目的第一步之外,所采取的每一个行动都是为了实现创建主页的目标。

事实上,编写的几乎每一行代码都是针对这个目标的。不计算 Java import 语句,只计算控制器类中的两行代码,而视图模板中没有 Spring 的特定代码。尽管测试类的大部分都使用了 Spring 的测试支持,但是在测试上下文中,它的侵入性似乎要小一些。

这是使用 Spring 开发的一个重要好处。可以关注于满足应用程序需求的代码,而不是满足框架的需求。尽管确实需要不时地编写一些特定于框架的代码,但这通常只是代码库的一小部分。如前所述,Spring (通过 Spring Boot)可以被认为是 无框架的框架

这到底是怎么回事?Spring 在幕后做了什么来确保您的应用程序需求得到满足?为了理解 Spring 在做什么,让我们从构建规范开始。

在 pom.xml 文件中,声明了对 Web 和 Thymeleaf 启动器的依赖。这两个依赖关系带来了一些其他的依赖关系,包括:

  • Spring MVC 框架
  • 嵌入式 Tomcat
  • Thymeleaf 和 Thymeleaf 布局方言

它还带来了 Spring Boot 的自动配置库。当应用程序启动时,Spring Boot 自动配置自动检测这些库并自动执行:

  • 在 Spring 应用程序上下文中配置 bean 以启用 Spring MVC
  • 将嵌入式 Tomcat 服务器配置在 Spring 应用程序上下文中
  • 为使用 Thymeleaf 模板呈现 Spring MV C视图,配置了一个 Thymeleaf 视图解析器

简而言之,自动配置完成了所有繁重的工作,让你专注于编写实现应用程序功能的代码。如果你问我这样好不好,我会说这是一个很好的安排!

你的 Spring 之旅才刚刚开始。Taco Cloud 应用程序只涉及 Spring 提供的一小部分内容。在你开始下一步之前,让我们来俯瞰 Spring 的风景线,看看你在旅途中会遇到什么地标。