《Spring实战》学习笔记-第五章:构建Spring web应用

以前一直在看《Spring实战》第三版,看到第五章时发现不少东西已通过时被废弃了,因而如今开始读《Spring实战》第四版了,章节安排与以前不一样了,里面应用的应该是最新的技术。javascript

本章中,将会接触到Spring MVC基础,以及如何编写控制器来处理web请求,如何通明地绑定请求参数到业务对象上,同时还能够提供数据校验和错误处理的功能。css

Spring MVC初探

跟踪Spring MVC请求

请求会由DispatcherServlet分配给控制器(根据处理器映射来肯定),在控制器完成处理后,接着请求会被发送给一个视图来呈现结果
请求会由DispatcherServlet分配给控制器(根据处理器映射来肯定),在控制器完成处理后,接着请求会被发送给一个视图来呈现结果

在请求离开浏览器时,会带有用户所请求内容的信息,例如请求的URL、用户提交的表单信息。html

请求旅程的第一站是Spring的DispatcherServlet。Spring MVC全部的请求都会经过一个前端控制器Servlet。前端控制器是经常使用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其余组件来执行实际的处理。在Spring MVC中,DispatcherServlet 就是前端控制器。前端

DispatcherServlet的任务是将请求发送给Spring MVC控制器。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器, Dispatcher Servlet须要知道应该将请求发送给哪一个控制器。因此DispatcherServlet会查询一个或多个处理器映射来肯定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。java

一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到达了控制器,请求会卸下其负载(用户提交的信息)并等待控制器处理这些信息(实际上,设计良好的控制器自己只处理不多甚至不处理工做,而是将业务逻辑委托给个或多个服务对象)。git

控制器在完成逻辑处理后一般会产生一些信息,这些信息须要返回给用户并在浏览器上显示。这些信息被称为模型(Model)。不过仅仅给用户返回原始的信息是不够的--这些信息须要以用户友好的方式进行格式化,通常是HTML。因此,信息须要发送给—个视图(View),一般会是JSP。github

控制器所作的最后一件事是将模型数据打包,而且标示出用于渲染输出的视图名称。它接下来会将请求连同模型和视图名称发送回DispatcherServlet。web

这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名称并不直接表示某个特定的JSP。它仅仅传递了一个逻辑名,这个名字将会用来查找用来产生结果的真正视图。DispatcherServlet将会使用视图解析器来将逻辑视图名匹配为一个特定的视图实现。正则表达式

既然DispatcherServlet已经知道由哪一个视图渲染结果,那么请求的任务基本上也就完成了。它的最后一站是视图的实现(多是JSP),在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,并经过这个输出将响应对象传递给客户端。spring

搭建Spring MVC

配置DispatcherServlet

DispatcherServlet是Spring MVC的核心,它负责将请求分发到其余各个组件。

在旧版本中,DispatcherServlet之类的servlet通常在web.xml文件中配置,该文件通常会打包进最后的war包种;可是Spring3引入了注解,咱们在这一章将展现如何基于注解配置Spring MVC。

注意:
在使用maven构建web工程时,因为缺乏web.xml文件,可能会出现web.xml is missing and <failOnMissingWebXml> is set to true这样的错误,那么能够经过在pom.xml文件中添加以下配置来避免这种错误:

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> 

既然不适用web.xml文件,你须要在servlet容器中使用Java配置DispatcherServlet,具体的代码列举以下:

package spittr.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebConfig.class }; } } 

任意继承自AbstractAnnotationConfigDispatcherServletInitializer的类都会被自动用来配置DispatcherServlet,这个类负责配置DispatcherServlet初始化Spring MVC容器和Spring容器

SpittrWebAppInitializer重写了三个方法,getRootConfigClasses()方法用于获取Spring应用容器的配置文件,这里咱们给定预先定义的RootConfig.class;getServletConfigClasses()负责获取SpringMVC应用容器,这里传入预先定义好的WebConfig.class;getServletMappings()方法负责指定须要由DispatcherServlet映射的路径,这里给定的是"/",意思是由DispatcherServlet处理全部向该应用发起的请求。

两种应用上下文

当DispatcherServlet启动时,会建立一个Spring应用上下文而且会加载配置文件中声明的bean,经过getServletConfigClasses()方法,DispatcherServlet会加载WebConfig配置类中所配置的bean。

在Spring web应用中,一般还有另一种应用上下文:ContextLoaderListener

DispatcherServlet用来加载web组件bean,如控制器(controllers)、视图解析器(view resolvers)以及处理器映射(handler mappings)等。而ContextLoaderListener则用来加载应用中的其余bean,如运行在应用后台的中间层和数据层组件。

AbstractAnnotationConfigDispatcherServletInitializer会同时建立DispatcherServlet和ContextLoaderListener。getServletConfigClasses()方法返回的@Configuration类会定义DispatcherServlet应用上下文的bean。同时,getRootConfigClasses()返回的@Configuration类用来配置ContextLoaderListener上下文建立的bean。

相对于传统的web.xml文件配置的方式,经过AbstractAnnotationConfigDispatcherServletInitializer来配置DispatcherServlet是一种替代方案。须要注意的是,这种配置只适用于Servlet 3.0,例如Apache Tomcat 7或者更高。

激活Spring MVC

正若有多种方式能够配置DispatcherServlet,激活Spring MVC组件也有不止一种方法。通常的,都会经过XML配置文件的方式来配置Spring,例如能够经过<mvc:annotation-driven>来激活基于注解的Spring MVC。

最简单的配置Spring MVC的一种方式是经过@EnableWebMvc注解:

package spittr.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc public class WebConfig { } 

@Configuration表示这是Java配置类;@EnableWebMvc注解用于启动Spring MVC特性。

这样就能够激活Spring MVC了,可是还有其余一些问题:

  • 没有配置视图解析器(view resolvers),这种状况下,Spring会默认使用BeanNameViewResolver,它会经过寻找那些与视图id匹配的bean以及实现了View接口的类进行视图解析;
  • 没有激活组件扫描:这样Spring会寻找配置中明确声明的任意控制器;
  • DispatcherServlet会处理全部的请求,包括静态资源请求,如图片和样式(这些每每不是咱们想要的)。

所以,须要为WebConfig增长一些配置:

package 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.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc @ComponentScan("spitter.web") // 激活Spring MVC public class WebConfig extends WebMvcConfigurerAdapter { // 配置一个JSP视图解析器 @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB_INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } } 

首先须要注意的是,WebConfig使用了@ComponentScan注解,所以会在spitter.web包下扫描寻找组件,这些组件包括使用@Controller进行注解的控制器。这样就再也不须要在配置类中显式地声明其余控制器。

接下来,添加了一个ViewResolverbean,即InternalResourceViewResolver。它经过匹配符合设置的前缀和后缀的视图来用来寻找对应的JSP文件,好比视图home会被解析为/WEB-INF/views/home.jsp。这里的三个函数的含义依次是:setPrefix()方法用于设置视图路径的前缀;setSuffix()用于设置视图路径的后缀,即若是给定一个逻辑视图名称——"home",则会被解析成"/WEB-INF/views/home.jsp"; setExposeContextBeansAsAttributes(true)使得能够在JSP页面中经过${}访问容器中的bean。

而后,WebConfig继承自WebMvcConfigurerAdapter,而且重写了configureDefaultServletHandling()方法,经过调用enable()方法从而可让DispatcherServlet将静态资源的请求转发给默认的servlet。

package 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 = { "spitter" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) }) public class RootConfig { } 

须要注意的一点是,RootConfig 使用了@ComponentScan注解。

Spittr应用介绍

这一章要用的例子应用,从Twitter获取了一些灵感,所以最开始叫Spitter;而后又借鉴了最近比较流行的网站Flickr,所以咱们也把e去掉,最终造成Spittr这个名字。这也有利于区分领域名称(相似于twitter,这里用spring实现,所以叫spitter)和应用名称。

Spittr相似于Twitter,用户能够经过它添加一些推文。Spittr有两个重要的概念:spitter(应用的用户)和spittle(用户发布简单状态)。本章将会构建该应用的web层、建立用于展现spittle的控制器以及用户注册流程。

编写简单的控制器

Spring MVC中,控制器仅仅是拥有@RequestMapping注解方法的类,从而能够声明它们能够处理何种请求。

在开始以前,咱们先假设一个控制器,它能够处理匹配/的请求并会跳转到主页面。

package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller // 声明一个控制器 public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) // 处理GET请求 public String home() { return "home"; } } 

@Controller是一个构造型注解,它基于@Component,组件扫描器会自动地将HomeController声明为Spring上下文的一个bean。

home()方法采用了@RequestMapping注解,属性value指定了该方法处理的请求路径,method方法指定了能够处理的HTTP方法。这种状况下,当一个来自/的GET方法请求时,就会调用home()方法。

home()方法仅仅返回了一个"home"的String值,Spring MVC会对这个String值进行解析并跳转到指定的视图上。DispatcherServlet则会请求视图解析器将这个逻辑视图解析到真实视图上。

咱们已经配置了InternalResourceViewResolver,“home”视图会被解析到/WEB-INF/views/home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <html> <head> <meta charset="utf-8"> <title>Spittr</title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />"> </head> <body> <h1>Welcome to Spittr</h1> <a href="<c:url value="/spittles" />">Spittles</a> | <a href="<c:url value="/spitter/register" />">Register</a> </body> </html> 

下面对HomeController进行测试。

测试控制器

通常的web测试须要将工程发布到一个web容器中,启动后才能观察运行结果。
如:


主页
主页

从另外的角度来看,HomeController实际上是一个简单的POJO对象,那么可使用下面的方法对其进行测试:

package spittr.web; import org.junit.Test; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; public class HomeControllerTest { @Test public void testHomePage() throws Exception { HomeController controller = new HomeController(); // 设置MockMvc MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.view().name("home")); } } 

相对于直接调用home()方法测试它的返回值,上面的测试中发起了一个来自/的 GET 请求,而且对其结果视图进行断言。将HomeController的实例传送给MockMvcBuilders.standaloneSetup,而且调用build()方法来建立一个MockMvc实例。而后,使用MockMvc实例产生了一个GET请求,而且设置了视图的指望。

定义类层级的请求处理

package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller // 声明一个控制器 @RequestMapping("/") // 控制器匹配路径 public class HomeController { @RequestMapping(method = RequestMethod.GET) // 处理GET请求 public String home() { return "home";// 视图名称 } } 

在这个新版的HomeController中,将请求匹配路径移到了类层级,HTTP方法的匹配仍处在方法层级。当有控制类中有一个类层级的@RequestMapping,该类中全部的用@RequestMapping注解的处理方法共同组成了类层级的@RequestMapping

@RequestMapping的value属性接受String数组,那么就可使用以下配置:

@Controller // 声明一个控制器 @RequestMapping("/", "/homepage") // 控制器匹配路径 public class HomeController { ... } 

这种状况下,home()方法就能够处理来自//homepage的GET请求。

将model数据传送给视图

在Spittr应用中,须要一个页面,用来显示最近提交的spittle清单。首先须要定义一个数据访问的仓库,用来抓取spittle:

package spittr.data; import java.util.List; import spittr.Spittle; public interface SpittleRepository { /** * @param max * 待返回的最大的Spittle ID * @param count * 返回Spittle对象的个数 * @return */ List<Spittle> findSpittles(long max, int count); } 

若是要获取最近的20个Spittle对象,那么只需调用这样调用:
List<Spittle> recent = spittleRepository.findSpittles(Long.MAX_VALUE, 20);

下面对Spittle进行定义:

package spittr; import java.util.Date; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class Spittle { private final Long id; private final String message;// 消息 private final Date time;// 时间戳 private Double latitude; private Double longitude; public Spittle(String message, Date time) { this(message, time, null, null); } public Spittle(String message, Date time, Double latitude, Double longitude) { this.id = null; this.message = message; this.time = time; this.latitude = latitude; this.longitude = longitude; } @Override public boolean equals(Object that) { return EqualsBuilder.reflectionEquals(this, that, "id", "time"); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this, "id", "time"); } //getters and setters } 

Spittle对象中如今包含信息、时间戳、位置这几个属性。

下面利用Spring的MockMvc来断言新的控制器的行为是否正确:

 
  

上面的测试经过建立一个SpittleRepository接口的mock实现,该实现会经过findSpittles()方法返回一个包含20个Spittle对象的集合。而后将这个bean注入到SpittleController实例中,并设置MockMvc使用该实例。

不一样于HomeControllerTest,该测试使用了setSingleView(),发起一个/spittles的GET请求,并断言视图是否为spittles以及model是否含有一个spittleList的属性值。

固然,如今运行这个测试代码确定是会出错的,由于尚未SpittleController。

package spittr.web; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.hamcrest.core.IsCollectionContaining; import org.junit.Test; import org.mockito.Mockito; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.view.InternalResourceView; import spittr.Spittle; import spittr.data.SpittleRepository; public class SpittleControllerTest { @Test public void shouldShowRecentSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(20); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findSpittles(Long.MAX_VALUE, 20)).thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB_INF/views/spittles.jsp")).build(); // 调用MockMvc.perform(RequestBuilder requestBuilder)发起一个http请求,而后将获得ResultActions mockMvc.perform(MockMvcRequestBuilders.get("/spittles"))// 添加验证断言来判断执行请求后的结果是不是预期的; .andExpect(MockMvcResultMatchers.view().name("spittles"))// view():获得视图验证器; // 获得相应的***ResultMatchers后,接着再调用其相应的API获得ResultMatcher, // 如ModelResultMatchers.attributeExists(final String... names)判断Model属性是否存在。 .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))// model():获得模型验证器; .andExpect(MockMvcResultMatchers.model().attribute("spittleList", IsCollectionContaining.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 ", new Date())); } return spittles; } } 

SpittleController中,使用@Autowired注解注入了spittleRepository属性。

须要注意的是spittles()方法使用了Model(控制器和视图之间传递的数据)做为入参,Model本质上是一个map,它会被传送至view,所以数据能够提供给客户端。若是在调用addAttribute()方法时没有指定key,那么就会从传入的对象中获取,好比代码中传入的参数属性是List<Spittle>,那么key就是spittleList。最后,该方法返回spittles做为传动给model的视图名称。

也能够显示的指定key:

model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20)); 

也能够直接采用map的方式:

@RequestMapping(method = RequestMethod.GET) public String spittles(Map model) { // 将spittles添加到model中 model.put("spittles", spittleRepository.findSpittles(Long.MAX_VALUE, 20)); // 返回视图名称 return "spittles"; } 

无论采用何种方式实现spittles()方法,结果都是同样的。一个Spittle对象集合会存储在model中,并分配到名为spittles的view中,根据测试方法中的配置,该视图就是/WEB-INF/views/spittles.jsp。

如今model已经有数据了,那么JSP页面中如何获取数据呢?当视图是一个JSP页面时,model数据会做为请求属性被拷贝到请求中,所以能够经过JSTL(JavaServer Pages Standard Tag Library)<c:forEach>来获取:

<c:forEach items="${spittleList}" var="spittle"> <li id="spittle_<c:out value="spittle.id"/>"> <div class="spittleMessage"> <c:out value="${spittle.message}" /> </div> <div> <span class="spittleTime"><c:out value="${spittle.time}" /></span> <span class="spittleLocation"> (<c:out value="${spittle.latitude}" />, <c:out value="${spittle.longitude}" />) </span> </div> </li> </c:forEach> 

下面对SpittleController进行扩展,让它能够处理一些输入。

接受输入请求

Spring MVC提供了以下方式供客户端传递数据到控制器处理方法:

  • Query parameters
  • Form parameters
  • Path variables

处理查询参数:@RequestParam

Spittr应用的一个需求就是要对spittle列表分页展现,可是SpittleController仅仅展现最近的spittle。若是要让用户能够每次获得一页的spittle记录,那么就须要让用户能够经过某种方式将他们想看的spittle记录的参数传递到后台。

在浏览spittle时,若是想要查看下一页的spittle,那么就须要传递比当前页的最后一个spittle的id小一位的id,也能够传递想要展现的spittle的数量。

为了实现分页,须要编写一个控制器知足:

  • before参数,结果中的spittle的id都要在这个参数以前;
  • count参数,结果中要包含的spittle的个数

下面咱们对上面的spittles()方法进行小小的改动,让它可使用before和count参数。首先对测试方法进行改动:

@Test public void shouldShowRecentSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(20); 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(RequestBuilder requestBuilder)发起一个http请求,而后将获得ResultActions mockMvc.perform(MockMvcRequestBuilders.get("/spittles?max=238900&count=50"))// 添加验证断言来判断执行请求后的结果是不是预期的; .andExpect(MockMvcResultMatchers.view().name("spittles"))// view():获得视图验证器; // 获得相应的***ResultMatchers后,接着再调用其相应的API获得ResultMatcher, // 如ModelResultMatchers.attributeExists(final String... names)判断Model属性是否存在。 .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))// model():获得模型验证器; .andExpect(MockMvcResultMatchers.model().attribute("spittleList", IsCollectionContaining.hasItems(expectedSpittles.toArray()))); } 

这个测试方法的主要改动就是它发起的GET请求传递了两个参数:max和count。对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参数没有指定,那么就会使用默认的设置。因为查询参数是String类型的,所以defaultValue属性值也须要设置为String类型,须要对Long.MAX_VALUE进行设置:
private static final String MAX_LONG_AS_STRING = "9223372036854775807";

虽然,这里defaultValue的属性为String类型,当运行到函数时,将会根据函数的参数类型进行转换。

查询参数是请求中传送信息给控制器的最经常使用方式,另一种流行的方式就是将参数做为请求路径的一部分。

经过路径参数传递数据:@PathVariable

假设如今应用须要展现单独的一篇Spittle,那么就须要一个id做为查询参数,对应的处理方法能够是:

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

这个handler方法将会处理形如/spittles/show?spittle_id=12345的请求,可是这并不符合资源导向的观点。理想状况下,应该使用URL路径对资源进行区分,而不是查询参数,即应该使用/spittles/12345这种形式。

为了实现资源导向的控制器,咱们先在测试中得到这个需求(使用了静态引入):

@Test public void testSpittle() throws Exception { Spittle expectedSpittle = new Spittle("Hello", new Date()); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); when(mockRepository.findOne(12345)).thenReturn(expectedSpittle); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spittles/12345")) .andExpect(view().name("spittle")) .andExpect(model().attributeExists("spittle")) .andExpect(model().attribute("spittle", expectedSpittle)); } 

该测试中发起了一个/spittles/12345的GET请求,而且对其返回结果视图进行断言。

为了知足路径参数,Spring MVC容许在@RequestMapping路径中使用占位符(须要用大括号包围),下面是使用占位符来接受一个id做为路径的一部分:

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

spittle()方法的spittleId入参使用了@PathVariable("spittleId")注解,代表请求中占位符位置的值都会被传送到handler的spittleId参数。@RequestMapping中value属性的占位符必须和@PathVariable包裹的参数一致。若是@PathVariable中没有给定参数,那么将默认使用入参的册数参数名。便可以使用下面的方法:

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

spittle()方法会将接收的参数值传递给spittleRepository的findOne()方法并查找到一个Spittle,并将其放置到model中,model的key值会是spittle,接下来就能够在视图中引用这个Spittle:

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

查询参数和路径参数能够处理一些少许的请求数据,可是当请求数据过大时,它们就再也不适用,下面就来说解一下如何处理表单数据。

处理表单

Web应用不只仅是将内容推送给用户,它同时也会让用户填写表单并将数据提交给应用。

对于表单有两种处理方式:展现表单以及处理用户提交的表单数据。在Spittr中,须要提供一个供新用户进行注册的表单。

SpitterController:展现用户注册表单

package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/spitter") public class SpitterController { // 处理来自/spitter/register的get请求 @RequestMapping(value = "/register", method = RequestMethod.GET) public String showRegistrationForm() { return "registerForm"; } } 

showRegistrationForm方法的@RequestMapping注解,以及类级别的注解@RequestMapping,代表了这个方法会处理来自/spitter/register的get请求,该方法仅仅返回了一个名为registerForm的逻辑视图。根据以前在InternalResourceViewResolver中的配置,这个逻辑视图会导向到/WEB-INF/views/registerForm.jsp该界面。

对应的测试方法:

package spittr.web; 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 org.junit.Test; import org.springframework.test.web.servlet.MockMvc; public class SpitterControllerTest { @Test public void shouldShowRegistration() throws Exception { SpitterController controller = new SpitterController(); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spitter/register")).andExpect(view().name("registerForm")); } } 

也能够经过启动项目访问界面的方式验证:

<%@ 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 method="POST"> First Name: <input type="text" name="firstName" /><br/> Last Name: <input type="text" name="lastName" /><br/> Username: <input type="text" name="username" /><br/> Password: <input type="password" name="password" /><br/> <input type="submit" value="Register" /> </form> </body> </html> 
该界面提供了用户注册的功能
该界面提供了用户注册的功能

接下来须要对提交的表单进行处理。

编写表单处理控制器

在处理POST请求时,控制器须要接受表单数据而且将这些数据存储为一个Spitter对象。为了不重复提交,应该重定向到一个新的界面:用户信息页。在处理post请求时,一个聪明的作法就是在处理完成后发送一个重定向的请求,从而能够避免重复提交。

下面来实现控制器方法,从而能够处理注册请求。

private SpitterRepository spitterRepository; public SpitterController() { } // 注入SpitterRepository @Autowired public SpitterController(SpitterRepository spitterRepository) { this.spitterRepository = spitterRepository; } public String processRegistration(Spitter spitter) { // 保存Spitter spitterRepository.save(spitter); // 重定向到新的页面 return "redirect:/spitter/" + spitter.getUsername(); } 

processRegistration方法使用Spitter对象做为入参,该对象的属性会从请求中填充。该方法中调用了spitterRepository的save方法对Spitter对象进行存储。最后返回了一个带有redirect:的字符串。

当InternalResourceViewResolver遇到redirect:时,它会自动地将其当作一个重定向请求,从而能够重定向到用户详情页面,如/spitter/xiaoming。

同时,InternalResourceViewResolver也能够识别前缀forward:,这种状况下,请求会被转向到给定的URL地址。

下面须要编写处理处理用户详情页面的方法:

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

参数校验

从Spring3.0开始,Spring支持Java校验api,从而能够从而能够不须要添加其余配置,仅仅须要有一个Java API 的实现,如Hibernate Validator。

Java Validation API定义了许多注解,可使用这些注解来约束参数的值,全部的注解都在包javax.validation.constraints中。

注解 描述
@AssertFalse(@AssertTrue) 对象必须是布尔类型,而且必须为false(true)
@DecimalMax(value)、@DecimalMin(value) 限制对象必须是一个数字,其值不大于(不小于)指定的BigDecimalString值
@Digits(integer,fraction) 对象必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 必须是一个未来的日期
@Max(value)、@Min(value) 必须为一个不大于(不小于)指定值的数字
@NotNull 限制对象不能为空
@Null 限制对象必须为空
@Past 必须是一个过去的日期
@Pattern(value) 必须符合指定的正则表达式
@Size(min,max) 限制字符长度必须在min到max之间

使用示例:

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; ... 

既然已经对Spitter的参数添加了约束,那么就须要改动processRegistration方法来应用校验:

@RequestMapping(value = "/register", method = RequestMethod.POST) public String processRegistration(@Valid Spitter spitter, Errors errors) { // 若校验中出现错误,那么就返回到注册界面 if (errors.hasErrors()) { return "registerForm"; } // 保存Spitter spitterRepository.save(spitter); // 重定向到新的页面 return "redirect:/spitter/" + spitter.getUsername(); } 

总结

这一章比较适合Spring MVC的入门学习资料。涵盖了Spring MVC处理web请求的处理过程、如何写简单的控制器和控制器方法来处理Http请求、如何使用mockito框架测试控制器方法。

基于Spring MVC的应用有三种方式读取数据:查询参数、路径参数和表单输入。本章用两节介绍了这些内容,并给出了相似错误处理和参数验证等关键知识点。

因为缺乏真正的入库操做,所以本章节的一些方法不能真正的运做。

在接下来的章节中,咱们会对Spring视图进行深刻了解,对如何在JSP页面中使用Spring标签库进行展开。

做者:hoxis 连接:https://www.jianshu.com/p/74357110e4cc 來源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
相关文章
相关标签/搜索