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

之前一直在看《Spring实战》第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读《Spring实战》第四版了,章节安排与之前不同了,里面应用的应该是最新的技术。

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

Spring MVC初探

跟踪Spring MVC请求

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

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

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

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

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

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

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

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

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

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

搭建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</failonmissingwebxml>这样的错误,那么可以通过在pom.xml文件中添加如下配置来避免这种错误:

<span class="hljs-tag"><<span class="hljs-name">build>
    <span class="hljs-tag"><<span class="hljs-name">plugins>
        <span class="hljs-tag"><<span class="hljs-name">plugin>
            <span class="hljs-tag"><<span class="hljs-name">groupId>org.apache.maven.plugins<span class="hljs-tag">groupId>
            <span class="hljs-tag"><<span class="hljs-name">artifactId>maven-war-plugin<span class="hljs-tag">artifactId>
            <span class="hljs-tag"><<span class="hljs-name">version>2.6<span class="hljs-tag">version>
            <span class="hljs-tag"><<span class="hljs-name">configuration>
                <span class="hljs-tag"><<span class="hljs-name">failOnMissingWebXml>false<span class="hljs-tag">failOnMissingWebXml>
            <span class="hljs-tag">configuration>
        <span class="hljs-tag">plugin>
    <span class="hljs-tag">plugins>
<span class="hljs-tag">build>
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

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

<span class="hljs-keyword">package spittr.config;

<span class="hljs-keyword">import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">SpittrWebAppInitializer <span class="hljs-keyword">extends <span class="hljs-title">AbstractAnnotationConfigDispatcherServletInitializer {

    </span></span></span></span></span></span></span></span>

任意继承自 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></mvc:annotation-driven>来激活基于注解的Spring MVC。

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

<span class="hljs-keyword">package spittr.config;

<span class="hljs-keyword">import org.springframework.context.annotation.Configuration;
<span class="hljs-keyword">import org.springframework.web.servlet.config.annotation.EnableWebMvc;

</span></span></span>

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

这样就可以激活Spring MVC了,但是还有其他一些问题:

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

因此,需要为WebConfig增加一些配置:

<span class="hljs-keyword">package spittr.config;

<span class="hljs-keyword">import org.springframework.context.annotation.Bean;
<span class="hljs-keyword">import org.springframework.context.annotation.ComponentScan;
<span class="hljs-keyword">import org.springframework.context.annotation.Configuration;
<span class="hljs-keyword">import org.springframework.web.servlet.ViewResolver;
<span class="hljs-keyword">import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
<span class="hljs-keyword">import org.springframework.web.servlet.config.annotation.EnableWebMvc;
<span class="hljs-keyword">import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
<span class="hljs-keyword">import org.springframework.web.servlet.view.InternalResourceViewResolver;

</span></span></span></span></span></span></span></span></span>

首先需要注意的是, 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。

<span class="hljs-keyword">package spittr.config;

<span class="hljs-keyword">import org.springframework.context.annotation.ComponentScan;
<span class="hljs-keyword">import org.springframework.context.annotation.ComponentScan.Filter;
<span class="hljs-keyword">import org.springframework.context.annotation.Configuration;
<span class="hljs-keyword">import org.springframework.context.annotation.FilterType;
<span class="hljs-keyword">import org.springframework.web.servlet.config.annotation.EnableWebMvc;

</span></span></span></span></span></span>

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

Spittr应用介绍

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

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

编写简单的控制器

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

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

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import org.springframework.stereotype.Controller;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMethod;

</span></span></span></span>

@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

<span class="hljs-tag"><<span class="hljs-name">%@ <span class="hljs-attr">page <span class="hljs-attr">language=<span class="hljs-string">"java" <span class="hljs-attr">contentType=<span class="hljs-string">"text/html; charset=UTF-8"
    <span class="hljs-attr">pageEncoding=<span class="hljs-string">"UTF-8"%>

<span class="hljs-tag"><<span class="hljs-name">%@ <span class="hljs-attr">taglib <span class="hljs-attr">prefix=<span class="hljs-string">"c" <span class="hljs-attr">uri=<span class="hljs-string">"http://java.sun.com/jsp/jstl/core"%>
<span class="hljs-tag"><<span class="hljs-name">%@ <span class="hljs-attr">taglib <span class="hljs-attr">prefix=<span class="hljs-string">"spring" <span class="hljs-attr">uri=<span class="hljs-string">"http://www.springframework.org/tags"%>

<span class="hljs-tag"><<span class="hljs-name">html>
<span class="hljs-tag"><<span class="hljs-name">head>
<span class="hljs-tag"><<span class="hljs-name">meta <span class="hljs-attr">charset=<span class="hljs-string">"utf-8">
<span class="hljs-tag"><<span class="hljs-name">title>Spittr<span class="hljs-tag">title>
<span class="hljs-tag"><<span class="hljs-name">link <span class="hljs-attr">rel=<span class="hljs-string">"stylesheet" <span class="hljs-attr">type=<span class="hljs-string">"text/css"
    <span class="hljs-attr">href=<span class="hljs-string">"<c:url value="/<span class=" hljs-attr">resources/<span class="hljs-attr">style.css" />">
<span class="hljs-tag">head>
<span class="hljs-tag"><<span class="hljs-name">body>
    <span class="hljs-tag"><<span class="hljs-name">h1>Welcome to Spittr<span class="hljs-tag">h1>
    <span class="hljs-tag"><<span class="hljs-name">a <span class="hljs-attr">href=<span class="hljs-string">"<c:url value="/<span class=" hljs-attr">spittles" />">Spittles<span class="hljs-tag">a> |
    <span class="hljs-tag"><<span class="hljs-name">a <span class="hljs-attr">href=<span class="hljs-string">"<c:url value="/<span class=" hljs-attr">spitter/<span class="hljs-attr">register" />">Register<span class="hljs-tag">a>
<span class="hljs-tag">body>
<span class="hljs-tag">html>
</span></span></span></span></c:url></span></span></span></span></span></c:url></span></span></span></span></span></span></span></span></span></span></span></c:url></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

下面对HomeController进行测试。

测试控制器

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

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

主页

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

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import org.junit.Test;
<span class="hljs-keyword">import org.springframework.test.web.servlet.MockMvc;
<span class="hljs-keyword">import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
<span class="hljs-keyword">import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
<span class="hljs-keyword">import org.springframework.test.web.servlet.setup.MockMvcBuilders;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">HomeControllerTest {

    </span></span></span></span></span></span></span></span></span></span>

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

定义类层级的请求处理

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import org.springframework.stereotype.Controller;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMethod;

</span></span></span></span>

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

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

这种情况下,home()方法就可以处理来自 //homepage的GET请求。

将model数据传送给视图

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

<span class="hljs-keyword">package spittr.data;

<span class="hljs-keyword">import java.util.List;
<span class="hljs-keyword">import spittr.Spittle;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">interface <span class="hljs-title">SpittleRepository {
    </span></span></span></span></span></span></span>

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

下面对Spittle进行定义:

<span class="hljs-keyword">package spittr;

<span class="hljs-keyword">import java.util.Date;

<span class="hljs-keyword">import org.apache.commons.lang3.builder.EqualsBuilder;
<span class="hljs-keyword">import org.apache.commons.lang3.builder.HashCodeBuilder;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">Spittle {
    <span class="hljs-keyword">private <span class="hljs-keyword">final Long id;
    <span class="hljs-keyword">private <span class="hljs-keyword">final String message;</span></span></span></span></span></span></span></span></span></span></span></span>

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

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

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

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

当然,现在运行这个测试代码肯定是会出错的,因为还没有SpittleController。

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import java.util.ArrayList;
<span class="hljs-keyword">import java.util.Date;
<span class="hljs-keyword">import java.util.List;

<span class="hljs-keyword">import org.hamcrest.core.IsCollectionContaining;
<span class="hljs-keyword">import org.junit.Test;
<span class="hljs-keyword">import org.mockito.Mockito;
<span class="hljs-keyword">import org.springframework.test.web.servlet.MockMvc;
<span class="hljs-keyword">import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
<span class="hljs-keyword">import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
<span class="hljs-keyword">import org.springframework.test.web.servlet.setup.MockMvcBuilders;
<span class="hljs-keyword">import org.springframework.web.servlet.view.InternalResourceView;

<span class="hljs-keyword">import spittr.Spittle;
<span class="hljs-keyword">import spittr.data.SpittleRepository;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">SpittleControllerTest {

    </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

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

需要注意的是 spittles()方法使用了Model( 控制器和视图之间传递的数据)作为入参,Model本质上是一个map,它会被传送至view,因此数据可以提供给客户端。如果在调用 addAttribute()方法时没有指定key,那么就会从传入的对象中获取,比如代码中传入的参数属性是List

也可以显示的指定key:

model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, <span class="hljs-number">20));
</span>

也可以直接采用map的方式:

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

现在model已经有数据了,那么JSP页面中如何获取数据呢?当视图是一个JSP页面时,model数据会作为请求属性被拷贝到请求中,因此可以通过JSTL(JavaServer Pages Standard Tag Library) <c:foreach></c:foreach>来获取:

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

下面对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参数。首先对测试方法进行改动:

这个测试方法的主要改动就是它发起的GET请求传递了两个参数:max和count。对 spittles()进行修改:

这种情况下,如果没有max参数没有指定,那么就会使用默认的设置。由于查询参数是String类型的,因此 defaultValue属性值也需要设置为String类型,需要对Long.MAX_VALUE进行设置:
private static final String MAX_LONG_AS_STRING = "9223372036854775807";

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

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

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

假设现在应用需要展示单独的一篇Spittle,那么就需要一个id作为查询参数,对应的处理方法可以是:

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

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

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

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

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

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

<div <span class="hljs-class"><span class="hljs-keyword">class=<span class="hljs-string">"spittleView">
    <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div <span class="hljs-attr">class=<span class="hljs-string">"spittleMessage">
        <span class="hljs-tag"><<span class="hljs-name">c:out <span class="hljs-attr">value=<span class="hljs-string">"${spittle.message}" />
    <span class="hljs-tag">div>
    <span class="hljs-tag"><<span class="hljs-name">div>
        <span class="hljs-tag"><<span class="hljs-name">span <span class="hljs-attr">class=<span class="hljs-string">"spittleTime"><span class="hljs-tag"><<span class="hljs-name">c:out <span class="hljs-attr">value=<span class="hljs-string">"${spittle.time}" /><span class="hljs-tag">span>
    <span class="hljs-tag">div>
<span class="hljs-tag">div>
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></div>

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

处理表单

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

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

SpitterController:展示用户注册表单

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import org.springframework.stereotype.Controller;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import org.springframework.web.bind.annotation.RequestMethod;

</span></span></span></span>

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

对应的测试方法:

<span class="hljs-keyword">package spittr.web;

<span class="hljs-keyword">import <span class="hljs-keyword">static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
<span class="hljs-keyword">import <span class="hljs-keyword">static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
<span class="hljs-keyword">import <span class="hljs-keyword">static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

<span class="hljs-keyword">import org.junit.Test;
<span class="hljs-keyword">import org.springframework.test.web.servlet.MockMvc;

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">SpitterControllerTest {

    </span></span></span></span></span></span></span></span></span></span></span></span></span>

也可以通过启动项目访问界面的方式验证:

<span class="hljs-tag"><<span class="hljs-name">%@ <span class="hljs-attr">taglib <span class="hljs-attr">uri=<span class="hljs-string">"http://java.sun.com/jsp/jstl/core" <span class="hljs-attr">prefix=<span class="hljs-string">"c" %>
<span class="hljs-tag"><<span class="hljs-name">%@ <span class="hljs-attr">page <span class="hljs-attr">session=<span class="hljs-string">"false" %>
<span class="hljs-tag"><<span class="hljs-name">html>
  <span class="hljs-tag"><<span class="hljs-name">head>
    <span class="hljs-tag"><<span class="hljs-name">title>Spitter<span class="hljs-tag">title>
    <span class="hljs-tag"><<span class="hljs-name">link <span class="hljs-attr">rel=<span class="hljs-string">"stylesheet" <span class="hljs-attr">type=<span class="hljs-string">"text/css" <span class="hljs-attr">href=<span class="hljs-string">"<c:url value="/<span class=" hljs-attr">resources/<span class="hljs-attr">style.css" />" >
  <span class="hljs-tag">head>
  <span class="hljs-tag"><<span class="hljs-name">body>
    <span class="hljs-tag"><<span class="hljs-name">h1>Register<span class="hljs-tag">h1>

    <span class="hljs-tag"><<span class="hljs-name">form <span class="hljs-attr">method=<span class="hljs-string">"POST">
      First Name: <span class="hljs-tag"><<span class="hljs-name">input <span class="hljs-attr">type=<span class="hljs-string">"text" <span class="hljs-attr">name=<span class="hljs-string">"firstName" /><span class="hljs-tag"><<span class="hljs-name">br/>
      Last Name: <span class="hljs-tag"><<span class="hljs-name">input <span class="hljs-attr">type=<span class="hljs-string">"text" <span class="hljs-attr">name=<span class="hljs-string">"lastName" /><span class="hljs-tag"><<span class="hljs-name">br/>
      Username: <span class="hljs-tag"><<span class="hljs-name">input <span class="hljs-attr">type=<span class="hljs-string">"text" <span class="hljs-attr">name=<span class="hljs-string">"username" /><span class="hljs-tag"><<span class="hljs-name">br/>
      Password: <span class="hljs-tag"><<span class="hljs-name">input <span class="hljs-attr">type=<span class="hljs-string">"password" <span class="hljs-attr">name=<span class="hljs-string">"password" /><span class="hljs-tag"><<span class="hljs-name">br/>
      <span class="hljs-tag"><<span class="hljs-name">input <span class="hljs-attr">type=<span class="hljs-string">"submit" <span class="hljs-attr">value=<span class="hljs-string">"Register" />
    <span class="hljs-tag">form>
  <span class="hljs-tag">body>
<span class="hljs-tag">html>
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></c:url></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

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

该界面提供了用户注册的功能

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

编写表单处理控制器

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

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

    <span class="hljs-keyword">private SpitterRepository spitterRepository;

    <span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-title">SpitterController<span class="hljs-params">() {
    }

    </span></span></span></span></span>

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

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

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

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

参数校验

从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之间

使用示例:

<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">Spitter {

    <span class="hljs-keyword">private Long id;

    </span></span></span></span></span>

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

总结

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

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

由于缺少真正的入库操作,因此本章节的一些方法不能真正的运作。

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

Original: https://www.cnblogs.com/shizhijie/p/10286883.html
Author: 人情世故
Title: 《Spring实战》学习笔记-第五章:构建Spring web应用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/539668/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球