2.1 展现信息
2.1 展现信息
从根本上来讲,Taco Cloud是一个可以在线订购taco的地方。但是,除此之外,Taco Cloud允许客户展现其创意,能够让他们通过丰富的配料(ingredient)设计自己的taco。
因此,Taco Cloud需要有一个页面为taco艺术家展现都可以选择哪些配料。可选的配料可能随时会发生变化,所以不能将它们硬编码到HTML页面中。我们应该从数据库中获取可用的配料并将其传递给页面,进而展现给客户。
在Spring Web应用中,获取和处理数据是控制器的任务,而将数据渲染到HTML中并在浏览器中展现则是视图的任务。为了支撑taco的创建页面,我们需要构建如下组件。
- 用来定义taco配料属性的领域类。
- 用来获取配料信息并将其传递至视图的Spring MVC控制器类。
- 用来在用户的浏览器中渲染配料列表的视图模板。
这些组件之间的关系如图2.1所示。
因为本章主要关注Spring的Web框架,所以我们会将数据库相关的内容放到第3章中进行讲解。现在的控制器只负责向视图提供配料。在第3章中,我们会重新改造这个控制器,让它能够与repository协作,从数据库中获取配料数据。
在编写控制器和视图之前,我们首先确定一下用来表示配料的领域类型,它会为我们开发Web组件奠定基础。
2.1.1 构建领域类
应用的领域指的是它所要解决的主题范围:也就是会影响到对应用理解的理念和概念[^1]。在Tao Cloud应用中,领域对象包括taco设计、组成这些设计的配料、顾客以及顾客所下的订单。作为开始,我们首先关注taco的配料。
在我们的领域中,taco配料是非常简单的对象。每种配料都有一个名称和类型,以便于对其进行可视化的分类(蛋白质、奶酪、酱汁等)。每种配料还有一个ID,这样的话对它的引用就能非常容易和明确。如下的Ingredient类定义了我们所需的领域对象。
程序清单2.1 定义taco配料
1 | package tacos; |
我们可以看到,这是一个非常普通的Java领域类,它定义了描述配料所需的3个属性。在程序清单2.1中,Ingredient类最不寻常的一点是它似乎缺少了常见的getter和setter方法,以及equals()、hashCode()、toString()等方法。
在程序清单2.1中没有这些方法,部分原因是节省空间,此外还因为我们使用了名为Lombok的库(这是一个非常棒的库,它能够在运行时动态生成这些方法)。实际上,类级别的@Data
注解就是由Lombok提供的,它会告诉Lombok生成所有缺失的方法,同时还会生成所有以final属性作为参数的构造器。通过使用Lombok,我们能够让Ingredient的代码简洁明了。
Lombok并不是Spring库,但是它非常有用,我发现如果没有它,开发工作将很难开展。当我需要在书中将代码示例编写得短小简洁时,它简直成了我的救星。
要使用Lombok,首先要将其作为依赖添加到项目中。如果你使用Spring ToolSuite,那么只需要用右键点击pom.xml,并从Spring上下文菜单选项中选择“Edit Starters”。在第1章中看到的选择依赖的对话框将会再次出现(见图1.4),这样的话我们就有机会添加依赖或修改已选择的依赖了。找到Lombok选项,并确保它处于已选中的状态,然后点击“OK”,Spring Tool Suite会自动将其添加到构建规范中。
另外,你也可以在pom.xml中通过如下条目进行手动添加:
1 | <dependency> |
这个依赖将会在开发阶段为你提供Lombok注解(例如@Data),并且会在运行时进行自动化的方法生成。但是,我们还需要将Lombok作为扩展添加到IDE上,否则IDE将会报错,提示缺少方法和final属性没有赋值。参见Lombok项目页面,以查阅如何在你所选择的IDE上安装Lombok。
我相信你会发现Lombok非常有用,但你也要知道,它是可选的。在开发Spring应用的时候,它并不是必备的,所以如果你不想使用它的话,完全可以手动编写这些缺失的方法。当你完成之后,我们将会在应用中添加一些控制器,让它们来处理Web请求。
2.1.2 创建控制器类
在Spring MVC框架中,控制器是重要的参与者。它们的主要职责是处理HTTP请求,要么将请求传递给视图以便于渲染HTML(浏览器展现),要么直接将数据写入响应体(RESTful)。在本章中,我们将会关注使用视图来为Web浏览器生成内容的控制器。在第6章中,我们将会看到如何以REST API的形式编写控制器来处理请求。
对于Taco Cloud应用来说,我们需要一个简单的控制器,它要完成如下功能。
- 处理路径为“/design”的HTTP GET请求。
- 构建配料的列表。
- 处理请求,并将配料数据传递给要渲染为HTML的视图模板,发送给发起请求的Web浏览器。
程序清单2.2中的DesignTacoController类解决了这些需求。
程序清单2.2 初始的Spring控制器类
1 | package tacos.web; |
对于DesignTacoController,我们先要注意在类级别所应用的注解。首先是@Slf4j
,这是Lombok所提供的注解,在运行时,它会在这个类中自动生成一个SLF4J(Simple Logging Facade for Java)Logger。这个简单的注解和在类中通过如下代码显式声明的效果是一样的:
1 | private static final org.slf4j.Logger log = |
随后,我们将会用到这个Logger。
DesignTacoController用到的下一个注解是@Controller。
这个注解会将这个类识别为控制器,并且将其作为组件扫描的候选者,所以Spring会发现它并自动创建一个DesignTacoController实例,并将该实例作为Spring应用上下文中的bean。
DesignTacoController还带有@RequestMapping
注解。当@RequestMapping
注解用到类级别的时候,它能够指定该控制器所处理的请求类型。在本例中,它规定DesignTacoController将会处理路径以“/design”开头的请求。
处理GET请求
修饰showDesignForm()方法的@GetMapping
注解对类级别的@RequestMapping
进行了细化。@GetMapping
结合类级别的@RequestMapping,
指明当接收到对“/design”的HTTP GET请求时,将会调用showDesignForm()来处理请求。
@GetMapping是一个相对较新的注解,是在Spring 4.3引入的。在Spring 4.3之前,你可能需要使用方法级别的@RequestMapping
注解作为替代:
1 |
显然,@GetMapping
更加简洁,并且指明了它的目标HTTP方法。@GetMapping
只是诸多请求映射注解中的一个。表2.1列出了Spring MVC中所有可用的请求映射注解。
表2.1 Spring MVC的请求映射注解
让正确的事情变得更容易
在为控制器方法声明请求映射时,越具体越好。这意味着至少要声明路径(或者从类级别的@RequestMapping
继承一个路径)以及它所处理的HTTP方法。
但是更长的@RequestMapping(method=RequestMethod.GET)
注解很容易让开发人员采取懒惰的方式,也就是忽略掉method属性。幸亏有了Spring 4.3的新注解,正确的事情变得更容易了,我们的输入变得更少了。
新的请求映射注解具有和@RequestMapping
完全相同的属性,所以我们可以在使用@RequestMapping
的任何地方使用它们。
通常,我喜欢只在类级别上使用@RequestMapping,
以便于指定基本路径。在每个处理器方法上,我会使用更具体的@GetMapping
、@PostMapping
等注解。
现在,我们已经知道showDesignForm()方法会处理请求,接下来我们看一下方法体,看它都做了些什么工作。这个方法构建了一个Ingredient对象的列表。现在,这个列表是硬编码的。当我们学习第3章的时候,会从数据库中获取可用taco配料并将其放到列表中。
配料列表准备就绪之后,showDesignForm()方法接下来的几行代码会根据配料类型过滤列表。配料类型的列表会作为属性添加到Model对象上,这个对象是以参数的形式传递给showDesignForm()方法的。Model对象负责在控制器和展现数据的视图之间传递数据。实际上,放到Model属性中的数据将会复制到ServletResponse的属性中,这样视图就能在这里找到它们了。showDesignForm()方法最后返回“design”,这是视图的逻辑名称,会用来将模型渲染到视图上。
我们的DesignTacoController已经具备雏形了。如果你现在运行应用并在浏览器上访问“/design”路径,DesignTacoController的showDesignForm()将会被调用,它会从repository中获取数据并放到模型中,然后将请求传递给视图。但是,我们现在还没有定义视图,请求将会遇到很糟糕的问题,也就是HTTP 404(NotFound)。为了解决这个问题,我们将注意力切换到视图上,在这里数据将会使用HTML进行装饰,以便于在用户的Web浏览器中进行展现。
2.1.3 设计视图
在控制器完成它的工作之后,现在就该视图登场了。Spring提供了多种定义视图的方式,包括JavaServer Pages(JSP)、Thymeleaf、FreeMarker、Mustache和基于Groovy的模板。就现在来讲,我们会使用Thymeleaf,这也是我们在第1章开启这个项目时的选择。我们会在第2.5节考虑其他的可选方案。
为了使用Thymeleaf,我们需要添加另外一个依赖到项目构建中。如下的<dependency>
条目使用了Spring Boot的Thymeleaf starter,从而能够让Thymeleaf渲染我们将要创建的视图:
1 | <dependency> |
在运行时,Spring Boot的自动配置功能会发现Thymeleaf在类路径中,因此会为Spring MVC创建支撑Thymeleaf视图的bean。
像Thymeleaf这样的视图库在设计时是与特定的Web框架解耦的。这样的话,它们无法感知Spring的模型抽象,因此无法与控制器放到Model中的数据协同工作。但是,它们可以与Servlet的request属性协作。所以,在Spring将请求转移到视图之前,它会把模型数据复制到request属性中,Thymeleaf和其他的视图模板方案就能访问到它们了。
Thymeleaf模板就是增加一些额外元素属性的HTML,这些属性能够指导模板如何渲染request数据。举例来说,如果有一个请求属性的key为“message”,我们想要使用Thymeleaf将其渲染到一个HTML <p>
标签中,那么在Thymeleaf模板中我们可以这样写:
1 | <p th:text="${message}">placeholder message</p> |
当模板渲染成HTML的时候,<p>
元素体将会被替换为Servlet Request中key为“message”的属性值。“th:text”是Thymeleaf命名空间中的属性,它会执行这个替换过程。${}会告诉它要使用某个请求属性(在本例中,也就是“message”)中的值。
Thymeleaf还提供了一个属性“th:each”,它会迭代一个元素集合,为集合中的每个条目渲染HTML。在我们设计视图展现模型中的配料列表时,这就非常便利了。举例来说,如果只想渲染“wrap”配料的列表,我们可以使用如下的HTML片段:
1 | <h3>Designate your wrap:</h3> |
在这里,我们在<div>
标签中使用th:each
属性,这样的话就能针对wrap request属性所对应集合中的每个元素重复渲染<div>
了。在每次迭代的时候,配料元素都会绑定到一个名为ingredient的Thymeleaf变量上。
在<div>
元素中,有一个<input>
复选框元素,还有一个为复选框提供标签的<span>
元素。复选框使用Thymeleaf
的th:value
来为渲染出的<input>
元素设置value
属性,这里会将其设置为所找到的ingredient
的id
属性。<span>
元素使用th:text
将“INGREDIENT
”占位符文本替换为ingredient
的name
属性。
当用实际的模型数据进行渲染的时候,其中一个<div>
迭代的渲染结果可能会如下所示:
1 | <div> |
最终,上述的Thymeleaf片段会成为一大段HTML表单的一部分,我们taco艺术家用户会通过这个表单来提交其美味的作品。完整的Thymeleaf模板会包括所有的配料类型,表单如程序清单2.3所示:
程序清单2.3 设计taco的完整页面
1 |
|
可以看到,我们会为各种类型的配料重复定义<div>
片段。另外,我们还包含了一个Submit
按钮,以及用户用来定义其作品名称的输入域。
值得注意的是,完整的模板包含了一个Taco Cloud的logo图片以及对样式表的<link>
引用[^2]。在这两个场景中,都使用了Thymeleaf的@{}
操作符,用来生成一个相对上下文的路径,以便于引用我们需要的静态制件(artifact)。正如我们在第1章中所学到的,在Spring Boot应用中,静态内容要放到根路径的“/static”目录下。
我们的控制器和视图已经完成了,现在我们可以将应用启动起来,看一下我们的劳动成果。运行Spring Boot应用有很多种方式。在第1章中,我为你首先展示了如何将应用构建成一个可执行的JAR文件,然后通过java –jar命令来运行这个JAR。我还展示了如何直接通过mvn spring-boot:run构建命令来运行应用。
不管你采用哪种方式来启动Taco Cloud应用,在启动之后都可以通过 http://localhost:8080/design 来进行访问。你将会看到如图2.2所示的一个页面。
这看上去非常不错!访问你站点的taco艺术家可以看到一个表单,这个表单中包含了各种taco配料,他们可以使用这些配料创建自己的杰作。但是当他们点击Submit Your Taco按钮的时候会发生什么呢?
我们的DesignTacoController还没有为接收创建taco的请求做好准备。如果提交设计表单,用户就会遇到一个错误(具体来讲,将会是一个HTTP 405错误:Request Method “POST” Not Supported)。接下来,我们通过编写一些处理表单提交的控制器代码来修正这个错误。
[^1]: 如果你想更深入地了解应用领域,我推荐你阅读Eric Evans的《领域驱动设计》(ISBN978-7-115-37675-6,人民邮电出版社出版)。
[^2]: 样式表的内容与我们的讨论无关,它只是包含了让配料两列显示的样式,避免出现一个很长的配料列表。