2.1.2 创建控制器类

控制器是 Spring MVC 框架的主要参与者。它们的主要工作是处理 HTTP 请求,或者将请求传递给视图以呈现 HTML(浏览器显示),或者直接将数据写入响应体(RESTful)。在本章中,我们将重点讨论使用视图为 web 浏览器生成内容的控制器的类型。在第 6 章中,我们将讨论如何在 REST API 中编写处理请求的控制器。

对于 Taco Cloud 应用程序,需要一个简单的控制器来执行以下操作:

  • 处理请求路径为 /design 的 HTTP GET 请求
  • 构建成分列表
  • 将请求和成分数据提交给视图模板,以 HTML 的形式呈现并发送给请求的 web 浏览器

下面的 DesignTacoController 类处理这些需求。

{% code title=”程序清单 2.2 Spring 控制器类的开始” %}

  1. package tacos.web;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. import java.util.stream.Collectors;
  5. import javax.validation.Valid;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.ui.Model;
  8. import org.springframework.validation.Errors;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.PostMapping;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import lombok.extern.slf4j.Slf4j;
  13. import tacos.Taco;
  14. import tacos.Ingredient;
  15. import tacos.Ingredient.Type;
  16. @Slf4j
  17. @Controller
  18. @RequestMapping("/design")
  19. public class DesignTacoController {
  20. @GetMapping
  21. public String showDesignForm(Model model) {
  22. List<Ingredient> ingredients = Arrays.asList(
  23. new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
  24. new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
  25. new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
  26. new Ingredient("CARN", "Carnitas", Type.PROTEIN),
  27. new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
  28. new Ingredient("LETC", "Lettuce", Type.VEGGIES),
  29. new Ingredient("CHED", "Cheddar", Type.CHEESE),
  30. new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
  31. new Ingredient("SLSA", "Salsa", Type.SAUCE),
  32. new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
  33. );
  34. Type[] types = Ingredient.Type.values();
  35. for (Type type : types) {
  36. model.addAttribute(type.toString().toLowerCase(),
  37. filterByType(ingredients, type));
  38. }
  39. model.addAttribute("design", new Taco());
  40. return "design";
  41. }
  42. // provided by 'aexiaosong'
  43. private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) {
  44. return ingredients.stream().filter(x -> x.getType().equals(type)).collect(Collectors.toList());
  45. }
  46. }

{% endcode %}

关于 DesignTacoController,首先要注意的是在类级应用的一组注释。第一个是 @Slf4j,它是 Lombok 提供的注释,在运行时将自动生成类中的 SLF4J(Java 的简单日志门面,https://www.slf4j.org/)记录器。这个适当的注释具有与显式地在类中添加以下行相同的效果:

  1. private static final org.slf4j.Logger log =
  2. org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);

稍后您将使用这个 Logger。

下一个应用到 DesignTacoController 的注释是 @Controller。此注释用于将该类标识为控制器并将其标记为组件扫描的候选对象,以便 Spring 将发现该类并在 Spring 应用程序上下文中自动创建 DesignTacoController 实例作为 bean。

DesignTacoController 也用 @RequestMapping 注释。@RequestMapping 注释在类级应用时,指定该控制器处理的请求的类型。在本例中,它指定 DesignTacoController 将处理路径以 /design 开头的请求。

处理 GET 请求

类级别的 @RequestMapping 注释用于 showDesignForm() 方法时,可以用 @GetMapping 注释进行改进。@GetMapping 与类级别的 @RequestMapping 配对使用,指定何时接收 /design 的 HTTP GET 请求,showDesignForm() 将用来处理请求。

@GetMapping 是一个相对较新的注释,是在 Spring 4.3 中引入的。在 Spring 4.3 之前,可能使用了一个方法级别的 @RequestMapping 注释:

表 2.1 Spring MVC 请求映射注释

注释 描述
@RequestMapping 通用请求处理
@GetMapping 处理 HTTP GET 请求
@PostMapping 处理 HTTP POST 请求
@PutMapping 处理 HTTP PUT 请求
@DeleteMapping 处理 HTTP DELETE 请求
@PatchMapping 处理 HTTP PATCH 请求

让正确的事情变得简单

在控制器方法上声明请求映射时,尽可能具体总是一个好主意。至少,这意味着声明一个路径(或者从类级 @RequestMapping 继承一个路径)和它将处理哪个 HTTP 方法。

长度更长的 @RequestMapping(method=RequestMethod.GET) 使我们很容易采取惰性的方式,同时去掉方法属性。由于 Spring 4.3 的新映射注释,正确的做法也很容易做到 —— 只需较少的输入。

新的请求映射注释具有与 @RequestMapping 相同的所有属性,因此可以在使用 @RequestMapping 的任何地方使用它们。

通常,我倾向于只在类级别上使用 @RequestMapping 来指定基本路径。我在每个处理程序方法上使用更具体的 @GetMapping、@PostMapping 等。

现在已经知道 showDesignForm() 方法将处理请求,让我们来看看方法体,看看它是如何工作的。该方法的大部分构造了一个成份对象列表。这个列表现在是硬编码的。当我们讲到第 3 章的时候,你会从数据库中找到玉米饼的原料列表。

一旦准备好了原料列表,接下来的几行 showDesignForm() 将根据原料类型过滤该列表。然后将成分类型列表作为属性添加到传递到 showDesignForm() 的 Model 对象。Model 是一个对象,它在控制器和负责呈现数据的视图之间传输数据。最后,放置在 Model 类属性中的数据被复制到 servlet 响应属性中,视图可以在其中找到它们。showDesignForm() 方法最后返回 “design”,这是将用于向浏览器呈现 Model 的视图的逻辑名称。

DesignTacoController 真的开始成形了。如果您现在运行应用程序并将您的浏览器指向 /design 路径,DesignTacoController 的 showDesignForm() 将被占用,它从存储库中获取数据并将其放在 Model 中,然后将请求传递给视图。但是因为还没有定义视图,所以请求会发生可怕的转变,导致 HTTP 404(Not Found)错误。为了解决这个问题,让我们将注意力转移到视图上,其中的数据将用 HTML 进行修饰,并在用户的 web 浏览器中显示。