03.spring mvc实践

一、简单使用

1、搭建Spring MVC

1.1 配置DispatcherServlet并让Servlet容器发现他

(1)说明

DispatcherServlet是Spring MVC的核心。在这里请求request第一次接触到框架,它要负责将请求路由到其他的组件之中

(2)配置方式

      1)方式一(传统方式):配置在web.xml文件中,这个文件会放到应用的WAR包里面。

      2)方式二Servlet 3规范和Spring 3.1的功能增强):以编写Java代码的方式,将DispatcherServlet配置在Servlet容器中,不会再使用web.xml文件

如果按照方式二配置DispatcherServlet,而不是使用web.xml的话,那唯一问题在于它只能部署到支持Servlet 3.0的服务器中才能正常工作,如Tomcat 7或更高版本;

本文主要介绍方式二,具体做法是自定义一个类,继承自AbstractAnnotationConfigDispatcherServletInitializer

    方式二原理:

 

  • 在 Servlet 3.0 环境下,Servlet 容器会在 classpath 下搜索实现了 javax.servlet.ServletContainerInitializer 接口的任何类,找到之后用它来初始化 Servlet 容器。Spring 实现了以上接口,实现类叫做SpringServletContainerInitializer, 它会依次搜寻实现了WebApplicationInitializer的任何类,并委派这个类实现配置。之后,Spring 3.2 开始引入一个简易的WebApplicationInitializer 实现类,这就是 AbstractAnnotationConfigDispatcherServletInitializer。所以 SpittrWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer之后,也就是间接实现了 WebApplicationInitializer,在 Servlet 3.0 容器中,它会被自动搜索到,被用来配置 servlet 上下文。
  • 扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置DispatcherServletSpring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中。
  • Servlet容器启动时会通过AbstractAnnotationConfigDispatcherServletInitializer创建DispatcherServlet与ContextLoaderListener
  • SpringWeb应用中,会有以下两个上下文:
    • DispatcherServlet用于创建Spring应用上下文--加载包含Web组件的bean
    • ContextLoaderListener用于创建Web上下文--加载应用中的其他bean
  • 在Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用它来配置Servlet容器。Spring提供了这个接口的实现,名SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring 3.2引入了一个便利的WebApplicationInitializer基础实现,也就AbstractAnnotationConfigDispatcherServletInitializer因为我们的SpittrWebAppInitializer扩展了 AbstractAnnotationConfigDispatcherServletInitializer(同时也就实现了WebApplicationInitializer),因此当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。

AbstractAnnotationConfigDispatcherServletInitializer抽象类中有三个需要实现的方法(这三个方法就是围绕上面的原理中流程需要而定义的):

方法一:getServletMappings()

@Override
protected String[] getServletMappings() {
    return new String[]{"/"};//将DispatcherServlet映射到“/”
}
它会将一个或多个路径映射到DispatcherServlet 上。在本例中,它映射的是“/”,这表示它会处理进入应用的所有请求。

方法二:getServletConfigClasses()

getServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet负责创建的Spring应用上下文中的bean。

@Override
protected Class<?>[] getServletConfigClasses() {
    return new Class[]{WebConfig.class};
}

WebConfig.java见下文

方法三:getRootConfigClasses()

getRootConfigClasses() 方法返回的带有@Configuration 注解的类将会用来配置ContextLoaderListener负责 创建的应用上下文中的 bean
@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class[]{RootConfig.class};
}

RootConfig.java见下文

1.2 启用Spring MVC

1.2.1 配置getServletConfigClasses()中的WebConfig.java

方式一:使用spring配置文件方式启动:可以使用<mvc:annotation-driven>启用注解驱动的Spring MVC

方式二:使用JavaConfig配置方式启动:使用@EnableWebMvc注解修饰一个JavaConfig类(已经使用@Configuration注解的类

本文主要介绍方式二:

步骤1:@EnableWebMvc注解

所能创建的最简单的 Spring MVC 配置就是一个带有@EnableWebMvc 注解的类

步骤2:选择是否启动组件扫描

如果没有启用组件扫描。这样的结果就是, Spring 只能找到显式声明在配置类中的控制器(在JavaConfig中通过@Bean注解的方法),这里我们选择通过自动组件扫描方式让spring发现相关bean。

步骤3:配置视图解析器

如果没有配置,spring将使用默认的视图解析器,行为是:默认会使用BeanNameViewResolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口,它以这样的方式来解析视图

步骤4: 目前这样配置的话,DispatcherServlet会映射为应用的默认Servlet,所以它会处理所有的请求,包括对静态资源的请求,如
图片和样式表(在大多数情况下,这可能并不是你想要的效果)
解决办法:
 
完整的WebConfig如下:
package com.mzj.springmvc.spittr.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc//启用Spring MVC
@ComponentScan("com.mzj.springmvc.spittr.webbean")//指定组件扫描范围,可以扫描到带有@Controller注解的bean(spring mvc中的Controller)
public class WebConfig extends WebMvcConfigurerAdapter {

  /**
   * 添加视图解析器bean
   *
   * 本实现:
   *
   * 会查找.JSP文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀(例如,名为home的视图将会解析为/WEB-INF/views/home.jsp)。
   *
   * @return
   */
  @Bean
  public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
  }

  /**
   * 通过继承WebMvcConfigurerAdapter
   * 复写configureDefaultServletHandling
   * 调用 configurer.enable();
   * 达到的效果是:要求DispatcherServlet将对静态资源的请求转发到Servlet容 器中默认的Servlet上,而不是使用DispatcherServlet本身来处理 此类请求。
   * @param configurer
   */
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }
  
}

1.2.2 配置getRootConfigClasses()中的RootConfig.java

因为本文关注于 Web 开发,而Web 相关的配置通过 DispatcherServlet 创建的应用上下文都已经配置好了,因此现在的RootConfig相对很简单,唯一需要注意的是RootConfig使用了@ComponentScan注解,并且扫描的范围是整个项目的根,即:
com.mzj.springmvc.spittr
这样的话,我们会将很多组件(非Web的组件)来应用到RootConfig中,来充实完善RootConfig。
 
目前阶段~~完整的~~~RootConfig如下:
package com.mzj.springmvc.spittr.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"com.mzj.springmvc.spittr"},
    excludeFilters={
        @Filter(type=FilterType.ANNOTATION, value= EnableWebMvc.class)
    })
public class RootConfig {
}

2、编写简单的控制器

步骤1:使用@Controller注解声明一个类是一个控制器

此注解对spring mvc作用没有想象的大,使用此注解仅是为了告诉spring ioc容器,将其当成一个bean被管理而已;甚至可以将这个注解换成@Component注解,它所实现的效果是一样的,但是在表意性上可能会差一些,无法确定是什么组件类型。
步骤2:设置类级别的 @RequestMapping 注解
类级别的 @RequestMapping 注解会应用到控制器的所有处理器方法上
 
步骤3:设置方法级别的 @RequestMapping 注解,方法级别是类级别的补充
在Controller中增加方法,并在方法上增加 @RequestMapping 注解@RequestMapping注解的 value 属性指定了这个方法所要处理的请求路径,method 属性细化了它所处理的 HTTP 方法。
 
步骤4:方法返回String类型,这个String将会被Spring MVC解读为要渲染的视图名称。鉴于我们配置InternalResourceViewResolver的方式,视图名“home”将会解析为“/WEB-INF/views/home.jsp”路径的JSP
 
完整代码:
package com.mzj.springmvc.spittr.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

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

/**
 * 编写基本的控制器
 *
 * @Auther: mazhongjia
 * @Date: 2020/3/30 12:44
 * @Version: 1.0
 */
@Controller//1、声明此类是一个控制器
public class HomeController {

    @RequestMapping(value="/",method = GET)//2、处理对“/”的GET请求
    public String home(){
        return"home";//3、视图名为”home“
    }
}

步骤5:测试这个Controller

测试controller的方式

方式一:传统的方式测试控制器最直接的办法可能就是构建并部署应用,然后通过浏览器对其进行访问

方式二:spring3.2开始,可以通过mock的方式测试controller,spring包含了一种mock Spring MVC并针对控制器执行HTTP请求的机制。这样的话,在测试控制器的时候,就没有必要再启动Web服务器和Web浏览器了。

package com.mzj.springmvc.spittr;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

import com.mzj.springmvc.spittr.web.HomeController;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;

public class HomeControllerTest {

  @Test
  public void testHomePage() throws Exception {
    HomeController controller = new HomeController();

    /**
     * 从Spring 3.2开始,我们可以按照控制器的方式来测试Spring MVC 中的控制器了,
     * Spring现在包含了一 种mock Spring MVC并针对控制器执行HTTP请求的机制
     * 这样的话, 在测试控制器的时候,就没有必要再启动Web服务器和Web浏览器了:
     *
     */
    MockMvc mockMvc = standaloneSetup(controller).build();
    mockMvc.perform(get("/"))
           .andExpect(view().name("home"));//这种测试发起了对“/”的GET请求,并断言结果视图的名称为 home。
  }

}

运行测试:成功

@RequestMapping注解

  • 可以用在类、方法上
  • 应用在类上对类中所有方法生效
  • 应用在方法上是对应用在类上的补充
  • value可以接受一个数组,代表这个controller类或方法响应数组中多个URL的请求路径

参考测试用例:com.mzj.springmvc.spittr.HomeControllerTest2

3、Controller中传递模型数据到视图

步骤1:创建一个实现业务的查询数据库Repository接口及实现

package com.mzj.springmvc.spittr.data;

import com.mzj.springmvc.spittr.Spittle;

import java.util.List;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/31 12:55
 * @Version: 1.0
 */
public interface SpittleRepository {
    /**
     *
     * @param max Spittle列表中Spittle的ID最大值
     * @param count 要返回的列表中Spittle数量
     * @return
     */
    List<Spittle> findSpittles(long max, int count);
}

接口实现略。

步骤2:在Controller中的声明的Controller方法参数列表增加org.springframework.ui.Model作为参数,Controller方法就能将Repository中获取到的 Spittle列表填充到模型中。Model实际上就是一个Map(也就是 key-value对的集合),它会传递给视图,这样数据就能渲染到客户端了

步骤3:Controller方法返回对应的视图名,这个视图会渲染模型(在视图中可以获取模型中数据)

Controller代码:

package com.mzj.springmvc.spittr.web;

import com.mzj.springmvc.spittr.data.SpittleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

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

import java.util.List;
import java.util.Map;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/31 13:06
 * @Version: 1.0
 */
@Controller(value = "spittleController")
@RequestMapping("/spittles")
public class SpittleController {

    private SpittleRepository spittleRepository;

    @Autowired
    public SpittleController(SpittleRepository spittleRepository) {
        this.spittleRepository = spittleRepository;
    }

    /**
     * 在spittles()方法中给定了一个Model作为参 数。这样,spittles()方法就能将Repository中获取到的 Spittle列表填充到模型中。Model实际上就是一个Map(也就是 key-value对的集合),它会传递给视图,这样数据就能渲染到客户端了。
     * <p>
     * 以下两个注释方法,与本方法达到的效果一致
     *
     * @param model
     * @return
     */
    @RequestMapping(method = GET)
    public String spittles(Model model) {//如果你希望使用非Spring类型的话,那么可以用java.util.Map来 代替Model。实际上Model就是Map
        //当调用addAttribute()方法并且不指定key的时候,那么key会 根据值的对象类型推断确定。在本例中,因为它是一 个List<Spittle>,因此,键将会推断为spittleList。
        model.addAttribute("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20));//将spittle添加到模型中
        return "spittles";//返回视图名,spittles()方法所做的最后一件事是返回spittles作为视图的名字,这个视图会渲染模型。
    }

//    public String spittles(Map model){/
//        model.put("spittleList",spittleRepository.findSpittles(Long.MAX_VALUE,20));
//        return "spittles";
//    }

//    public List<Spittle> spittles() {//这个版本与其他的版本有些差别。它并没有返回视图名称,也没有显 式地设定模型,这个方法返回的是Spittle列表。当处理器方法像 这样返回对象或集合时,这个值会放到模型中,模型的key会根据其 类型推断得出(在本例中,也就是spittleList),而逻辑视图的名称将会根据请求路径推断得出。因为这个方法处理针 对“/spittles”的GET请求,因此视图的名称将会是spittles(去掉开 头的斜线)。
//        return spittleRepository.findSpittles(Long.MAX_VALUE, 20);
//    }


}

步骤4:测试

方式1:部署到tomcat中运行

方式2:通过测试用例mock的方式(可以不创建SpittleRepository接口的实现)

package com.mzj.springmvc.spittr;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.mzj.springmvc.spittr.data.SpittleRepository;
import com.mzj.springmvc.spittr.web.SpittleController;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.view.InternalResourceView;

public class SpittleControllerTest {

  @Test
  public void houldShowRecentSpittles() throws Exception {
    //1、这个测试首先会创建SpittleRepository接口的mock实现,这个 实现会从它的findSpittles()方法中返回20个Spittle对象。
    List<Spittle> expectedSpittles = createSpittleList(20);
    SpittleRepository mockRepository = mock(SpittleRepository.class);//mock   SpittleRepository接口的实现
    when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
        .thenReturn(expectedSpittles);

    //2、它将这个Repository注入到一个新的SpittleController 实例中,然后创建MockMvc并使用这个控制器
    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = standaloneSetup(controller)//mock   spring mvc
        .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))//这里设置的路径其实没有太大意义,只是与在WebConfig中定义的viewResolver保持一致
            //调用了setSingleView()。这样的话,mock框架就不用解析 控制器中的视图名了,之所以使用这种直接set视图名的方式,是因为视图名与请求路径是非常相似的(spittles),这样按照默认 的视图解析规则时,MockMvc就会发生失败,因为无法区分视图路 径和控制器的路径。
            .build();
    //3、开始断言:测试对“/spittles”发起GET请求,然后断言视图的名称为spittles并 且模型中包含名为spittleList的属性,在spittleList中包含 预期的内容。
    mockMvc.perform(get("/spittles"))//对/spittles发起GET请求
       .andExpect(view().name("spittles"))//断言期望值
       .andExpect(model().attributeExists("spittleList"))
       .andExpect(model().attribute("spittleList",
                  hasItems(expectedSpittles.toArray())));
  }

  private List<Spittle> createSpittleList(int count) {
    List<Spittle> spittles = new ArrayList<Spittle>();
    for (int i=0; i < count; i++) {
      spittles.add(new Spittle("Spittle " + i, new Date()));
    }
    return spittles;
  }
}

4、Controller中接收请求的输入

Spring MVC 允许以多种方式将客户端中的数据传送到控制器的controller方法中,方式主要有三种:
  • 请求参数(请求少量数据)
  • 路径变量(请求少量数据,restful风格)
  • 表单参数(传递很多数据时使用)

4.1 通过【请求参数】接收输入

步骤1:controller方法

  • @RequestParam注解:使用此注解修饰controller方法的参数,其中

    • value属性对应请求参数中属性名

    • defaultValue属性用于不传递此参数时默认取值

这样,在controller中就获取了通过请求参数方式接收客户端的输入

package com.mzj.springmvc.spittr.web.param;

import com.mzj.springmvc.spittr.Spittle;
import com.mzj.springmvc.spittr.data.SpittleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

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

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/31 13:06
 * @Version: 1.0
 */
@Controller
@RequestMapping("/spittles2")
public class SpittleController2 {

    private static final String MAX_LONG_AS_STRING = "9223372036854775807";
    private SpittleRepository spittleRepository;

    @Autowired
    public SpittleController2(SpittleRepository spittleRepository) {
        this.spittleRepository = spittleRepository;
    }

//    @RequestMapping(method = 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);
//    }

    @RequestMapping(method = GET)
    public String spittles(Model model,@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max,
                                  @RequestParam(value = "count", defaultValue = "20") int count) {
        System.out.println("controller收到:count = " + count + ",max = " + max);
        model.addAttribute("spittleList", spittleRepository.findSpittles(max,count));
        return "spittles";
    }


}

步骤2:测试

package com.mzj.springmvc.spittr.param;

import com.mzj.springmvc.spittr.Spittle;
import com.mzj.springmvc.spittr.data.SpittleRepository;
import com.mzj.springmvc.spittr.web.param.SpittleController2;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.view.InternalResourceView;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static org.hamcrest.Matchers.hasItems;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

public class SpittleControllerTest2 {

  @Test
  public void shouldShowPagedSpittles() throws Exception {
    List<Spittle> expectedSpittles = createSpittleList(50);
    SpittleRepository mockRepository = mock(SpittleRepository.class);
    when(mockRepository.findSpittles(238900, 50))//预期的max和count参数
        .thenReturn(expectedSpittles);

    SpittleController2 controller = new SpittleController2(mockRepository);
    MockMvc mockMvc = standaloneSetup(controller)//mock   spring mvc
        .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
            .build();

    mockMvc.perform(get("/spittles2?max=238900&count=50"))//对/spittles发起GET请求,同时传入max和count参数,它测试 了这些参数存在时的处理器方法
       .andExpect(view().name("spittles"))
       .andExpect(model().attributeExists("spittleList"))
       .andExpect(model().attribute("spittleList",
                  hasItems(expectedSpittles.toArray())));
  }

  private List<Spittle> createSpittleList(int count) {
    List<Spittle> spittles = new ArrayList<Spittle>();
    for (int i=0; i < count; i++) {
      spittles.add(new Spittle("Spittle " + i, new Date()));
    }
    return spittles;
  }
}

4.2 通过【路径参数】接收输入

说明:此种传递参数的方式常用在构建面向资源的控制器时(restful风格),这种方式就是将传递参数作为请求路径的一部分。

步骤1:controller方法的@RequestMapping注解中增加代表路径参数的占位符,比如这里的"/{spittleId}"中spittleId,其中spittleId是URL路径中的占位符,占位符的名称要用大括号(“{”和“}”)括起来。 路径中的其他部分要与所处理的请求完全匹配,占位符部分可以是任意的值。

 
步骤2:controller方法的参数使用 @PathVariable("spittleId")注解 修饰接收客户端请求路径的参数
controller方法的参数列表参数名spittleId与RequestMapping中请求占位符/{spittleId}需对应
 
如果 @PathVariable 中没有 value 属性的话,它会假设占位符的名称与方法的参数名相同
package com.mzj.springmvc.spittr.web.restful;

import com.mzj.springmvc.spittr.data.SpittleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

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

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/31 13:06
 * @Version: 1.0
 */
@Controller
@RequestMapping("/spittles3")
public class SpittleController3 {

    private SpittleRepository spittleRepository;

    @Autowired
    public SpittleController3(SpittleRepository spittleRepository) {
        this.spittleRepository = spittleRepository;
    }

    @RequestMapping(value = "/{spittleId}",method = GET)//其中spittleId是URL路径中的占位符,占位符的名称要用大括号(“{”和“}”)括起来。 路径中的其他部分要与所处理的请求完全匹配,但是占位符部分可以 是任意的值。
//    public String spittles(Model model, @PathVariable("spittleId") long spittleId) {//这里的spittleId与RequestMapping中/{spittleId}对应
//    需要注意的是:代码中spittleId这个词出现了好几次:先是 在@RequestMapping的路径中,然后作为@PathVariable属性的 值,最后又作为方法的参数名称。因为方法的参数名碰巧与占位符的 名称相同,因此我们可以去掉@PathVariable中的value属性:
    public String spittles(Model model, @PathVariable long spittleId) {//这里参数名spittleId与RequestMapping中请求占位符/{spittleId}对应
        System.out.println("controller收到:spittleId = " + spittleId);
        model.addAttribute(spittleRepository.findOne(spittleId));//将查询到的模型放入model中,以便在视图中拿到,模型的key将会是spittle,这是根据传递 到addAttribute()方法中的类型推断得到的
        return "spittles";
    }
}

步骤3:测试

package com.mzj.springmvc.spittr.restful;

import com.mzj.springmvc.spittr.Spittle;
import com.mzj.springmvc.spittr.data.SpittleRepository;
import com.mzj.springmvc.spittr.web.SpittleController;
import com.mzj.springmvc.spittr.web.param.SpittleController2;
import com.mzj.springmvc.spittr.web.restful.SpittleController3;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.view.InternalResourceView;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static org.hamcrest.Matchers.hasItems;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

public class SpittleControllerTest3 {

  @Test
  public void testSpittle() throws Exception {
    Spittle expectedSpittle = new Spittle("Hello", new Date());
    SpittleRepository mockRepository = mock(SpittleRepository.class);//构建了一个mock Repository
    when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);

    SpittleController3 controller = new SpittleController3(mockRepository);//构建了一个控制器
    MockMvc mockMvc = standaloneSetup(controller).build();

    mockMvc.perform(get("/spittles3/12345"))//构建了一个mock mvc,对“/spittles/12345”发起GET请求
            .andExpect(view().name("spittles"))
            .andExpect(model().attributeExists("spittle"))
            .andExpect(model().attribute("spittle", expectedSpittle));
  }
}

4.3 表单参数

1、说明:使用表单分为两个过程:①展现表单②处理用户通过表单提交的数据
 
2、步骤
1)展现表单的控制器:SpitterController
 
请求URL:XXX/spitter/register
 /**
   * 展现表单控制器
   * @return
   */
  @RequestMapping(value="/register", method=GET)
  public String showRegistrationForm() {
    return "registerForm";
  }
按照配置InternalResourceViewResolver的方式,将会使用“/WEB-INF/ views/registerForm.jsp”这个 JSP 来渲染注册表单。
 
2)表单JSP:registerForm.jsp
  • 这个JSP必须要包含一个HTML<form>标签
  • 这里的<form>标签中并没有设置action属性。在这种情况下,当表单提交时,它会提交到与展现时相同的URL路径上。也就是说,它会提交到“/spitter/register”
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          href="<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Register</h1>
<%--这里的<form>标签中并没有设置action属性。在 这种情况下,当表单提交时,它会提交到与展现时相同的URL路径 上。也就是说,它会提交到“/spitter/register”上--%>
    <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>
  </body>
</html>

3)处理表单的控制器SpitterController

  • 处理注册表单的 POST 请求时,控制器需要接受表单数据并将表单数据保存为Spitter 对象。
  • 为了防止重复提交(用户点击浏览器的刷新按钮有可能会发生这种情况),将浏览器重定向到新创建用户的基本信息页面。
  • 其他细节关注下面代码注释
/**
   * 处理表单提交控制器
   * @param spitter
   * @return
   */
  @RequestMapping(value="/register", method=POST)
  public String processRegistration(Spitter spitter) {
    /**
     * 这个控制器方法直接声明接收一个Spittler对象参数,需要依赖发送请求的客户端:在请求中使用与Spittler对象中属性同名的参数,这样Spittler对象的属性会自动进行填充,对于本测试来说,是SpittlerControllerTest的shouldProcessRegistration中的下面代码起到的作用:
     *
     * mockMvc.perform(post("/spitter/register")//)对“/spitter/ register”发起了一个POST请求。
     *             .param("firstName", "Jack")
     *             .param("lastName", "Bauer")
     *             .param("username", "jbauer")
     *             .param("password", "24hours")
     *             .param("email", "jbauer@ctu.gov"))
     */
    System.out.println(spitter);
    spitterRepository.save(spitter);//当使用Spitter对象调用processRegistration()方法时,它会进而调用SpitterRepository的save()方法,SpitterRepository是在Spitter-Controller的构造器中注入进来的。
    //返回的值还带有重定向的格式
    //请求重定向:当InternalResourceViewResolver看到视图格式中 的“redirect:”前缀时,它就知道要将其解析为重定向的规则,而不是视图的名称。
    //请求转发:除了“redirect:”,InternalResourceViewResolver还能识 别“forward:”前缀。当它发现视图格式中以“forward:”作为前缀时,请求将会前往(forward)指定的URL路径,而不再是重定向。
    return "redirect:/spitter/" + spitter.getUsername();//在处理POST类型的请求时,在请求处理完成后,最好进行一下重定向,这样浏览器的刷新就不会重复提交表单了。在在这个测试中,预期 请求会重定向到“/spitter/jbauer”,也就是新建用户的基本信息页面
  }

4)表单提交后重定向请求(基本信息页面的控制器)SpitterController

/**
   * 处理提交表单后处理完重定向请求的跳转,也就是基本信息页面的请求
   * @param username
   * @param model
   * @return
   */
  @RequestMapping(value="/{username}", method=GET)
  public String showSpitterProfile(@PathVariable String username, Model model) {
    /**
     * SpitterRepository通过用户名获取一个Spitter对 象,showSpitter-Profile()得到这个对象并将其添加到模型 中,然后返回profile,也就是基本信息页面的逻辑视图名。
     */
    Spitter spitter = spitterRepository.findByUsername(username);
    model.addAttribute(spitter);
    return "profile";
  }

5)基本信息页面profile.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <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}" />
  </body>
</html>

4.4 表单校验

1、作用:用来验证表单提交内容是否为空,提交内容是否合法等。

2、方式

1)方式一:直接再业务控制器方法中加入if代码进行校验

缺点:校验逻辑弄脏了业务处理器方法。

2)方式二:Java校验API(Java Validation API,又称JSR-303)(Spring3.0以上对其进行了支持)

3、使用(这里介绍方式二使用)

1)准备:在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java校验API的实现即可,比如Hibernate Validator

2)说明:Java校验API定义了多个注解,这些注解可以放到属性上,从而限制这些属性的值。

所有的注解都位于javax.validation.constraints包中,常用的见下表

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

3)在bean的属性上增加上述校验注解进行限制

代码见:com.mzj.springmvc.spittr.Spitter

package com.mzj.springmvc.spittr;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;

public class Spitter {

  private Long id;
  
  @NotNull
  @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;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  
  public String getEmail() {
    return email;
  }
  
  public void setEmail(String email) {
    this.email = email;
  }

  @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");
  }

}

4)在控制器方法的对应参数上增加@Valid注解,以启用校验功能(这会告知Spring,需要确保这个对象满足校验限制)

注意@Valid注解是Java校验API中的注解,而不是spring的,spring是对这个API进行了实现

5)增加Errors参数

!!很重要一点需要注意,Errors参数要紧跟在带有@Valid注解的参数后面,@Valid注解所标注的就是要检验的参数。)

以上两点代码实现:com.mzj.springmvc.spittr.web.form.SpitterController,关键代码如下:

/**
   * 处理表单提交控制器
   * @param spitter
   * @return
   */
  @RequestMapping(value="/register", method=POST)
  public String processRegistration(@Valid Spitter spitter,Errors errors) {

    if(errors.hasErrors()){//如果验证失败,重新返回表单
      return "registerForm";
    }
    ...

  }
上面的处理仅仅能够让用户的浏览器重新回到注册表单页面,但是会显示空的表单,好的做法是在表单中显示最初提交的值并将校验错误反馈给用户:
 
待续.。。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值