欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

1 SpringMVC详解(第一部分)

程序员文章站 2024-02-15 13:22:58
...

Spring MVC基于模型-视图-控制器(Model-View-Controller,MVC)模式实现,它能够帮你构建像Spring框架那样灵活和松耦合的Web应用程序。

1 跟踪Spring MVC请求

1 SpringMVC详解(第一部分)

  1. 在请求离开浏览器时,会带有用户所请求内容的信息,至少会包含请求的URL。请求旅程的第一站是DispatcherServlet。DispatcherServlet就是前端控制器。
  2. DispatcherServlet以会查询一个或多个处理器映射(handler mapping) 来确定请求的下一站在哪里。
  3. 一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器 。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。
  4. 控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化。所以,信息需要发送给一个视图(view),通常会是JSP。控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet。传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。
  5. DispatcherServlet将会使用视图解析器(view resolver) 来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是JSP。
  6. 既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP)。
  7. 在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。

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()方法与之前的版本在功能上是一样的:

1 SpringMVC详解(第一部分)

 现在,数据已经放到了模型中,在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 查询参数

1 SpringMVC详解(第一部分)

这个测试方法与程序清单5.9中的测试方法关键区别在于它针对“/spittles”发送GET请求,同时还传入了max和count参数。

@RequestParam注解表示参数接收传递过来的值,并且defaultValue属性可以给参数默认值:

1 SpringMVC详解(第一部分)

4.2 路径参数

1 SpringMVC详解(第一部分)

这个测试中最重要的部分是最后几行,它对“spittles12345”发起GET请求,然后断言视图的名称是spittle,并且预期的Spittle对象放到了模型之中。

1 SpringMVC详解(第一部分)

在我们编写的控制器中,所有的方法都映射到了(通过@RequestMapping)   静态定义好的路径上。但是,如果想让这个测试通过的话,我们编写的@RequestMapping  要包含变措部分,这部分代表了Spittle ID。

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

4.3 表单参数

4.3.1 对象接收表单数据

1 SpringMVC详解(第一部分)

模拟表单的提交

1 SpringMVC详解(第一部分)

Spitter对象作为参数。这个对象有firstName、lastName、username和password属性,这些属性将会使用请求中同名   的参数进行填充。

视图格式中的“redirect:”前缀时,它就知道要将其解析为重定向的规则,而不是视图的名称。在本例中,它将会重定向到用户基本信息的页面。例如,如果Spitter.username属性的值为“jbauer”,那么视图将会重定向到“spitter/jbauer”。

4.3.2 表单数据校验

在接收对象上添加注解,可以校验表单参数的正确性。更多的校验注解,可以查看相关资料。

1 SpringMVC详解(第一部分)

Spitter的所有属性都添加了@NotNull注解,以确保它们的值不为null。类似 地,属性上也添加了@Size注解以限制它们的长度在最大值和最小值之间。对Spittr应用   来说,这意味着用户必须要填完注册表单,并且值的长度要在给定的范围内。

1 SpringMVC详解(第一部分)

(1)@Valid注解所标注的就是要检验的参数。

(2)如果有校验出现错误的话,那么这些错误可以通过Errors对象进行访问。

(3)processRegistration()方法所做的第一件事就是调用Errors.hasErrors() 来检查是否有错误。如果有错误的话,Errors.hasErrors()将会返回到registerForm,也就是注册表单的视图。 

 

 

 

 

相关标签: Spring实战