在Spring MVC中,控制器只是方法上添加了@RequestMapping注解 的类,这个注解声明了它们所要处理的请求。
-
假设控制器类要处理对“/”的请求, 并渲染应用的首页。程序清单5.3所示的HomeController可能是最 简单的Spring MVC控制器类了。
程序清单5.3 HomeController:超级简单的控制器* package com.spring.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * 声明一个控制器 * @author huyingqi */ @Controller public class HomeController { @RequestMapping(value = "/",method = GET ) public String home(){ //视图名为home return "home"; } }
-
在上述内容中,我们可以注意到HomeController带有@Controller注解。
- 这个注解用于声明控制器,但它对Spring MVC本身的影响并不大。
-
HomeController是一个构造型注解,基于@Component注解。
- 它的目的是辅助实现组件扫描。
- 由于HomeController带有@Controller注解,组件扫描器会自动找到它,并将其声明为Spring应用上下文中的一个bean。
实际上,你也可以让HomeController带有@Component注解,它的效果是一样的。但在表意性上可能会有所差别,无法确定HomeController是什么类型的组件。
-
HomeController中唯一的方法是home()方法,它带有@RequestMapping注解。
- value属性
- 指定了该方法要处理的请求路径,
- method属性
- 细化了它所处理的HTTP方法。
- 在这个例子中,当收到对"/"的HTTP GET请求时,将调用home()方法。
- value属性
-
在home()方法中,并没有做太多的事情,它只是返回一个String类型的"home"。这个字符串将被Spring MVC解释为要渲染的视图名称。DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图。
鉴于我们配置InternalResourceViewResolver的方式,视图 名“home”将会解析为“/WEB-INF/views/home.jsp”路径的JSP 。现在, 我们会让Spittr应用的首页相当简单,如下所示。
程序清单5.4 Spittr应用的首页,定义为一个简单的JSP
[图片]
这个JSP并没有太多需要注意的地方。它只是欢迎应用的用户,并提 供了两个链接:一个是查看Spittle列表,另一个是在应用中进行 注册。图5.2展现了此时的首页是什么样子的。
在本章完成之前,我们将会实现处理这些请求的控制器方法。但现在,让我们对这个控制器发起一些请求,看一下它是否能够正常工作。测试控制器最直接的办法可能就是构建并部署应用,然后通过浏 览器对其进行访问,但是自动化测试可能会给你更快的反馈和更一致 的独立结果。所以,让我们编写一个针对HomeController的测 试。
[图片]
图5 .2 当前的Spittr首页
5.2.1 测试控制器
让我们再审视一下HomeController。如果你眼神不太好的话,你 甚至可能注意不到这些注解,所看到的仅仅是一个简单的POJO 。我 们都知道测试POJO是很容易的。因此,我们可以编写一个简单的类 来测试HomeController,如下所示:
程序清单5.5 HomeControllerTest:测试HomeControllerpackage com.spring.mvc; import com.spring.mvc.controller.HomeController; import org.junit.Assert; import org.junit.Test; public class HomeControllerTest { @Test public void testHomePage() throws Exception { HomeController homeController = new HomeController(); Assert.assertEquals("home",homeController.home()); } } -
在程序清单5.5中的测试非常简单,它只测试了home()方法的行为。测试直接调用home()方法,并断言返回的字符串包含"home"值。这个测试并没有从Spring MVC控制器的角度进行测试,它没有断言当接收到针对"/"的GET请求时会调用home()方法。由于返回的值正好是"home",所以也没有真正判断"home"是否是视图的名称。
然而,从Spring 3.2开始,我们可以使用控制器的方式来测试Spring MVC中的控制器,而不仅仅作为POJO进行测试。Spring现在提供了一种模拟Spring MVC并执行HTTP请求的机制。这样,在测试控制器时,就不需要启动Web服务器和Web浏览器了。
-
为了演示如何测试Spring MVC控制器,我们重写了HomeControllerTest,并使用了Spring MVC中的新测试特性。程序清单5.6展示了新的HomeControllerTest。
程序清单5.6 改进HomeControllerTest
新版本的测试相比之前的版本只多了几行代码,但它更完整地测试了HomeController。这次测试不是直接调用home()方法并测试返回值,而是发起了对"/"的GET请求,并断言结果视图的名称为"home"。首先,它使用HomeControllerMockMvcBuilders.standaloneSetup()创建一个MockMvc实例,并调用build()方法进行构建。然后,使用MockMvc实例执行针对"/"的GET请求,并设置期望得到的视图名称。
5.2.2 定义类级别的请求处理现在,已经为HomeController编写了测试,那么我们可以做一些重构,并通过测试来保证不会对功能造成什么破坏。
我们可以做的一件事就是拆分@RequestMapping,并将其路径映射部分放到类级别 上。
-
程序清单5.7展示了这个过程。
程序清单5.7 拆分HomeController中的@RequestMapping- package com.spring.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * 声明一个控制器 * @author huyingqi */ @Controller. ——>将控制器映射到“/” @RequestMapping("/) public class HomeController { @RequestMapping(method = GET ) //处理GET请求 public String home(){ //视图名为home return "home"; ——> 视图名为home } }
-
在这个新版本的HomeController中,路径被转移到类级别的@RequestMapping上,而HTTP方法仍然映射在方法级别上。
- 当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。
- 处理器方法上的@RequestMapping注解会补充类级别上的@RequestMapping的声明。
-
对于HomeController来说,只有一个控制器方法。
- 在与类级别的@RequestMapping合并之后,该方法的@RequestMapping注解明确了它将处理对"/"路径的GET请求。
换句话说,我们实际上没有改变任何功能,只是将一些代码移动了位置,但HomeController所做的事情与之前相同。由于我们现在有了测试,可以确保在这个过程中没有破坏原有的功能。
-
当我们在修改@RequestMapping时,还可以对HomeController 做另外一个变更。
@RequestMapping的value属性能够接受一 个String类型的数组。
到目前为止,我们给它设置的都是一个 String类型的“/” 。
-
但是,我们还可以将它映射到对“/homepage”的 请求,只需将类级别的@RequestMapping改为如下所示:
- - @Controller. ——>将控制器映射到“/” @RequestMapping("/","/homepage") public class HomeController { }
现在,HomeController的home ()方法能够映射到对“/”和“/homepage”的GET请求。
5.2.3 传递模型数据到视图中
- HomeController是一个简单的控制器样例,但大多数控制器并不是这么简单。
- 在Spittr应用中,需要有一个页面来展示最近提交的Spittle列表,因此需要一个新的方法来处理这个页面。
- 首先,需要定义一个数据访问的Repository。为了解耦和避免陷入数据库访问的细节,将Repository定义为一个接口,并在稍后实现它(在第10章中)。
-
目前,只需要一个能够获取Spittle列表的Repository,下面是一个足够实现功能的SpittleRepository的示例。
public. interface Spilttlerepository{ List<Spittle> findSpittleRepository(long max,int count); }
-
-
findSpittles ()方法接受两个参数。
- 其中max参数代表所返回的 Spittle中,Spittle ID属性的最大值,
- 而count参数表明要返回 多少个Spittle对象。
为了获得最新的20个Spittle对象,我们可 以这样调用findSpittles ():
List<Spittle> findSpittleRepository(long max,int count);
现在,我们让Spittle类尽可能的简单,如下面的程序清单5.8所 示。它的属性包括消息内容、时间戳以及Spittle发布时对应的经纬 度。
程序清单5.8 Spittle类:包含消息内容、时间戳和位置信息
[图片]
- Spittle是一个基本的POJO数据对象,没有复杂的内容。
- 在Spittle类中,我们使用了Apache Common Lang包来实现equals()和hashCode()方法。
- 这些方法除了常规的作用外,当我们为控制器的处理器方法编写测试时,它们也是有用的。
既然我们说到了测试,那么我们继续讨论这个话题并为新的控制器方 法编写测试。如下的程序清单使用Spring的MockMvc来断言新的处理 器方法中你所期望的行为。
程序清单5.9 测试SpittleController处理针对“/spittles”的GET请求
[图片]
- 首先,测试会创建一个SpittleRepository接口的mock实现。
- 这个mock实现会在其findSpittles()方法中返回20个Spittle对象。
- 接下来,将这个mock实现注入到一个新的SpittleController实例中。
- 然后,创建一个MockMvc并使用这个控制器进行测试。
根据您提供的内容,我将尝试梳理一下:
- 首先,与HomeController不同,这个测试在MockMvc构造器上调用了setSingleView()方法。
- 优点
- 这样可以避免mock框架解析控制器中的视图名。在很多场景中,这样做并不是必需的。
- 缺点
- 但是对于这个控制器方法来说,视图名与请求路径非常相似。按照默认的视图解析规则,MockMvc会发生失败,因为无法区分视图路径和控制器的路径。
- 在这个测试中,设置InternalResourceView的实际路径与InternalResourceViewResolver的配置一致是无关紧要的。
- 这个测试对"/spittles"发起GET请求,并断言视图的名称为"spittles",并且模型中包含名为"spittleList"的属性,该属性包含预期的内容。
- 当然,如果此时运行测试,它将会失败。它不是在运行时失败,而是在编译时就会失败。这是因为我们还没有编写SpittleController。现在,我们需要创建SpittleController。
总结概要,我梳理了以下内容:与HomeController不同,这个测试在MockMvc构造器上调用了setSingleView()方法,避免了视图名的解析问题。测试对"/spittles"发起GET请求,并断言视图的名称为"spittles",并且模型中包含名为"spittleList"的属性,该属性包含预期的内容。然而,由于还没有编写SpittleController,测试将在编译时失败。请注意,以上内容是用中文输出的。如果您有任何其他问题或需要进一步的解释,请随时告诉我。
让它满足程序清单5.9的预期。如下的SpittleController实现将 会满足以上测试的要求。
程序清单5.10 SpittleController:在模型中放入最新的spittle 列表
[图片]
- 首先,SpittleController类有一个构造器,使用@Autowired注解来注入SpittleRepository。这个SpittleRepository随后在spittles()方法中使用,用于获取最新的spittle列表。
- 在spittles()方法中,我们给定了一个Model作为参数。这样,spittles()方法可以将Repository中获取到的Spittle列表填充到模型中。Model实际上是一个Map(键值对的集合),它会传递给视图,从而使数据能够渲染到客户端。
- 当调用addAttribute()方法并且不指定key时,key会根据值的对象类型进行推断。在这个例子中,因为值是一个List<Spittle>,所以键会被推断为spittleList。
- spittles()方法的最后一步是返回spittles作为视图的名称,这个视图将用于渲染模型中的数据。
如果你希望显式声明模型的key的话,那也尽可以进行指定。例如, 下面这个版本的spittles ()方法与程序清单5. 10中的方法作用是一样的:
[图片]
如果你希望使用非Spring类型的话,那么可以用java.util.Map来 代替Model。下面这个版本的spittles ()方法与之前的版本在功能 上是一样的:
[图片]
既然我们现在提到了各种可替代的方案,那下面还有另外一种方式来 编写spittles ()方法:
[图片] - 首先,这个版本与其他版本有一些差别。它没有返回视图名称,也没有显式地设置模型,而是直接返回了Spittle列表。
- 当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据其类型推断得出(在本例中,是spittleList)。
- 逻辑视图的名称将根据请求路径推断得出。因为这个方法处理针对“/spittles”的GET请求,所以视图的名称将是spittles(去掉开头的斜线)。
- 无论选择哪种方式编写spittles()方法,结果都是相同的。模型中会存储一个Spittle列表,key为spittleList,然后将这个列表发送到名为spittles的视图中。
- 根据配置的InternalResourceViewResolver,视图的JSP文件将是“/WEB-INF/views/spittles.jsp”。
- 现在,数据已经放到模型中,那么在JSP中如何访问它呢?实际上,当视图是JSP时,模型数据会作为请求属性放到请求(request)中。因此,在spittles.jsp文件中可以使用JSTL(JavaServer Pages Standard Tag Library)的<c:forEach>标签来渲染spittle列表。
[图片]
图5.3为显示效果,能够让你对它在Web浏览器中是什么样子有个可视 化的印象。
尽管SpittleController很简单,但是它依然比 HomeController更进一步了。不过,SpittleController和 HomeController都没有处理任何形式的输入。现在,让我们扩 展SpittleController,让它从客户端接受一些输入。
图5 .3 控制器中的Spittle模型数据将会作为请求参数,并在Web页面上渲染为列 表的形式
本文由博客一文多发平台 OpenWrite 发布!