> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v5/di-2-zhang-kai-fa-web-ying-yong-cheng-xu/2.2-chu-li-biao-dan-ti-jiao.md).

# 2.2 处理表单提交

如果在视图中查看 `<form>` 标签，可以看到它的 `method` 属性被设置为 POST。而且，`<form>` 没有声明 action 属性。这意味着在提交表单时，浏览器将收集表单中的所有数据，并通过 HTTP POST 请求将其发送到服务器，发送到显示表单的 GET 请求的同一路径 —— `/design` 路径。

因此，需要在该 POST 请求的接收端上有一个控制器处理程序方法。需要在 DesignTacoController 中编写一个新的处理程序方法来处理 `/design` 接口的 POST 请求。

在程序清单 2.2 中，使用 @GetMapping 注释指定 showDesignForm() 方法应该处理 HTTP GET 请求 `/design`。与 @GetMapping 处理 GET 请求一样，可以使用 @PostMapping 处理 POST 请求。为了处理玉米饼艺术家提交的设计，将以下程序清单中的 processDesign() 方法添加到 DesignTacoController 中。

{% code title="程序清单 2.4 使用 @PostMapping 处理 POST 请求" %}

```java
@PostMapping
public String processDesign(Taco design) {
    // Save the taco design...
    // We'll do this in chapter 3
    log.info("Processing design: " + design);
    return "redirect:/orders/current";
}
```

{% endcode %}

当应用到 processDesign() 方法时，@PostMapping 与类级别 @RequestMapping 相协调，以表明 processDesign() 应该处理 `/design` 接口的 POST 请求。这正是需要处理的一个玉米饼艺术家提交的作品。

提交表单时，表单中的字段被绑定到 Taco 对象的属性（其类在下一个程序清单中显示），该对象作为参数传递给 processDesign()。从这里开始，processDesign() 方法可以对 Taco 对象做任何它想做的事情。

{% code title="程序清单 2.5 域对象定义玉米饼设计" %}

```java
package tacos;
​
import java.util.List;
import lombok.Data;
​
@Data
public class Taco {
    private String name;
    private List<String> ingredients;
}
```

{% endcode %}

Taco 是一个简单的 Java 域对象，具有两个属性。与 Ingredient 类似，Taco 类也使用 @Data 进行注释，以便在运行时自动生成基本的 JavaBean 方法。

如果查看程序清单 2.3 中的表单，将看到几个 checkbox 元素，它们都带有 ingredients 名称和一个名为 name 的文本输入元素。表单中的这些字段直接对应于 Taco 类的 ingredients 和 name 属性。

表单上的 Name 字段只需要捕获一个简单的文本值。因此 Taco 的 name 属性的类型是 String。配料复选框也有文本值，但是因为可能选择了零个或多个配料，所以它们绑定到的 ingredients 属性是一个 `List<String>`，它将捕获每个选择的配料。

目前，processDesign() 方法对 Taco 对象没有任何作用。事实上，它什么都做不了。没关系。在第 3 章中，将添加一些持久性逻辑，将提交的 Taco 保存到数据库中。

与 showDesignForm() 方法一样，processDesign() 通过返回一个 String 结束。与 showDesignForm() 类似，返回的值指示将显示给用户的视图。但是不同的是，从 processDesign() 返回的值的前缀是 “redirect:”，表示这是一个重定向视图。更具体地说，它表明在 processDesign() 完成之后，用户的浏览器应该被重定向到相对路径 /order/current。

这样做的想法源于，在创建了一个玉米饼之后，用户将被重定向到一个订单表单，他们可以从该表单下订单，以交付他们的玉米饼。但是还没有一个控制器来处理 /orders/current 请求。

根据现在对 @Controller、@RequestMapping 和 @GetMapping 的了解，可以轻松地创建这样的控制器。它可能类似于下面的清单。

{% code title="程序清单 2.6 展现玉米饼订单表单的控制器" %}

```java
package tacos.web;
​
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.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Order;
​
@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
    @GetMapping("/current")
    public String orderForm(Model model) {
        model.addAttribute("order", new Order());
        return "orderForm";
    }
}
```

{% endcode %}

同样，可以使用 Lombok 的 @Slf4j 注释在运行时创建一个 SLF4J Logger 对象。稍后，将使用这个 Logger 来记录提交的订单的详细信息。

类级别的 @RequestMapping 指定该控制器中的任何请求处理方法都将处理路径以 /orders 开头的请求。当与方法级 @GetMapping 结合使用时，它指定 orderForm() 方法将处理 /orders/current 的 HTTP GET 请求。

至于 orderForm() 方法本身，它非常简单，只返回 orderForm 的逻辑视图名。在第 3 章中，一旦有了把创建的 taco 持久化到数据库的方法，将重新访问该方法并修改它，以使用 taco 对象的列表填充模型，这些对象将按顺序放置。

orderForm 视图由一个名为 orderForm.html 的 Thymeleaf 模板提供，如下面显示的。

{% code title="程序清单 2.7 taco 订单表单视图" %}

```markup
<!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>
        <form method="POST" th:action="@{/orders}" th:object="${order}">
            <h1>Order your taco creations!</h1>
            <img th:src="@{/images/TacoCloud.png}"/>
            <a th:href="@{/design}" id="another">Design another taco</a><br/>
            <div th:if="${#fields.hasErrors()}">
                <span class="validationError">
                    Please correct the problems below and resubmit.
                </span>
            </div>
            <h3>Deliver my taco masterpieces to...</h3>

            <label for="name">Name: </label>
            <input type="text" th:field="*{name}"/>
            <br/>

            <label for="street">Street address: </label>
            <input type="text" th:field="*{street}"/>
            <br/>

            <label for="city">City: </label>
            <input type="text" th:field="*{city}"/>
            <br/>

            <label for="state">State: </label>
            <input type="text" th:field="*{state}"/>
            <br/>

            <label for="zip">Zip code: </label>
            <input type="text" th:field="*{zip}"/>
            <br/>

            <h3>Here's how I'll pay...</h3>

            <label for="ccNumber">Credit Card #: </label>
            <input type="text" th:field="*{ccNumber}"/>
            <br/>

            <label for="ccExpiration">Expiration: </label>
            <input type="text" th:field="*{ccExpiration}"/>
            <br/>

            <label for="ccCVV">CVV: </label>
            <input type="text" th:field="*{ccCVV}"/>
            <br/>

            <input type="submit" value="Submit order"/>
        </form>
    </body>
</html>
```

{% endcode %}

在大多数情况下，orderForm.html 视图是典型的 HTML/Thymeleaf 内容，没有什么值得注意的。但是注意，这里的 \<form> 标记与程序清单 2.3 中使用的 \<form> 标记不同，因为它还指定了一个表单操作。如果没有指定操作，表单将向呈现表单的相同 URL 提交 HTTP POST 请求。但是在这里，指定表单应该提交到 /orders（使用 Thymeleaf 的 `@{…}` 操作符作为上下文相关路径）。

因此，需要添加另外一个方法到 OrderController 类中，去处理 /orders 接口的 POST 请求。在进行到下一章之前，还没有办法将订单持久化，因此在这里简化它 —— 类似于在下一个程序清单中看到的内容。

{% code title="程序清单 2.8 处理 taco 订单提交" %}

```java
@PostMapping
public String processOrder(Order order) {
    log.info("Order submitted: " + order);
    return "redirect:/";
}
```

{% endcode %}

当调用 processOrder() 方法来处理提交的订单时，它将获得一个 order 对象，其属性绑定到提交的表单字段。Order 非常像 Taco，是一个相当简单的类，它携带订单信息。

{% code title="程序清单 2.9 taco 订单域对象" %}

```java
package tacos;
​
import javax.validation.constraints.Digits;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import org.hibernate.validator.constraints.NotBlank;
import lombok.Data;
​
@Data
public class Order {
    private String name;
    private String street;
    private String city;
    private String state;
    private String zip;
    private String ccNumber;
    private String ccExpiration;
    private String ccCVV;
}
```

{% endcode %}

现在已经开发了一个 OrderController 和 order 表单视图，可以开始尝试运行了。打开浏览器访问 <http://localhost:8080/design>，为你的玉米饼选择一些原料，然后点击 `Submit Your Taco` 按钮。应该会看到类似于图 2.3 所示的表单。

![图 2.3 taco 订单表单](/files/-MO1GhvxAoT2sOb-qS1G)

在表单中填写一些字段，然后按 `Submit Order` 按钮。与此同时，请密切关注应用程序日志，以查看订单信息。当我尝试它，日志条目看起来像这样（重新格式化以适应这个页面的宽度）：

```
Order submitted: Order(name=Craig Walls,street1=1234 7th Street,
    city=Somewhere, state=Who knows?, zip=zipzap, ccNumber=Who can guess?,
ccExpiration=Some day, ccCVV=See-vee-vee)
```

如果仔细查看来自测试订单的日志条目，可以看到，虽然 processOrder() 方法完成了它的工作并处理了表单提交，但是它让一些错误的信息进来了。表单中的大多数字段包含的数据可能是不正确的。需要添加一些验证，以确保提供的数据至少与所需的信息类型相似。
