美文网首页
14、构建Spring Web应用程序(2)(spring笔记)

14、构建Spring Web应用程序(2)(spring笔记)

作者: yjaal | 来源:发表于2017-04-09 11:53 被阅读183次

三、接受请求的输入

Spring MVC中,允许以多种方式将客户端中的数据传送到控制器的处理方法中,包括:

  • 查询参数
  • 表单参数
  • 路径变量

3.1 处理查询参数

如果我们向让用户每次都能产看某一页的Spittle历史,那么就需要提供一种方式让用户传递参数进来,进而确定要展现哪些Spittle集合。在确定该如何实现时,假设我们要查看某一页Spittle列表,这个列表会按照最新的Spittle在前的方式进行排序(最后发布是Spittle消息的ID最大)。因此,下一页中第一条的ID肯定会早于当前页最后一条的ID。因此,为了显示下一页的Spittle,需要将一个SpittleID传入进来,这个ID要恰好小于当前页最后一条SpittleID。另外,还可以传入一个参数确定要展现的Spittle数量。

为了实现这个分页的功能,所编写的处理器方法要接受如下的参数:

  • max参数(标明结果中所有的SpittleID均应该在这个值之前)
  • count参数(表明要查询的Spittle数量)

下面给出SpittleController控制器中新的spittles()方法:

@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
    @RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
    @RequestParam(value="count", defaultValue="20") int count) {
  return spittleRepository.findSpittles(max, count);
}

说明:可以看到这里max参数的默认值是MAX_LONG_AS_STRING因为查询参数都是String类型的,即通过GET方式传递参数都是String类型的,因此这里不能使用Long.MAX_VALUE,当MAX_LONG_AS_STRING绑定到max的时候会转换成Long类型),即最大值,也就是说所有的Spittle消息的ID都不可能大于此数。再次对其进行测试:

@Test
public void shouldShowPagedSpittles() throws Exception {
    List<Spittle> expectedSpittles = createSpittleList(50);
    SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
    Mockito.when(mockRepository.findSpittles(238900, 50))
            .thenReturn(expectedSpittles);

    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller)
            .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
            .build();

    mockMvc.perform(MockMvcRequestBuilders.get("/spittles?max=238900&count=50"))
            .andExpect(MockMvcResultMatchers.view().name("spittles"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))
            .andExpect(MockMvcResultMatchers.model().attribute("spittleList",
                    org.hamcrest.Matchers.hasItems(expectedSpittles.toArray())));
}

3.2 通过路径参数接受输入

假设应用程序需要根据给定的ID来展现某一个Spittle记录。其中一种方案就是编写处理器方法,通过使用@RequestParam注解,让它接受ID作为查询参数:

@RequestMapping(value="/show", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittle_id") long spittleId, Model model) {
  model.addAttribute(spittleRepository.findOne(spittleId));
  return "spittle";
}

说明:这个处理器方法将会处理形如“/spittles/show?spittle_id=12345”这样的请求。这样可以正常工作,但是从面向资源的角度来看这并不理想。在理想情况下,要识别的资源应该通过URL路径进行标识,而不是通过查询参数。对“spittles/12345”发起GET请求要优于对“/spittles/show?spittle_id=12345”发起请求。

下面我们给出新的测试方法,它会断言SpittleController中对面向资源的请求的处理。

@Test
public void testSpittle() throws Exception {
    Spittle expectedSpittle = new Spittle("Hello", new Date());
    SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
    Mockito.when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);

    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

    mockMvc.perform(MockMvcRequestBuilders.get("/spittles/12345"))
            .andExpect(MockMvcResultMatchers.view().name("spittle"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("spittle"))
            .andExpect(MockMvcResultMatchers.model().attribute("spittle", expectedSpittle));
}

说明:可以看到这里使用地址“/spittles/12345”进行测试,如果向让测试通过,需要编写@RequestParam要包含变量部分,这部分代表SpittleID。下面给出新的spittle()方法:

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittleId") long spittleId, Model model) {
  model.addAttribute(spittleRepository.findOne(spittleId));
  return "spittle";
}

说明:这里在@RequestMapping中使用了占位符spittleId,而方法参数中使用@PathVariable标识,这标明不管占位符的名字(这里是spittleId)是什么,都会传递到处理器方法的spittleId参数中。当然这里占位符和方法参数名一致,可以去掉@PathVariable中的值。下面给出spittle.jsp:

<div class="spittleView">
  <div class="spittleMessage"><c:out value="${spittle.message}" /></div>
  <div>
    <span class="spittleTime"><c:out value="${spittle.time}" /></span>
  </div>
</div>

四、处理表单

使用表单分为两个方面:展现表单以及处理用户通过表单提交的数据。在本应用中需要有各表单让新用户进行注册。

package spittr.web;
import org.springframework.web.bind.annotation.RequestMethod;
import ...;

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

  @RequestMapping(value="/register", method=RequestMethod.GET)
  public String showRegistrationForm() {
    return "registerForm";
  }
}

说明:这个方法非常简单,就是向应用发起请求,返回一个表单页面,相关测试如下:

@Test
public void shouldShowRegistration() throws Exception {
    SpitterRepository mockRepository = Mockito.mock(SpitterRepository.class);
    SpitterController controller = new SpitterController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    mockMvc.perform(MockMvcRequestBuilders.get("/spitter/register"))
            .andExpect(MockMvcResultMatchers.view().name("registerForm"));
}

说明:这里断言视图为“registerForm”。下面给出注册表单registerForm.jsp

<form method="POST">
  First Name: <input type="text" name="firstName" /><br/>
  Last Name: <input type="text" name="lastName" /><br/>
  Email: <input type="email" name="email" /><br/>
  Username: <input type="text" name="username" /><br/>
  Password: <input type="password" name="password" /><br/>
  <input type="submit" value="Register" />
</form>

说明:这里的<form>标签中并没有设置action属性。此时,它会提交到与展现时相同的URL路径上(即GET请求地址“/spitter/register”)。当表单填写无误提交后,需要在服务器端处理该HTTP POST请求。

4.1 编写处理表单的控制器

当处理注册表单的POST请求时,控制器需要接受表单数据并将表单数据保存为Spitter对象。最后,为了防止重复提交,应该将浏览器重定向到新创建用户的基本信息页面。下面先给出测试代码:

@Test
public void shouldProcessRegistration() throws Exception {
    SpitterRepository mockRepository = Mockito.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");
    Mockito.when(mockRepository.save(unsaved)).thenReturn(saved);

    SpitterController controller = new SpitterController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

    mockMvc.perform(MockMvcRequestBuilders.post("/spitter/register")
            .param("firstName", "Jack")
            .param("lastName", "Bauer")
            .param("username", "jbauer")
            .param("password", "24hours")
            .param("email", "jbauer@ctu.gov"))
            .andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/jbauer"));

    Mockito.verify(mockRepository, Mockito.atLeastOnce()).save(unsaved);
}

说明:这里首先构建了两个相同Spitter对象(具体代码后面会给出),只是一个有ID(这个ID是随意加的),一个没有。然后发起注册请求,断言至少保存了一个Spitter对象。在处理POST请求完后,为防止重复提交,最好进行一下重定向,这里重定向到此对象的显示页面(“/spitter/jbauer”)中。给出修改后的控制器SpitterController :

package spittr.web;
import ...

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

    private SpitterRepository spitterRepository;

    @Autowired
    public SpitterController(SpitterRepository spitterRepository) {
        this.spitterRepository = spitterRepository;
    }

    @RequestMapping(value="/register", method=RequestMethod.GET)
    public String showRegistrationForm() {
        return "registerForm";
    }

    @RequestMapping(value="/register", method=RequestMethod.POST)
    public String processRegistration(Spitter spitter) {
        spitterRepository.save(spitter);
        return "redirect:/spitter/" + spitter.getUsername();
    }
}

说明:控制器在处理完注册请求之后,对访问进行了重定向,这里使用“redirect:”进行重定向。当然我们还可以使用“forward:”进行请求转发。这里进行请求重定向之后,我们在控制器中还需要针对此重定向的请求进行处理:

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

说明:下面给出相关的profile.jsp:

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

4.2 校验表单

在填写表单的时候,一般需要对填写的字段进行校验。但是手动为每个字段编写校验显然比较麻烦。从Spring 3.0开始,在Spring MVC中提供了对Java校验的API支持。这里需要导入两个额外的包:hibernate-validator-6.0.0.Alpha2.jar、validation-api-2.0.0.Alpha2.jar

Java校验API所提供的校验注解

注解 描述
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString
@Digits 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解的元素的值必须不能为null
@Null 所注解的元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围

下面给出spitter类:

package spittr;
import javax.validation.constraints.*;
import org.apache.commons.lang3.builder.*;
import org.hibernate.validator.constraints.Email;

public class Spitter {

  private Long id;
  
  @NotNull//非空,5到16各字符
  @Size(min=5, max=16)
  private String username;

  @NotNull
  @Size(min=5, max=25)
  private String password;
  
  @NotNull
  @Size(min=2, max=30)
  private String firstName;

  @NotNull
  @Size(min=2, max=30)
  private String lastName;
  
  @NotNull
  @Email
  private String email;

  public Spitter() {}
  
  public Spitter(String username, String password, String firstName, String lastName, String email) {
    this(null, username, password, firstName, lastName, email);
  }

  public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {
    this.id = id;
    this.username = username;
    this.password = password;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
  }

  //getter、setter方法省略

  @Override
  public boolean equals(Object that) {
    return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");
  }
  
  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");
  }
}

说明:现在已经为spitter添加了校验注解,接下来需要修改控制器中的注册方法processRegistration()方法来应用校验功能。

@RequestMapping(value="/register", method=RequestMethod.POST)
public String processRegistration(@Valid Spitter spitter, Errors errors) {
    if (errors.hasErrors()) {
        return "registerForm";
    }

    spitterRepository.save(spitter);
    return "redirect:/spitter/" + spitter.getUsername();
}

说明:这里使用Spring的校验注解@Valid来对Spitter输入进行校验,如果校验出现错误,则重新放安徽表单。注意,在Spitter属性上添加校验限制并不能组织表单提交。在填写出现错误时,需要让相关错误信息显示在表单页面(下一节进行讲解)。

相关文章

网友评论

      本文标题:14、构建Spring Web应用程序(2)(spring笔记)

      本文链接:https://www.haomeiwen.com/subject/kjqdattx.html