# 5.4.1　编写处理表单的控制器

当处理注册表单的 POST 请求时，控制器需要接受表单数据并将表单数据保存为 Spitter 对象。最后，为了防止重复提交（用户点击浏览器的刷新按钮有可能会发生这种情况），应该将浏览器重定向到新创建用户的基本信息页面。这些行为通过下面的 shouldProcessRegistration() 进行了测试。

```java
@Test
public void shouldProcessRegistration() throws Exception {
  SpitterRepository mockRepository = mock(SpitterRepository.class);
  Spitter unsaved = new Spitter("jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov");
  Spitter saved = new Spitter(24L, "jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov");
  when(mockRepository.save(unsaved)).thenReturn(saved);
    
  SpitterController controller = new SpitterController(mockRepository);
  MockMvc mockMvc = standaloneSetup(controller).build();

  mockMvc.perform(post("/spitter/register")
         .param("firstName", "Jack")
         .param("lastName", "Bauer")
         .param("username", "jbauer")
         .param("password", "24hours")
         .param("email", "jbauer@ctu.gov"))
         .andExpect(redirectedUrl("/spitter/jbauer"));
    
  verify(mockRepository, atLeastOnce()).save(unsaved);
}
```

显然，这个测试比展现注册表单的测试复杂得多。在构建完 SpitterRepository 的 mock 实现以及所要执行的控制器和 MockMvc 之后，shouldProcess-Registration() 对 `/spitter/register` 发起了一个 POST 请求。作为请求的一部分，用户信息以参数的形式放到 request 中，从而模拟提交的表单。

在处理 POST 类型的请求时，在请求处理完成后，最好进行一下重定向，这样浏览器的刷新就不会重复提交表单了。在这个测试中，预期请求会重定向到`/spitter/jbauer`，也就是新建用户的基本信息页面。

最后，测试会校验 SpitterRepository 的 mock 实现最终会真正用来保存表单上传入的数据。

现在，我们来实现处理表单提交的控制器方法。通过 shouldProcess-Registration() 方法，我们可能认为要满足这个需求需要做很多的工作。但是，在如下的程序清单中，我们可以看到新的 SpitterController 并没有做太多的事情。

```java
package spittr.web;

import static org.springframework.web.bind.annotation.RequestMethod.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import spittr.Spitter;
import spittr.data.SpitterRepository;

@Controller
@RequestMapping("/spitter")
public class SpitterController {

  private SpitterRepository spitterRepository;

  @Autowired
  public SpitterController(SpitterRepository spitterRepository) {
    this.spitterRepository = spitterRepository;
  }
  
  @RequestMapping(value="/register", method=GET)
  public String showRegistrationForm() {
    return "registerForm";
  }
  
  @RequestMapping(value="/register", method=POST)
  public String processRegistration(Spitter spitter) {    
    spitterRepository.save(spitter);
    return "redirect:/spitter/" + spitter.getUsername();
  }
  
}
```

我们之前创建的 showRegistrationForm() 方法依然还在，不过请注意新创建的 processRegistration() 方法，它接受一个 Spitter 对象作为参数。这个对象有firstName、lastName、username 和 password 属性，这些属性将会使用请求中同名的参数进行填充。

当使用 Spitter 对象调用 processRegistration() 方法时，它会进而调用 Spitter-Repository 的 save() 方法，SpitterRepository 是在 SpitterController 的构造器中注入进来的。

processRegistration() 方法做的最后一件事就是返回一个 String 类型，用来指定视图。但是这个视图格式和以前我们所看到的视图有所不同。这里不仅返回了视图的名称供视图解析器查找目标视图，而且返回的值还带有重定向的格式。

当 InternalResourceViewResolver 看到视图格式中的 `redirect:` 前缀时，它就知道要将其解析为重定向的规则，而不是视图的名称。在本例中，它将会重定向到用户基本信息的页面。例如，如果 Spitter.username 属性的值为`jbauer`，那么视图将会重定向到 `/spitter/jbauer`。

需要注意的是，除 了 `redirect:`，InternalResourceViewResolver 还能识别`forward:` 前缀。当它发现视图格式中以 `forward:` 作为前缀时，请求将会前往（forward）指定的 URL 路径，而不再是重定向。

万事俱备！现在，程序清单 5.16 中的测试应该能够通过了。但是，我们的任务还没有完成，因为我们重定向到了用户基本信息页面，那么我们应该往 Spitter-Controller 中添加一个处理器方法，用来处理对基本信息页面的请求。如下的 showSpitterProfile() 将会完成这项任务：

```java
@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
  Spitter spitter = spitterRepository.findByUsername(username);
  model.addAttribute(spitter);
  return "profile";
}
```

SpitterRepository 通过用户名获取一个 Spitter 对象，showSpitterProfile() 得到这个对象并将其添加到模型中，然后返回 profile，也就是基本信息页面的逻辑视图名。像本章展现的其他视图一样，现在的基本信息视图非常简单：

```markup
<h1>Your Profile</h1>
<c:out value="${spitter.username}" /><br/>
<c:out value="${spitter.firstName}" />
<c:out value="${spitter.lastName}" />
```

图 5.6 展现了在 Web 浏览器中渲染的基本信息页面。

如果表单中没有发送 username 或 password 的话，会发生什么情况呢？或者说，如果 firstName 或 lastName 的值为空或太长的话，又会怎么样呢？接下来，让我们看一下如何为表单提交添加校验，从而避免数据呈现的不一致性。

![图 5.6　Spittr 的基本信息页展现了用户的情况，这些信息是由 SpitterController 填充到模型中的](/files/-LmrwzK54b4ndABEFJEx)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/di-5-zhang-gou-jian-spring-web-ying-yong-cheng-xu/untitled-3/untitled-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
