- Spring实战(第6版)
- (美)克雷格·沃斯
- 5187字
- 2022-12-20 19:14:50
1.3 编写Spring应用
因为我们刚刚开始,所以首先为Taco Cloud应用做一些小的变更,但是这些变更会展现Spring的很多优点。在刚开始的时候,比较合适的做法是为Taco Cloud应用添加一个主页。在添加主页时,我们将会创建两个代码构件:
● 一个控制器类,用来处理主页相关的请求;
● 一个视图模板,用来定义主页看起来是什么样子。
测试是非常重要的,所以我们还会编写一个简单的测试类来测试主页。但是,要事优先,我们需要先编写控制器。
1.3.1 处理Web请求
Spring自带了一个强大的Web框架,名为Spring MVC。Spring MVC的核心是控制器(controller)的理念。控制器是处理请求并以某种方式进行信息响应的类。在面向浏览器的应用中,控制器会填充可选的数据模型并将请求传递给一个视图,以便于生成返回给浏览器的HTML。
在第2章中,我们将会学习更多关于Spring MVC的知识。现在,我们会编写一个简单的控制器类以处理来自根路径(如“/”)的请求,并将这些请求转发至主页视图,在这个过程中不会填充任何的模型数据。程序清单1.4展示了这个简单的控制器类。
程序清单1.4 主页控制器
package tacos;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller ⇽---控制器
public class HomeController {
@GetMapping("/") ⇽---处理对根路径“/ ”的请求
public String home() {
return "home"; ⇽---返回视图名
}
}
可以看到,这个类带有@Controller注解。就其本身而言,@Controller并没有做太多的事情。它的主要目的是让组件扫描将这个类识别为一个组件。因为HomeController带有@Controller注解,所以Spring的组件扫描功能会自动发现它,并创建一个HomeController实例作为Spring应用上下文中的bean。
实际上,有一些其他的注解与@Controller有着类似的目的(包括@Component、@Service和@Repository)。你可以为HomeController添加上述的任意其他注解,其作用是完全相同的。但是,在这里选择使用@Controller更能描述这个组件在应用中的角色。
home()是一个简单的控制器方法。它带有@GetMapping注解,表明如果针对“/”发送HTTP GET请求,那么将会由这个方法来处理请求。该方法所做的只是返回String类型的home值。
这个值将会解析为视图的逻辑名。视图如何实现取决于多个因素,但是Thymeleaf位于类路径中,使得我们可以使用Thymeleaf来定义模板。
为何使用Thymeleaf?
你可能会想:为什么要选择Thymeleaf作为模板引擎?为何不使用JSP?为何不使用FreeMarker?为何不选择其他的几个可选方案呢?
简单来说,我必须要做出选择,我喜欢Thymeleaf,相对于其他的方案,我会优先使用它。即便JSP是更加显而易见的选择,但是组合使用JSP和Spring Boot需要克服一些挑战。我不想脱离第1章的内容定位,所以就此打住。在第2章中,我们会看到其他的模板方案,其中也包括JSP。
模板名称是由逻辑视图名派生而来的,再加上“/templates/”前缀和“.html”后缀。最终形成的模板路径将是“/templates/home.html”。所以,我们需要将模板放到项目的“/src/main/resources/templates/home.html”中。现在,就让我们来创建这个模板。
1.3.2 定义视图
为了让主页尽可能简单,主页除了欢迎用户访问站点之外,不会做其他的任何事情。程序清单1.5展现了基本的Thymeleaf模板,定义了Taco Cloud的主页。
程序清单1.5 Taco Cloud主页模板
<!DOCTYPE html>
<html xmlns = "http://www.w3.org/1999/xhtml"
xmlns:th = "http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
</head>
<body>
<h1>Welcome to...</h1>
<img th:src = "@{/images/TacoCloud.png}"/>
</body>
</html>
这个模板并没有太多需要讨论的。唯一需要注意的是用于展现Taco Cloud Logo的<img>标签。它使用了Thymeleaf的th:src属性和@{...}表达式,以便于引用相对于上下文路径的图片。除此之外,这个主页就是一个扮演“Hello World”角色的页面。
我们再讨论一下这个图片。我将定义Taco Cloud Logo的工作留给你,但是你需要将它放到应用的正确位置。
图片是使用相对于上下文的“/images/TacoCloud.png”路径来引用的。回忆一下我们的项目结构,像图片这样的静态资源位于“/src/main/resources/static”文件夹。这意味着,在项目中Taco Cloud Logo的图片路径必须为“/src/main/resources/static/images/ TacoCloud.png”。
现在,我们有了一个处理主页请求的控制器和渲染主页的模板,基本就可以启动应用来看一下它的效果了。但是,在此之前,我们先看一下如何为控制器编写测试。
1.3.3 测试控制器
在测试Web应用时,对HTML页面的内容进行断言是比较困难的。幸好,Spring对测试提供了强大的支持,这使得测试Web应用变得非常简单。
对于主页来说,我们所编写的测试在复杂性上与主页本身差不多。测试需要针对根路径“/”发送一个HTTP GET请求并期望得到成功结果,其中视图名称为home并且结果内容包含“Welcome to...”。程序清单1.6就能够完成该任务。
程序清单 1.6 针对主页控制器的测试
package tacos;
import static org.hamcrest.Matchers.containsString;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(HomeController.class) ⇽---针对HomeController的Web测试
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc; ⇽---注入MockMvc
@Test
public void testHomePage() throws Exception {
mockMvc.perform(get("/")) ⇽---发起对“/”的GET请求
.andExpect(status().isOk()) ⇽---期望得到HTTP 200
.andExpect(view().name("home")) ⇽---期望得到home视图
.andExpect(content().string( ⇽---期望包含“Welcome to...”
containsString("Welcome to...")));
}
}
对于这个测试,首先注意到的可能就是它使用了与TacoCloudApplicationTests类不同的注解。HomeControllerTest没有使用@SpringBootTest标记,而是添加了@WebMvcTest注解。这是Spring Boot提供的一个特殊测试注解,让这个测试在Spring MVC应用的上下文中执行。更具体来讲,在本例中,它会将HomeController注册到Spring MVC中,这样一来,我们就可以向它发送请求了。
@WebMvcTest同样会为测试Spring MVC应用提供了Spring环境的支持。尽管可以启动一个服务器来进行测试,但是对于我们的场景来说,仿造一下Spring MVC的运行机制就可以。测试类被注入了一个MockMvc,能够让测试实现mockup。
通过testHomePage()方法,我们定义了针对主页想要执行的测试。它首先使用MockMvc对象对“/”(根路径)发起HTTP GET请求。对于这个请求,我们设置了如下的预期:
● 响应应该具备HTTP 200 (OK)状态;
● 视图的逻辑名称应该是home;
● 渲染后的视图应该包含文本“Welcome to....”。
我们可以在所选的IDE中运行测试,也可以使用如下的Maven命令:
$ mvnw test
如果在MockMvc对象发送请求之后,上述预期没有全部满足,那么这个测试会失败。但是,我们的控制器和视图模板在编写时都满足了这些预期,所以测试应该能够通过,并且带有成功的图标——至少能够看到一些绿色的背景,表明测试通过了。
控制器已经编写好了,视图模板也已经创建完毕,而且我们还通过了测试。看上去,我们已经成功实现了主页。但是,尽管测试已经通过了,但是如果能够在浏览器中看到结果,会更有成就感。毕竟,这才是Taco Cloud的客户所能看到的效果。接下来,我们构建应用并运行它。
1.3.4 构建和运行应用
就像初始化Spring应用有多种方式一样,运行Spring应用也有多种方式。你如果愿意,可以翻到本书附录部分,了解运行Spring Boot应用的一些通用方式。
因为我们选择了使用Spring Tool Suite来初始化和管理项目,所以可以借助名为Spring Boot Dashboard的便捷功能来帮助我们在IDE中运行应用。Spring Boot Dashboard的表现形式是一个Tab,通常会位于IDE窗口的左下角附近。图1.7展现了一个带有标注的Spring Boot Dashboard截屏。
图1.7 Spring Boot Dashboard的重点功能
图1.7包含了一些有用的细节,但是我不想花太多时间介绍Spring Boot Dashboard支持的所有功能。对我们来说,现在最重要的事情是需要知道如何使用它来运行Taco Cloud应用。确保taco-cloud应用程序在项目列表中能够显示(这是图1.7中显示的唯一应用),然后单击启动按钮(最左边的按钮,也就是带有绿色三角形和红色正方形的按钮)。应用程序应该就能立即启动。
在应用启动的过程中,你会在控制台看到一些Spring ASCII码,随后会是描述应用启动各个步骤的日志条目。在控制台输出的最后,你将会看到一条日志显示Tomcat已经在port(s): 8080 (http)启动,这意味着此时可以打开Web浏览器并导航至主页,看到我们的劳动成果。
稍等一下!刚才说启动Tomcat?我们是什么时候将应用部署到Tomcat Web服务器的呢?
Spring Boot应用的习惯做法是将所有它所需要的东西都放到一起,没有必要将其部署到某种应用服务器中。在这个过程中,我们根本没有将应用部署到Tomcat中——Tomcat是我们应用的一部分!(在1.3.6小节,我会详细描述Tomcat是如何成为我们应用的一部分的。)
现在,应用已经启动起来了,打开Web浏览器并访问http://localhost:8080 (或者在Spring Boot Dashboard中点击地球样式的按钮),你将会看到如图1.8所示的界面。如果你设计了自己的Logo图片,显示效果可能会有所不同。但是,跟图1.8相比,应该不会有太大的差异。
图1.8 Taco Cloud主页
看上去,似乎并不太美观,但本书不是关于平面设计的,略显简陋的主页外观已经足够了。
到现在为止,我一直没有提及DevTools。在初始化项目的时候,我们将其作为一个依赖添加了进来。在最终生成的pom.xml文件中,它表现为一个依赖项。甚至Spring Boot Dashboard都显示项目启用了DevTools。那么,DevTools到底是什么,又能为我们做些什么呢?接下来,让我们快速浏览一下DevTool最有用的一些特性。
1.3.5 了解Spring Boot DevTools
顾名思义,DevTools为Spring开发人员提供了一些便利的开发期工具和特性,其中包括:
● 代码变更后应用会自动重启;
● 当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器;
● 自动禁用模板缓存;
● 如果使用H2数据库,则内置了H2控制台。
需要注意,DevTools并不是IDE插件,也不需要你使用特定的IDE。在Spring Tool Suite、IntelliJ IDEA和NetBeans中,它都能很好地运行。另外,因为它的用途仅仅是开发,所以它能够很智能地在生产环境中把自己禁用掉。我们将会在第18章讨论它是如何做到这一点的。现在,我们主要关注Spring Boot DevTools最有用的特性,那么先从应用的自动重启开始吧。
应用自动重启
如果将DevTools作为项目的一部分,那么你可以看到,当对项目中的Java代码和属性文件作出修改后,这些变更稍后就能发挥作用。DevTools会监控变更,在看到变化的时候自动重启应用。
更准确地说,当DevTools启用的时候,应用程序会加载到Java虚拟机(Java Virtual Machine,JVM)中的两个独立的类加载器中。其中一个类加载器会加载Java代码、属性文件,以及项目的“src/main/”路径下几乎所有的内容。这些条目很可能会经常发生变化。另外一个类加载器会加载依赖的库,这些库不太可能经常发生变化。
当探测到变更的时候,DevTools只会重新加载包含项目代码的类加载器,并重启Spring的应用上下文,在这个过程中,另外一个类加载器和JVM会原封不动。这个策略非常精细,但能减少应用启动的时间。
这种策略的一个不足之处就是自动重启无法反映依赖项的变化。这是因为包含依赖库的类加载器不会自动重新加载。这意味着每当在构建规范中添加、变更或移除依赖的时候,为了让变更生效,都要重新启动应用。
浏览器自动刷新和禁用模板缓存
默认情况下,像Thymeleaf和FreeMarker这样的模板方案在配置时,会缓存模板解析的结果,这样一来,在为每个请求提供服务的时候,模板就不用重新解析了。在生产环境中,这是一种很好的方式,因为它会带来一定的性能收益。
但是,在开发期,缓存模板就不太友好了。在应用运行的时候,如果缓存模板,刷新浏览器就无法看到模板变更的效果了。即便我们对模板做了修改,在应用重启之前,缓存的模板依然会有效。
DevTools通过禁用所有模板缓存解决了这个问题。你可以对模板进行任意数量的修改,只需刷新一下浏览器就能看到结果。
如果你像我一样,连浏览器的刷新按钮都懒得点,希望在对代码做出变更之后马上就能在浏览器中看到结果,那么很幸运,DevTools有一些特殊的功能可以供我们使用。
DevTools会和你的应用程序一起,自动启动一个LiveReload服务器。LiveReload服务器本身并没有太大的用处。但是,当它与LiveReload浏览器插件结合起来的时候,就能够在模板、图片、样式表、JavaScript等(实际上,几乎涵盖为浏览器提供服务的所有内容)发生变化的时候,自动刷新浏览器。
LiveReload有针对Google Chrome、Safari和Firefox的浏览器插件(这里要对Internet Explorer和Edge的支持者说声抱歉)。请访问LiveReload网站的Extensions页面了解如何为你的浏览器安装LiveReload。
内置的H2控制台
虽然我们的项目还没有使用数据库,但是这种情况在第3章中就会发生变化。如果你使用H2数据库进行开发,DevTools将会自动启用H2控制台,这样一来,我们可以通过Web浏览器进行访问。只需要让浏览器访问http://localhost:8080/h2-console,就能看到应用所使用的数据。
此时,我们已经编写了一个非常简单却很完整的Spring应用。在本书接下来的章节中,我们将会不断扩展它。但现在,要回过头来看一下我们都完成了哪些工作、Spring发挥了什么作用。
1.3.6 回顾一下
回想一下我们是怎样完成这一切的。简短来说,在构建基于Spring的Taco Cloud应用的过程中,我们执行了如下步骤:
● 使用Spring Initializr创建初始的项目结构;
● 编写控制器类处理针对主页的请求;
● 定义了一个视图模板来渲染主页;
● 编写了一个简单的测试类来验证工作符合预期。
这些步骤都非常简单直接,对吧?除了初始化应用的第一个步骤之外,我们所做的每一个操作都专注于生成主页的目标。
实际上,我们所编写的每行代码都致力于实现这个目标。除了Java import语句之外,我只能在控制器中找到两行Spring相关的代码,而在视图模板中,一行Spring相关的代码都没有。尽管测试类的大部分内容都使用了Spring对测试的支持,但是它在测试的运行环境中,似乎没有那么强的侵入性。
这是使用Spring进行开发的一个重要优势。你可以只关注满足应用需求的代码,无须考虑如何满足框架的需求。尽管我们偶尔还是需要编写一些框架特定的代码,但是它们通常只占整个代码库很小的一部分。正如我在前文所述,Spring(以及Spring Boot)可以视为感受不到框架的框架(frameworkless framework)。
但是,这一切到底是如何运行起来的呢?Spring在幕后做了些什么来保证应用的需求能够得到满足?要理解Spring到底做了些什么,我们首先来看一下构建规范。
在pom.xml文件中,我们声明了对Web和Thymeleaf starter的依赖。这两项依赖会传递引入大量其他的依赖,包括:
● Spring的MVC框架;
● 嵌入式的Tomcat;
● Thymeleaf和Thymeleaf布局方言。
它还引入了Spring Boot的自动配置库。当应用启动的时候,Spring Boot的自动配置将会探测到这些库,并自动完成如下功能:
● 在Spring应用上下文中配置bean以启用Spring MVC;
● 在Spring应用上下文中配置嵌入式的Tomcat服务器;
● 配置Thymeleaf视图解析器以便于使用Thymeleaf模板渲染Spring MVC视图。
简言之,自动配置功能完成了所有的脏活累活,让我们能够集中精力编写实现应用功能的代码。如果你问我的观点,我认为这是一个很好的安排!
我们的Spring之旅才刚刚开始。Taco Cloud应用程序只涉及了Spring所提供功能的一小部分。在开始下一步之前,我们先整体了解一下Spring,看看在我们的路途中都会有哪些地标。