美文网首页
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