2.1 展示信息
2.1 展示信息
从根本上说,
因此,
在
- 一个定义玉米卷成分特性的领域类
- 一个
Spring MVC 控制器类,它获取成分信息并将其传递给视图 - 一个视图模板,在用户的浏览器中呈现一个成分列表
这些组件之间的关系如图
图
由于本章主要讨论
在编写控制器和视图之前,让我们先确定表示配料的域类型。这将为开发
2.1.1 建立域
应用程序的域是它所处理的主题领域 —— 影响应用程序理解的思想和概念。在
在领域中,玉米饼配料是相当简单的对象。每一种都有一个名称和一个类型,这样就可以在视觉上对其进行分类(蛋白质、奶酪、酱汁等
package tacos;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class Ingredient {
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
这是一个普通的Ingredient
类,最不寻常的事情可能是它似乎缺少一组常用的equals()
、hashCode()
、toString()
等有用的方法。
在清单中看不到它们,部分原因是为了节省空间,但也因为使用了一个名为@Data
注释是由Ingredient
的代码保持整洁。
要使用
或者,可以使用
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
此依赖项将在开发时提供@Data
你会发现
2.1.2 创建控制器类
控制器是
对于
- 处理请求路径为
/design
的HTTP GET
请求 - 构建成分列表
- 将请求和成分数据提交给视图模板,以
HTML 的形式呈现并发送给请求的web 浏览器
下面的 DesignTacoController
类处理这些需求。程序清单
package tacos.web;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Taco;
import tacos.Ingredient;
import tacos.Ingredient.Type;
@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {
@GetMapping
public String showDesignForm(Model model) {
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CARN", "Carnitas", Type.PROTEIN),
new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
new Ingredient("LETC", "Lettuce", Type.VEGGIES),
new Ingredient("CHED", "Cheddar", Type.CHEESE),
new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
new Ingredient("SLSA", "Salsa", Type.SAUCE),
new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
);
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
model.addAttribute("design", new Taco());
return "design";
}
}
关于 DesignTacoController
,首先要注意的是在类级应用的一组注释。第一个是 @Slf4j
,它是
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);
稍后您将使用这个 Logger
。
下一个应用到 DesignTacoController
的注释是 @Controller
。此注释用于将该类标识为控制器并将其标记为组件扫描的候选对象,以便DesignTacoController
实例作为
DesignTacoController
也用 @RequestMapping
注释。@RequestMapping
注释在类级应用时,指定该控制器处理的请求的类型。在本例中,它指定 DesignTacoController
将处理路径以 /design
开头的请求。
处理
类级别的 @RequestMapping
注释用于 showDesignForm()
方法时,可以用 @GetMapping
注释进行改进。@GetMapping
与类级别的 @RequestMapping
配对使用,指定何时接收 /design
的showDesignForm()
将用来处理请求。
@GetMapping
是一个相对较新的注释,是在@RequestMapping
注释:
表
注释 | 描述 |
---|---|
@RequestMapping | 通用请求处理 |
@GetMapping | 处理 |
@PostMapping | 处理 |
@PutMapping | 处理 |
@DeleteMapping | 处理 |
@PatchMapping | 处理 |
让正确的事情变得简单
在控制器方法上声明请求映射时,尽可能具体总是一个好主意。至少,这意味着声明一个路径(或者从类级
@RequestMapping
继承一个路径)和它将处理哪个HTTP 方法。长度更长的
@RequestMapping(method=RequestMethod.GET)
使我们很容易采取惰性的方式,同时去掉方法属性。由于Spring 4.3 的新映射注释,正确的做法也很容易做到 —— 只需较少的输入。新的请求映射注释具有与
@RequestMapping
相同的所有属性,因此可以在使用@RequestMapping
的任何地方使用它们。通常,我倾向于只在类级别上使用
@RequestMapping
来指定基本路径。我在每个处理程序方法上使用更具体的@GetMapping
、@PostMapping
等。
现在已经知道 showDesignForm()
方法将处理请求,让我们来看看方法体,看看它是如何工作的。该方法的大部分构造了一个成份对象列表。这个列表现在是硬编码的。当我们讲到第
一旦准备好了原料列表,接下来的几行 showDesignForm()
将根据原料类型过滤该列表。然后将成分类型列表作为属性添加到传递到 showDesignForm()
的模型对象。模型是一个对象,它在控制器和负责呈现数据的视图之间传输数据。最后,放置在模型属性中的数据被复制到showDesignForm()
方法最后返回 “design”,这是将用于向浏览器呈现模型的视图的逻辑名称。
DesignTacoController
真的开始成形了。如果您现在运行应用程序并将您的浏览器指向 /design
路径,DesignTacoController
的 showDesignForm()
将被占用,它从存储库中获取数据并将其放在模型中,然后将请求传递给视图。但是因为还没有定义视图,所以请求会发生可怕的转变,导致
2.1.3 设计视图
控制器创建完成后,就该开始设计视图了。
为了使用<dependency>
条目使用了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在运行时,
像
<p>
<p th:text="${message}">placeholder message</p>
当模板被呈现为<p>
元素的主体将被th:text
是一个${}
操作符告诉它使用请求属性的值(在本例中为 “message”
th:each
,它遍历元素集合,为集合中的每个项目呈现一次
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
在这里,我们在 <div>
标签中填充 th:each
属性,用来对发现于 wrap
请求属性中的集合中的每一个项目进行重复呈现。在每次迭代中,成分项都绑定到一个名为 ingredient
的
在 <div>
元素内部,有一个复选框 <input>
元素和一个 <span>
元素,用于为复选框提供标签。复选框使用th:value
元素,它将把 <iuput>
元素的 value
属性呈现为在成分 id
属性中找到的值。<span>
元素使用 th:text
属性把 “INGREDIENT” 占位符替换为成分 name
属性的值。
当使用实际的模型数据呈现时,这个 <div>
循环迭代一次可能是这样的:
<div>
<input name="ingredients" type="checkbox" value="FLTO" />
<span>Flour Tortilla</span><br />
</div>
最后,前面的
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}" />
<form method="POST" th:object="${design}">
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input
name="ingredients"
type="checkbox"
th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input
name="ingredients"
type="checkbox"
th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input
name="ingredients"
type="checkbox"
th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input
name="ingredients"
type="checkbox"
th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input
name="ingredients"
type="checkbox"
th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}" /><br />
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
可以看到,对于每种类型的配料,都要重复 <div>
片段。还包括一个提交按钮和一个字段,用户可以在其中命名他们的创建。
值得注意的是,完整的模板包括<link>
引用。在这两种情况下,@{}
操作符被用来产生一个上下文相关路径的静态工件,它们正在引用。正如在第
现在控制器和视图已经完成,可以启动应用程序了。运行java -jar
运行这个mvn spring-boot:run
从构建中直接运行应用程序。
无论如何启动
图
它看起来真不错!访问这个玉米饼艺术家呈现形式的网站,包含一个调色板的玉米饼成分,从中他们可以创建自己的杰作。但是当他们点击 Submit Your Taco
按钮时会发生什么呢?
DesignTacoController
还没有准备好接受玉米饼创作的请求。如果提交了设计表单,用户将看到一个错误