1 SpringMVC详解(第一部分)
Spring MVC基于模型-视图-控制器(Model-View-Controller,MVC)模式实现,它能够帮你构建像Spring框架那样灵活和松耦合的Web应用程序。
1 跟踪Spring MVC请求
- 在请求离开浏览器时,会带有用户所请求内容的信息,至少会包含请求的URL。请求旅程的第一站是DispatcherServlet。DispatcherServlet就是前端控制器。
- DispatcherServlet以会查询一个或多个处理器映射(handler mapping) 来确定请求的下一站在哪里。
- 一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器 。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。
- 控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化。所以,信息需要发送给一个视图(view),通常会是JSP。控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet。传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。
- DispatcherServlet将会使用视图解析器(view resolver) 来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是JSP。
- 既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP)。
- 在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。
2 通过注解搭建SpringMVC
2.1 配置DispatcherServlet
我们会使用Java将DispatcherServlet配置在Servlet容器中,而不会再使用web.xml文件。如下的程序清单展示了所需的Java类。
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
要理解程序清单5.1是如何工作的,我们可能只需要知道扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置DispatcherServlet和Spring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中。当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。
配置DispatcherServlet包括三个方法:
getServletMappings():对哪些请求进行拦截。在本例中,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求。
getServletConfigClasses():”当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。在程序清单5.1的getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。如控制器、视图解析器以及处理器映射。
getRootConfigClasses():我们希望DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数据层组件。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean。
在本例中,根配置定义在RootConfig中,DispatcherServlet的配置声明 在WebConfig中。稍后我们将会看到这两个类的内容。
2.2 启用SpringMVC
我们需要配置控制器,处理器映射器,视图解析器
@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
(1)一个带有@EnableWebMvc注解的类,能够启用Spring MVC。
(2)没有启用组件扫描。这样的结果就是,Spring只能找到显式声明在配置类中的控制器。因为getServletClasses中配置的是WebConfig.
(3)viewResolver()是视图解析器,它会查找JSP文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀
(4)configureDefaultServletHandling()会把DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求。
2.3 配置控制器
@Controller
@RequestMapping("/")
public class HomeController {
@RequestMapping(method = GET)
public String home() {
return "home";
}
}
(1)@Controller注解。很显然这个注 解是用来声明控制器的。
(2)HomeController唯一的一个方法,也就是home()方法,带有@RequestMapping注解。它的value属性指定了这个方法所要处理的请求路径
(3)home()方法其实并没有做太多的事情:它返回了一个String类型的“home”。这个String将会被Spring MVC解读为要渲染的视图名称。
2.4 测试控制器
public class HomeControllerTest {
@Test
public void testHomePage() throws Exception {
HomeController controller = new HomeController();
// assertEquals("home", controller.home());
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/"))
.andExpect(view().name("home"));
}
}
从Spring 3.2开始,我们可以按照控制器的方式来测试Spring MVC中的控制器了,而不仅仅是作为POJO进行测试。Spring现在包含了一种mock Spring MVC并针对控制器执行HTTP请求的机制。这样的话,在测试控制器的时候,就没有必要再启动Web服务器和Web浏览器了。
3 传递模型到视图中
本节使用两个单元测试框架Mockito和mockMVC,下面简要说明一下这两个框架
3.1 Mockito
Mockito是一个模拟框架,基于JAVA的库,用于JAVA应用程序的有效单元测试. Mockito用于模拟接口,以便可以将虚拟功能添加到可用于单元测试的模拟接口。通俗的讲,创建一个替身,替身可以做主角(真实的对象)的动作。
Stubbing大概就是占坑的代码,桩代码给出的实现是临时性的/待编辑的。它使得程序在结构上能够符合标准,又能够使程序员可以暂时不编辑这段代码。通俗的讲,替身做假动作,并验证结果。
3.3.1 创建替身
三种方式
/***
* How to mock class by Mockito
* <ul>
* <li>use runner</li>
* <li>use annotation</li>
* <li>use rule</li>
* </ul>
*/
3.3.2 替身做动作(Stubbing)
- 对方法设定返回值
when(i.next()).thenReturn("Hello")
- 对方法设定返回异常
when(i.next()).thenThrow(new RuntimeException())
- Mockito支持迭代风格的返回值设定
第一种方式
when(i.next()).thenReturn("Hello").thenReturn("World")
第二种方式
when(i.next()).thenReturn("Hello", "World")
上面的设定相当于:
when(i.next()).thenReturn("Hello")
when(i.next()).thenReturn("World")
第一次调用i.next()将返回”Hello”,第二次的调用会返回”World”。
- Stubbing的另一种语法
doReturn(Object) 设置返回值
doReturn("Hello").when(i).next();
迭代风格
doReturn("Hello").doReturn("World").when(i).next();
返回值的次序为从左至右,第一次调用返回”Hello”,第二次返回”World”。
doThrow(Throwable) 设置返回异常
doThrow(new RuntimeException()).when(i).next();
因为这种语法的可读性不如前者,所以能使用前者的情况下尽量使用前者,当然在后面要介绍的Spy除外。
- 对void方法进行方法预期设定
void方法的模拟不支持when(mock.someMethod()).thenReturn(value)这样的语法,只支持下面的方式:
doNothing() 模拟不做任何返回(mock对象void方法的默认返回)
doNothing().when(i).remove();
doThrow(Throwable) 模拟返回异常
doThrow(new RuntimeException()).when(i).remove();
3.2 mockMVC
3.2.1 创建mockMVC对象
我们创建的HelloController就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中,如:
private MockMvc mvc = MockMvcBuilders.standaloneSetUp(new HelloController()).build();
3.2.2 MockMvc方法解析
perform:执行一个RequestBuilder请求,会自动执行SpringMvc的流程并映射到相应的控制器执行处理
get:声明发送一个get请求方法。MockHttpServletRequestBuilder get(Sring urlTemple, Object… urlVariables):根据url模板和url变量值得到一个GET请求方式的。另外提供了其他的请求方法,如:POST、PUT、DELETE等
param:添加request的参数,如上面发送请求的时候戴上了pcode=root的参数。假如使用需要发送json数据格式的时候将不能使用这种方式。
andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断)
andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断)
andReturn:最后返回相应的MvcResult:然后进行自定义验证/进行下一步异常处理(对返回的数据进行的判断)
如:
@Test
public void getHello() throws Exception{
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello World")));
}
3.3 将数据添加到模型中
Model实际上就是一个Map(也就是key-value对的集合),它会传递给视图,这样数据就能渲染到客户端了。当调用addAttribute()方法并且不指定key的时候,那么key会根据值的对象类型推断确定。在本例中,因为它是一个List<Spittle>,因此,键将会推断 为spittleList。
@RequestMapping(method = RequestMethod.GET)
public String spittle(Model model) {
model.addAttribute(spittleRepository.findSpittles(
Long.MAX_VALUE,20));
return "spittles";
}
如果你希望使用非Spring类型的话,那么可以用java.util.Map来代替Model。下面这个版本的spittles()方法与之前的版本在功能上是一样的:
现在,数据已经放到了模型中,在JSP中该如何访问它呢?实际上,当视图是JSP的时候,模型数据会作为请求属性放到请求(request)之中。因此,在spittles.jsp文件中可以使用JSTL(JavaServer Pages Standard Tag Library)的<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}" /></span>
<span class="spittleLocation">(<c:out value="${spittle.latitude}" />, <c:out value="${spittle.longitude}" />)</span>
</div>
</li>
</c:forEach>
4 接受请求的输入
Spring MVC允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:
查询参数(Query Parameter)。
路径参数(Path Variable)。
表单参数(Form Parameter)。
4.1 查询参数
这个测试方法与程序清单5.9中的测试方法关键区别在于它针对“/spittles”发送GET请求,同时还传入了max和count参数。
@RequestParam注解表示参数接收传递过来的值,并且defaultValue属性可以给参数默认值:
4.2 路径参数
这个测试中最重要的部分是最后几行,它对“spittles12345”发起GET请求,然后断言视图的名称是spittle,并且预期的Spittle对象放到了模型之中。
在我们编写的控制器中,所有的方法都映射到了(通过@RequestMapping) 静态定义好的路径上。但是,如果想让这个测试通过的话,我们编写的@RequestMapping 要包含变措部分,这部分代表了Spittle ID。
为了实现这种路径变措,Spring MVC允许我们在@RequestMapping路径中添加占位符。占位符的名称要用大括号(“{”和“}”)括起来。路径中的其他部分要与所处理的请求完全匹 配,但是占位符部分可以是任意的值。
4.3 表单参数
4.3.1 对象接收表单数据
模拟表单的提交
Spitter对象作为参数。这个对象有firstName、lastName、username和password属性,这些属性将会使用请求中同名 的参数进行填充。
视图格式中的“redirect:”前缀时,它就知道要将其解析为重定向的规则,而不是视图的名称。在本例中,它将会重定向到用户基本信息的页面。例如,如果Spitter.username属性的值为“jbauer”,那么视图将会重定向到“spitter/jbauer”。
4.3.2 表单数据校验
在接收对象上添加注解,可以校验表单参数的正确性。更多的校验注解,可以查看相关资料。
Spitter的所有属性都添加了@NotNull注解,以确保它们的值不为null。类似 地,属性上也添加了@Size注解以限制它们的长度在最大值和最小值之间。对Spittr应用 来说,这意味着用户必须要填完注册表单,并且值的长度要在给定的范围内。
(1)@Valid注解所标注的就是要检验的参数。
(2)如果有校验出现错误的话,那么这些错误可以通过Errors对象进行访问。
(3)processRegistration()方法所做的第一件事就是调用Errors.hasErrors() 来检查是否有错误。如果有错误的话,Errors.hasErrors()将会返回到registerForm,也就是注册表单的视图。
推荐阅读
-
1 SpringMVC详解(第一部分)
-
第一部分day1-变量、运算
-
《机器学习实战笔记--第一部分 分类算法:logistic回归1》
-
《机器学习实战笔记--第一部分 分类算法:朴素贝叶斯分类1》
-
第四章 Controller接口控制器详解(1)——跟着开涛学SpringMVC
-
SpringMVC强大的数据绑定(1)——第六章 注解式控制器详解——跟着开涛学SpringMVC
-
第四章 Controller接口控制器详解(1)——跟着开涛学SpringMVC
-
SpringMVC强大的数据绑定(1)——第六章 注解式控制器详解——跟着开涛学SpringMVC
-
第四章 Controller接口控制器详解(1)——跟着开涛学SpringMVC
-
SpringMVC架构流程详解,手把手教学:SpringMVC项目集成Spring,Mybatis,第一个SSM项目