Spring MVC起步

Spring将请求调度Servlet、处理器映射(handler mapping)、控制器以及视图解析器(view resolver)之间移动。
跟踪Spring MVC的请求
Web请求从离开浏览器开始到获取响应返回,它会经历好多站,每站都会留下一些信息同时带上其他信息。

请求带着URL以及其他信息离开浏览器后,第一站是Spring的DispatcherServlet(前端控制器)。它的任务将请求发送给Spring MVC控制器。由于可能有多个控制器,DispatcherServlet会查询一个或多个处理器映射来确定请求的下一站,处理器映射会根据URL信息来决策。选择合适的控制器后,DispatcherServlet将请求发送给选中的控制器。到了控制器,它会处理请求带来的信息。
控制器处理完成后,会将模型数据打包,并且标示出用于渲染输出的视图名。它接下来将请求连同模型和视图名发送回DispatcherServlet,目的是解耦。传递给DispatcherServlet的视图名并不直接标示某个特定的JSP。它仅仅传递了一个逻辑名称,用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器来将逻辑视图名匹配为一个特定的视图实现(可能是JSP)
DispatcherServlet的最后一站是视图的实现(可能是JSP),在这里它交付模型数据。视图将使用模型数据渲染输出,通过响应对象传递给客户端。
搭建Spring MVC
首先配置DispatcherServlet,它是Spring MVC的核心。如果按传统方式,DispatcherServlet会配置在web.xml中。另一种方式可以将DispatcherServlet配置在Servlet容器中。

package spittr.config;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class<?>[] { RootConfig.class };}@Override   // 指定配置类protected Class<?>[] getServletConfigClasses() {return new Class<?>[] { WebConfig.class };}@Override   // 将DispatcherServlet映射到“/”protected String[] getServletMappings() {return new String[]{ "/"};}
}

我们需要知道的是AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置DispatcherServlet和Spring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文中。

AbstractAnnotationConfigDispatcherServletInitializer剖析
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用它来配置Servlet容器。
Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,也就是
AbstractAnnotationConfigDispatcherServletInitializer。因为我们的SpittrWebAppInitializer扩展了AbstractAnnotationConfigDispatcherServletInitializer(同时也就实现了WebApplicationInitializer),因此当部署到Servlet3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。
在本例中,SpittrWebAppInitializer重新了三个方法。
第一个方法是getServletMappings(),它会将一个或多个路径映射到DispatcherServlet上。本例中,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求。
为了理解其他的两个方法,首先要理解DispatcherServlet和一个Servlet监听器(也就是ContextLoaderListener)的关系。
当DispatcherServlet启动时,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。在代码中的getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。
但是在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的。
我们希望DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数据层组件。
实际上,AbstractAnnotationConfigDispatcherServletInitializer会同时创建

DispatcherServlet和ContextLoaderListener。getServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中bean。
在本例中,根配置定义在RootConfig中,DispatcherServlet的配置声明在WebConfig中。
如果按照这种方式配置DispatcherServlet,它只能部署到支持Servlet 3.0的服务器中才能正常工作,如Tomcat 7或更高版本。
启动Spring MVC
启动Spring MVC组件的方法也不仅一种。除了传统的XML配置,还可选择基于Java进行配置。

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
@ComponentScan("spittr.web") // 启动组件扫描
@EnableWebMvc   // 启动spring mvc
public class WebConfig extends WebMvcConfigurerAdapter{@Beanpublic ViewResolver viewResolver(){   // 配置jsp视图解析器InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/views/");resolver.setSuffix(".jsp");resolver.setExposeContextBeansAsAttributes(true);return resolver;}// 配置静态资源的处理@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}
}

程序中第一件需要注意的事情就是WebConfig添加了@ComponentScan注解,因此将会扫描spitter.web包来查找组件。
接下来,添加了viewResolver bean,表示配置视图解析器。如果没有配置,则Spring默认会使用BeanNameViewResolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口。而InternalResourceViewResolver会查找JSP文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀。
最后,新的WebConfig类还扩展了WebMvcConfigurerAdapter并重写了其configureDefaultServletHandling()方法。通过调用DefaultServletHandlerConfigurer的enable()方法,我们要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求。
WebConfig已经就绪,下面来看RootConfig

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

我们基本已经可以使用Spring MVC构建Web应用了。

编写基本的控制器
在Spring MVC中,控制器只是方法上添加了@RequestMapping注解的类,这个注解声明了它们所要处理的请求
下面是简单的HomeController控制器

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

@Controller是一个构造型(stereotype)的注解,它基于@Component注解。在这里,它的目的就是辅助实现组件扫描。因为HomeController带有

@Controller注解,因此组件扫描器会自动找到HomeController,并将其声明为Spring应用上下文中的一个bean。其实也可用@Component,但在表意性上可能会差一些,无法确定HomeController是什么组件类型。
HomeController的home()方法带有@RequestMapping注解。它的value属性指定了这个方法所要处理的请求路径,method属性细化了它所要处理的HTTP方法。在本例中,当收到对“/”的HTTP GET请求时,就会调用home()方法。
home()方法其实只是返回了一个String类型的“home”。这个String将会被Spring MVC解读为要渲染的视图名称。DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图。
鉴于我们配置InternalResourceViewResolver的方式,视图名“home”将会解析为“/views/home.jsp”路径的JSP。下面来定义home.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" session="false"%>
<html>
<head><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,

package spittr.web;import org.junit.Test;
import static org.junit.Assert.assertEquals;public class HomeControllerTest {@Testpublic void testHomePage() throws Exception {HomeController controller = new HomeController();assertEquals("home", controller.home());}
}

这个测试没有断言当接收到针对“/”的GET请求时会调用home()方法。

从Spring 3.2开始,我们可以按照控制器的方式来测试Spring MVC中的控制器了,就是使用mock Spring MVC测试。

package spittr.web;import org.junit.Test;
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.springframework.test.web.servlet.MockMvc;public class HomeControllerTest {@Testpublic void testHomePage() throws Exception {HomeController controller = new HomeController();MockMvc mockMvc =standaloneSetup(controller).build();  // 搭建MockMvcmockMvc.perform(get("/")).   // 对"/"执行GET请求andExpect(view().name("home"));  // 预期得到home视图}
}

定义类级别的请求处理
我们可以拆分@RequestMapping,并将其路径映射部分放到类级别上。

package spittr.web;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;@Controller
@RequestMapping("/")  // 将控制器映射到"/"
public class HomeController {@RequestMapping(method = GET)  // 处理GET请求public String home(){return "home";   // 视图名为 home}
}

当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充。
我们还可以将它映射到对“/homepage”的请求

@Controller
@RequestMapping({"/","/homepage"})
public class HomeController {...
}

传递模型数据到视图中
我们需要有一个页面展现最近提交的Spittle列表。因此,我们需要一个新的方法来处理这个页面。
首先,需要定义一个数据访问的Repository。为了实现解耦以及避免陷入数据库访问的细节之中,我们将Repository定义为一个接口,并在稍后实现它。

package spittr.data;
import spittr.model.Spittle;
import java.util.List;public interface SpittleRepository {List<Spittle> findSpittles(long max, int count);
}

为了获得最新的20个Spittle对象,我们可以这样调用findSpittles():

List<Spittle> recent = spittleRepository.findSpittles(Long.MAX_VALUE, 20);

下面实现Spittle类

package spittr.model;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 longitude, Double latitude){this.id = null;this.message = message;this.time = time;this.longitude = longitude;this.latitude = latitude;}public long getId(){return id;}public String getMessage(){return message;}public Date getTime() {return time;}public Double getLatitude() {return latitude;}public Double getLongitude() {return longitude;}@Overridepublic boolean equals(Object that){return EqualsBuilder.reflectionEquals(this,that,"id", "time");}@Overridepublic int hashCode(){return HashCodeBuilder.reflectionHashCode(this,"id", "time");}
}

然后我们可以编写测试方法。

@Test
public void shouldShowRecentSpittles() throws Exception{List<Spittle> expectedSpittles = createSpittleList(20);SpittleRepository mockRepository = mock(SpittleRepository.class);when(mockRepository.findSpittles(Long.MAX_VALUE, 20)).thenReturn(expectedSpittles);SpittleController controller = new SpittleController(mockRepository);MockMvc mockMvc = standaloneSetup(controller).setSingleView(new InternalResourceView("/views/spittles.jsp")).build();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;
}

需要注意的是,这个测试在MockMvc构造器上调用了setSingleView()。这样的话,mock框架就不用解析控制器中的视图名了。这个场景下,如果不用这种方式,按照默认的视图解析规则时,MockMvc就会发生失败,因为无法区分视图路径和控制器的路径。在这个测试中,构建InternalResourceView时所设置的实际路径是无关紧要的,但我们将其设置为与InternalResourceViewResolver配置一致。
下一步我们创建SpittleController

package spittr.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import spittr.data.SpittleRepository;
import spittr.model.Spittle;
import java.util.List;@Controller
@RequestMapping("/spittles")
public class SpittleController {private SpittleRepository spittleRepository;@Autowiredpublic SpittleController(SpittleRepository spittleRepository){this.spittleRepository = spittleRepository;}@RequestMapping(method=RequestMethod.GET)public String spittles(Model model) {// 将spittle添加到模型中model.addAttribute(spittleRepository   .findSpittles(Long.MAX_VALUE,20));return "spittles";  // 返回视图名}
}

需要注意的是,我们在spittles()方法中定了一个Model作为参数。这样,spittles()方法就能将Repository中获取到的Spittle列表填充到模型中。Model实际上就是一个Map(可直接将Model替换为Map类),它会传递给视图,这样数据就能渲染到客户端了,当调用addAttribule()方法并且不指定key的时候,那么key会根据值的对象类型推断确定。在本例中,因为它是一个List,因此,键将会推断为spittleList。
spittles()方法所做的最后一件事是返回spittles作为视图的名字,这个视图会渲染模型。
也可以显示声明模型的key

@RequestMapping(method=RequestMethod.GET)public String spittles(Model model) {model.addAttribute("spittleList", spittleRepository   .findSpittles(Long.MAX_VALUE,20));return "spittles"; }

下面还有另一种方式来编写spittles()方法:

@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(){return spittleRepository.findSpittles(Long.MAX_VALUE, 20);
}

当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据其类型推断得出(在本例中,也就是spittleList)。
而逻辑视图的名称将会根据请求路径推断得出。因为这个方法处理针对“/spittles”的GET请求,因此视图的名称将会是spittles(去掉开头的斜线)。
现在,数据已经放到了模型中,当视图是JSP的时候,模型数据会作为请求属性放到请求之中。因此,在spittles.jsp文件中可以使用JSTL的<c:forEach>标签渲染spittle列表:

<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.toString()}"/></span><span class="spittleLocation">(<c:out value="${spittle.latitude}"/>, <c:out value="${spittle.longitude}"/>)</span></div></li>
</c:forEach>

接受请求的输入

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

  • 查询参数(Query Parameter)
  • 表单参数(Form Parameter)
  • 路径变量(Path Variable)

处理查询参数
我们来实现一个查询Spittle列表的分页功能。为了实现这个功能,我们所编写的处理器方法要接受如下的参数:

  • before参数(表明结果中所有Spittle的ID均应该在这个值之前)
  • count参数(表明在结果中要包含的Spittle数量)
    我们首先添加一个测试,这个测试反映了新spittles()方法的功能
    @Testpublic void shouldShowPagedSpittles() throws Exception{List<Spittle> expectedSpittles = createSpittleList(50);SpittleRepository mockRepository = mock(SpittleRepository.class);when(mockRepository.findSpittles(238900,50)).thenReturn(expectedSpittles);SpittleController controller = new SpittleController(mockRepository);MockMvc mockMvc = standaloneSetup(controller).setSingleView(new InternalResourceView("/views/spittles.jsp")).build();mockMvc.perform(get("/spittles?max=238900&count=50")).andExpect(view().name("spittles")).andExpect(model().attributeExists("spittleList")).andExpect(model().attribute("spittleList",hasItems(expectedSpittles.toArray())));}

这个测试方法针对“/spittles”发送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和count都指定了默认值。
通过路径参数接受输入
假设我们的应用程序需要根据给定的ID来展现某一个Spittle记录。其中一种方法就是编写处理器方法,通过使用@RequestParam注解,让它接受ID作为查询参数。另一种方式,要识别的资源应该通过URL路径进行标示。对“/spittles/12345”发起GET请求要优于对“/spittles/show?spittle_id=12345”发起请求。
我们先将这个需求转换为一个测试

    @Testpublic void testSpittle() throws Exception{Spittle expectedSpittle = new Spittle("Hello" , new Date());SpittleRepository mockRepository = 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));}

在Controller中,添加一个针对这种方式请求的方法

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

Spring MVC允许我们在@RequestMapping路径中添加占位符。占位符的名称要用大括号(“{”和“}”)括起来。路径中的其他部分要与所处理的请求完全匹配,但是占位符可以是任意的值。

我们可以看到,spittle()方法的spittleId参数上添加了@PathVariable(“spittleId”)注解,这表明在请求路径中,不管占位符部分的值是什么都会传递到处理器方法的spittleId参数中。
需要注意的是:在样例中spittleId这个词出现了好几次,因为方法的参数名碰巧与占位符的名称相同,因此我们可以去掉@PathVariable中的value属性:

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

需要注意的是,如果想要重命名参数时,必须要同时修改占位符的名称,使其互相匹配。如下为渲染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>

处理表单

Spring MVC的控制器也为表单处理提供了良好的支持。

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

package spittr.web;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.RequestPart;
import spittr.data.SpitterRepository;
import spittr.model.Spitter;
import static org.springframework.web.bind.annotation.RequestMethod.*;@Controller
@RequestMapping("/spitter")
public class SpitterController {@RequestMapping(value="/register", method=GET)public String showRegistrationForm(Model model) {return "registerForm";}
}

因为视图的名称为registerForm,所以JSP的名称需要是registerForm.jsp。如下就是我们现在所要使用的JSP

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" language="java" pageEncoding="UTF-8" contentType="text/html; charset=utf-8"%>
<!DOCTYPE HTML>
<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 path="firstName" /><br/>Last Name: <input path="lastName" /><br/>Email: <input type="email" path="email" /><br/>Username: <input path="username" /><br/>Password: <password path="password" /><br/><input type="submit" value="Register" /></form></body>
</html>

需要注意的是:这里的标签中并没有设置action属性。在这种情况下,当表单提交时,它会提交到与展现时相同的URL路径上。也就是,它会提交到“/spitter/register”上。
编写处理表单的控制器
当处理注册表单的POST请求时,控制器需要接受表单数据并将表单数据保存为Spitter对象。最后,为了防止重复提交(用户点击浏览器的刷新按钮又可能会发生这种情况),应该将浏览器重定向到新创建用户的基本信息页面。这些行为通过下面的shouldProcessRegistration()进行测试。

    @Testpublic void shouldProcessRegistration() throws Exception {SpitterRepository mockRepository = mock(SpitterRepository.class);Spitter unsaved = new Spitter("jbauer", "24hours","Jack", "Bauer");Spitter saved = new Spitter(24L,"jbauer", "24hours","Jack", "Bauer");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").andExpect(redirectedUrl("/spitter/jbauer"));verify(mockRepository,atLeastOnce()).save(unsaved);}

在处理POST类型的请求时,在请求处理完成后,最好进行一下重定向,这样浏览器的刷新就不会重复提交表单了。在这个测试中,预期请求会重定向到“/spitter/jbauer”,也就是新建用户的基本信息页面。
最后,测试会校验SpitterRepository的mock实现最终会真正用来保存表单上传入的数据。
现在我们来实现处理表单提交的控制器方法。

package spittr.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
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 spittr.data.SpitterRepository;
import spittr.model.Spitter;
import static org.springframework.web.bind.annotation.RequestMethod.*;@Controller
@RequestMapping("/spitter")
public class SpitterController {private SpitterRepository spitterRepository;@Autowiredpublic SpitterController(SpitterRepository spitterRepository){this.spitterRepository = spitterRepository;}@RequestMapping(value="/register", method=GET)public String showRegistrationForm(Model model) {return "registerForm";}@RequestMapping(value="/register", method=POST)public String processRegistration(Spitter spitter) {spitterRepository.save(spitter);return "redirect:/spitter/"+spitter.getUsername();}
}

当使用Spitter对象调用processRegistration()方法时,它会进而调用SpitterRepository的save()方法。
processRegistration()方法做的最后一件事就是返回一个String类型,用来指定视图。
当InternalResourceViewResolver看到视图格式中的“redirect:”前缀时,它就知道要将其解析为重定向的规则,而不是视图的名称。
需要注意的是,除了“redirect:”,InternalResourceViewResolver还能识别“forward:”前缀。当它发现视图格式中以“forward:”最为前缀时,请求将会前往(forward)指定的URL路径,而不再是重定向。
下面在SpitterController中添加一个处理器方法

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

像其他视图一样,现在的基本信息视图非常简单:

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


校验表单
如果用户在提交表单的时候,文本域为空,会导致新建对象中某些关键字段为空。如果不处理这种现象,将会出现安全问题。
从Spring 3.0开始,在Spring MVC中提供了对Java校验API的支持。在Spring MVC中要使用它的话,只要保证在类路径下包含这个Java API的实现即可,比如Hibernate Validator。
Java校验API定义了多个注解,这些注解可以放到属性上,从而限制这些属性的值。所有注解都位于javax.validation.constraints包中。下表列出了这些校验注解

除了以上表提供的注解,Java校验API的实现可能还会提供额外的校验注解。同时,也可以定义自己的限制条件。
我们现在把Spitter类的属性添加校验注解

package spittr.model;import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import  org.apache.commons.lang3.builder.EqualsBuilder;
import  org.apache.commons.lang3.builder.HashCodeBuilder;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;public Spitter() {}public Spitter(String username, String password, String firstName, String lastName) {this(null, username, password, firstName, lastName);}public Spitter(Long id, String username, String password, String firstName, String lastName) {this.id = id;this.username = username;this.password = password;this.firstName = firstName;this.lastName = lastName;}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;}@Overridepublic boolean equals(Object that) {return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password");}@Overridepublic int hashCode() {return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password");}
}

现在,Spitter的所有属性都添加了@NotNull和@Size,接下来需要修改processRegistration()方法来应用校验功能。

    @RequestMapping(value="/register", method=POST)public String processRegistration(@Valid Spitter spitter,   // 校验 Spitter输入Errors errors) {if (errors.hasErrors()) {return "registerForm";   // 如果校验出现错误,则重新返回表单}spitterRepository.save(spitter);return "redirect:/spitter/" + spitter.getUsername();}

Spitter参数添加了@Valid注解,这会告知Spring,需要确保这个对象满足校验限制。
如果校验出现错误的话,那么这些错误可以通过Errors对象进行访问,现在这个对象已作为processRegistration()方法的参数。(很重要一点需要注意,Errors参数要紧跟在带有@Valid注解的参数后面,@Valid注解所标注的就是要检验的参数。)processRegistration()方法所做的第一件事就是调用Errors.hasErrors()来检查是否有错误。
如果有错误的话,Errors.hasErrors()将会返回到registerForm,也就是注册表单的视图。如果没有错误的话,Spitter对象将会通过保存,控制器会像之前那样重定向到基本信息页面。

注:本文来自spring实战(第4版)所学以及简书文章参考。

构建Spring Web应用程序相关推荐

  1. spring(5)构建 spring web 应用程序

    [0]README 1)本文部分文字描述转自:"Spring In Action(中/英文版)",旨在review  "spring(5)构建 spring web 应用 ...

  2. Spring实战 | 第二部分 Web中的Spring(第五章 构建Spring Web应用程序)

    第五章 构建Spring Web应用程序 映射请求到Spring控制器 透明地绑定表单参数 校验表单提交 一.Spring MVC起步 1.跟踪spring MVC的请求 在请求离开浏览器时,会带有用 ...

  3. 使用Hibernate和Spring构建Java Web应用程序

    这篇文章将展示如何在Spring环境中使用带有Hibernate ORM的MYSQL DB创建学生注册应用程序. 这是一个简单的应用程序,旨在在注册过程中从用户收集输入详细信息,将这些详细信息保存在M ...

  4. 使用MyBatis和Spring构建Java Web应用程序

    这篇文章将展示如何在Spring环境中使用带有MyBatis框架的MYSQL DB创建学生注册应用程序. 这是一个简单的应用程序,旨在在注册期间从用户收集输入详细信息,将详细信息保存在MYSQL DB ...

  5. 使用Spring的Hibernate构建Java Web应用程序

    这篇文章将展示如何在Spring环境中使用带有Hibernate ORM的MYSQL DB创建学生注册应用程序. 这是一个简单的应用程序,旨在在注册期间从用户收集输入详细信息,将详细信息保存在MYSQ ...

  6. 程序员疯狂记事:如何利用众多技术栈构建一个 Web 应用程序?!

    [CSDN 编者按]"Elixir.Phoenix.Absinthe.GraphQL.React和Apollo"--在这几个关键词中,有几个是身为开发者的你一直想玩但还没来得及玩的 ...

  7. spring_了解Spring Web应用程序体系结构:经典方法

    spring 每个开发人员必须了解两件事: 架构设计是必要的. 精美的架构图并未描述应用程序的真实架构. 真正的体系结构是从开发人员编写的代码中找到的,如果不设计应用程序的体系结构,最终将得到一个具有 ...

  8. spring体系结构_了解Spring Web应用程序体系结构:经典方法

    spring体系结构 每个开发人员必须了解两件事: 架构设计是必要的. 花哨的体系结构图没有描述应用程序的真实体系结构. 真正的体系结构是从开发人员编写的代码中找到的,如果不设计应用程序的体系结构,最 ...

  9. java web mvc_构建Java Web应用程序时遵循MVC的三个步骤

    java web mvc 第1步 做 始终通过servlet / action bean处理URL(POST表单,单击链接等),而不是通过JSP处理 为什么 ActionBeans(无论某些框架调用那 ...

  10. 了解Spring Web应用程序体系结构:经典方法

    每个开发人员必须了解两件事: 架构设计是必要的. 花哨的体系结构图没有描述应用程序的真实体系结构. 真正的体系结构是从开发人员编写的代码中找到的,如果不设计应用程序的体系结构,最终将得到一个具有多个体 ...

最新文章

  1. awk4.0 — awk格式化
  2. PostgreSQL数据库数组相似度计算
  3. MySQL 笔记3 -- SQL 语言
  4. boost::detail::sp_typeinfo用法实例
  5. xshell执行结果到文本_xshell拷贝文件到本地
  6. linux脚本实现scp命令自动输入密码和yes/no等确认信息
  7. 【转】Mongodb 学习笔记
  8. java中匿名数组_Swagger UI:数组中的多个匿名对象
  9. rpc 服务器不可用_什么是远程过程调用RPC
  10. 使用flash在IPAD2上播放FLV效率不高
  11. 10 款最新的 jQuery 内容滑块插件
  12. 开心消消乐、纯前端实现开心消消乐、开心消消乐代码、HTML+JS实现开心消消乐
  13. mysql directx repair_[MySQL]快速解决is marked as crashed and should be repaired故障
  14. 氚云SaaS介绍文档
  15. 怎么考核程序员?(文末有互动红包)
  16. Xcode8 注释 快捷键
  17. 51单片机学习笔记7 -- 超声波测距
  18. Android马甲包
  19. 湘潭大学java实验
  20. 双系统(win10+ubuntu)引导页消失

热门文章

  1. mysql emoy表情_emo表情包 - emo微信表情包 - emoQQ表情包 - 发表情 fabiaoqing.com
  2. dns劫持教您dns被劫持如何修复、dns劫持如何修复
  3. 小青龙的Java面试笔记
  4. 如何从零创造一个围棋AI
  5. 我编写的肺炎疫情数据API的实现过程
  6. 将一个给定的 n×n 方阵转置(行列互换)
  7. 音频3A测试 NS降噪测试
  8. html5接金币游戏源码,利用HTML5实现Canvas聚宝盆接金币游戏
  9. DES加密解密base64转码和iphone平台一致结果
  10. IntelliJ IDEA(2017)安装和破解。