Spring MVC教程

SpringMVC的开发包叫spring-webmvc.jar

>>如何用spring mvc实现rest服务

和其它Web框架一样,SpringMVC也是基于MVC的设计理念。此外,它采用了松散耦合、可插拔的组件结构,比其他的MVC框架更具扩展性和灵活性。SpringMVC通过一套MVC注解,让POJO成为处理请求的控制器,无须实现任何接口。而且,SpringMVC还支持REST风格的URL请求:注解驱动和REST风格的SpringMVC是Spring的出色功能之一,有必要掌握。此外,SpringMVC在数据绑定、视图解析、本地化处理及静态资源处理上都有许多不俗的表现。

本章主要内容:

  • SpringMVC体系概述
  • 注解驱动的控制器
  • 数据绑定、输入/输出格式化及数据校验
  • 视图解析
  • 本地化解析
  • 文件上传
  • WebSocket支持
  • 静态资源处理、请求拦截器、异常处理

本章亮点:

深入分析并图解SpringMVC体系结构

对处理方法入参绑定及视图解析进行详细分析

17.1 SpringMVC体系概述

SpringMVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet是SpringMVC的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。SpringMVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理及表单标签绑定等内容。

(1)整个过程始于客户端发出一个HTTP请求,Web应用服务器接收到这个请求。如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),则Web服务器将该请求转交给DispatcherServlet处理。

(2)DispatcherServlet接收到这个请求后,将根据请求的信息(包括URL、HTTP方法、请求报文头、请求参数、Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。可将HandlerMapping看作路由控制器,将Handler看作目标主机。值得注意的是,在SpringMVC中并没有定义一个Handler接口,实际上,任何一个Object都可以成为请求的处理器。

(3)当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。HandlerAdapter是SpringMVC的框架级接口,顾名思义,HandlerAdapter是一个适配器,它用统一的接口对各种Handler方法进行调用。

(4)处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息。

(5)ModelAndView中包含的是“逻辑视图名”而非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。

(6)当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。

(7)最终客户端得到的响应消息可能是一个普通的HTML页面,也可能是一个XML或JSON串,甚至是一张图片或一个PDF文档等不同的媒体形式。

以上每个步骤都包含丰富的知识点,本章将逐步揭示每个组件的“庐山真面目”。不过现在我们第一步要做的是在web.xml中配置好DispatcherServlet,让SpringMVC的心脏跳动起来。

DispatcherServlet是SpringMVC的灵魂和心脏,它负责接收HTTP请求并协调SpringMVC的各个组件完成请求处理的工作。和任何Servlet一样,用户必须在web.xml中配置好DispatcherServlet。

要了解SpringMVC框架的工作原理,我们首先需要回答3个问题:

1. DispatcherServlet框架如何截获特定的HTTP请求并交由SpringMVC框架处理?

2. 位于Web层的Spring容器(WebApplicationContext)如何与位于业务层的Spring容器(ApplicationContext)建立关联,以使Web层的Bean可以调用业务层的Bean?

3. 如何初始化SpringMVC的各个组件,并将它们装配到DispatcherServlet中?

对于第一个问题,我们可以在web.xml中配置一个Servlet,并通过<servlet-mapping>指定其处理的URL。这是传统的DispatcherServlet配置方式。而对于Servlet3.0,可以采用编程式的配置方式。

代码清单17-1:web.xml:配置DispatcherServlet

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:/applicationContext.xml</param-value>
</context-param>
<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

<servlet>
  <servlet-name>smart</servlet-name>
  <servlet-class>
  org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>smart</servlet-name>
  <url-pattern>*.html</url-pattern>
</servlet-mapping>

通过contextConfigLocation参数指定业务层Spring容器的配置文件。

需要提醒的是,一个web.xml可以配置多个DispatcherServlet,通过其<servlet-mapping>配置,让每个DispatcherServlet处理不同的请求。

如果确实要对DispatcherServlet的默认规则进行调整,则DispatcherServlet是“敞开怀抱”的。下面是常用的一些配置参数,可通过<servlet>的<init-param>指定。

实例:

1. 编写处理请求的控制器

SpringMVC通过@Controller注解即可将一个POJO转化为处理请求的控制器,通过@RequestMapping为控制器指定处理哪些URL的请求。UserController是一个负责用户处理的控制器,其代码如代码清单17-2所示。

代码清单17-2:UserController.java

package com.smart.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController{
  
  @RequestMapping("/register")
  public String register(){//返回一个String类型的逻辑视图名
    return "user/register";
  }
}

首先使用@Controller对UserController类进行标注,使其成为一个可处理HTTP请求的控制器。然后使用@RequestMapping对UserController及其register()方法进行标注,确定register()对应的请求URL。

在UserController类定义处标注的@RequestMapping限定了UserController类处理所有URI为/user的请求,它相对于Web容器部署根路径。UserController类可以定义多个处理方法,处理来自/user URI的请求。假设Web容器的部署根路径为/chapter17,则代码清单17-2中的register()方法将处理所有来自/chapter17/user/register.html的请求。

register()方法返回一个字符串user/register,它代表一个逻辑视图名,将由视图解析器解析为一个具体的视图对象。在本例中,它将被映射为/WEB-INF/views/user/register.jsp。[可是我不想返回JSP页面,只想返回JSON数据]

2. 编写视图对象

我们使用一个register.jsp作为用户的注册页面,UserController#register()方法处理完成后,将转向这个register.jsp页面,如代码清单17-3所示。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>新增用户</title>
</head>
<body>
  <form method="post" action="<c:url value="/user.html"/>">
    <table>
        <tr>
           <td>用户名:</td>
           <td><input type="text" name="userName"  value="${user.userName}"/></td>
        </tr>
        <tr>
         <td>密码:</td>
           <td><input type="password" name="password" value="${user.password}"/></td>
        </tr>
        <tr>
         <td>姓名:</td>
           <td><input type="text" name="realName" value="${user.realName}"/></td>
        </tr>
        <tr>
         <td>生日:</td>
           <td><input type="text" name="realName" value="${user.birthday}"/></td>
        </tr>
                                <tr>
         <td>工资:</td>
           <td><input type="text" name="realName" value="${user.salary}"/></td>
        </tr>
        <tr>
         <td colspan="2"><input type="submit" name="提交"/></td>
        </tr>        
    </table>
  </form>
</body>
</html>

register.jsp很简单,它包括了一个表单,单击“提交”按钮后,表单提交到/user.html进行处理。UserController添加了一个createUser()方法用于处理表单提交的请求,如代码清单17-4所示。

    @RequestMapping(method = RequestMethod.POST)
    public ModelAndView createUser(User user) {
        userService.createUser(user);
        ModelAndView mav = new ModelAndView();
        mav.setViewName("user/createSuccess");
        mav.addObject("user", user);
        return mav;
    }

createUser()方法处的@RequestMapping注解让createUser()处理URI为/user.html且请求方法为POST的请求。SpringMVC自动将表单中的数据按参数名和User属性名匹配的方式进行绑定,将参数值填充到User的相应属性中。调用业务层的UserService进行业务处理,进而返回ModelAndView对象,逻辑视图名为user/createSuccess,user作为模型数据暴露给视图对象。

3. 配置SpringMVC的配置文件

要使以上实例正常工作,需要在SpringMVC配置文件中进行简单的配置,如代码清单17-7所示。

代码清单17-7:smart-servlet.xml

    <context:component-scan base-package="com.smart.web"/>

    <!-- 在使用Excel/PDF/XML的视图时,请先把这个视图解析器注释掉,否则产生视图解析问题-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:order="100" p:viewClass="org.springframework.web.servlet.view.JstlView"
          p:prefix="/WEB-INF/views/" p:suffix=".jsp"/>

再简单描述一下SpringMVC处理/user.html的整个过程。

(1)DispatcherServlet接收到客户端的/user.html请求。

(2)DispatcherServlet使用DefaultAnnotationHandlerMapping查找负责处理该请求的处理器。

(3)DispatcherServlet将请求分发给名为/user.html的UserController处理器。

(4)处理器完成业务处理后,返回ModelAndView对象,其中View的逻辑名为/user/createSuccess,而模型包含一个键为user的User对象。

(5)DispatcherServlet调用InternalResourceViewResolver组件对ModelAndView中的逻辑视图名进行解析,得到真实的/WEB-INF/view/user/createSuccess.jsp视图对象。

(6)DispatcherServlet使用/WEB-INF/view/user/createSuccess.jsp对模型中的user模型对象进行渲染。

(7)返回响应页面给客户端。

 发现是标签写错了

通过这个例子,我们了解了开发一个SpringMVC应用所需经历的大体过程。这个例子太过简单,后续章节中,我们将对以上各个步骤进行深入分析。

17.2 注解驱动的控制器

 使用POJO类定义处标注@Controller,再通过<context:component-scan/>扫描相应的类包,即可使POJO成为一个能处理HTTP请求的控制器。

用户可以创建数量不限的控制器,分别处理不同的业务请求,如LogonController、UserController、ForumController等。每个控制器可拥有多个处理请求的方法,每个方法负责不同的请求操作。如何将请求映射到对应的控制器方法中是SpringMVC框架的重要任务之一,这项任务由@RequestMapping承担。

在控制器的类定义及方法定义处都可以标注@RequestMapping,类定义处的@RequestMapping提供初步的请求映射信息,方法定义处的@RequestMapping提供进一步的细分映射信息。DispatcherServlet截获信息后,就通过控制器上@RequestMapping提供的映射信息确定请求所对应的处理方法。

将请求映射到控制器处理方法的工作包含一系列映射规则,这些规则是根据请求中的各种信息制定的,具体包括请求URL、请求参数、请求方法、请求头这4个方面的信息项。

1. 通过请求URL进行映射

@RequestMapping使用value值指定请求的URL,如@RequestMapping("/user")、@RequestMapping("/register")等。需要注意的是,@RequestMapping在类定义处指定的URL相对于Web应用的部署路径,而在方法定义处指定的URL则相对于类定义处指定的URL。如果在类定义处未标注@RequestMapping,则仅在处理方法处标注@RequestMapping,此时,方法处指定的URL则相对于Web应用的部署路径,如代码清单17-8所示。

代码清单17-8:UserController.java:在类定义处不使用@RequestMapping

package com.smart.web;
import org.springframework.web.bind.annotation.RequestMapping;
...
@Controller
public class UserController{
  @RequestMapping(path="/user/createUser")
  public String createUser(@ModelAttribute("user") User user){
    ...
    return "user/createSuccess";
  }
  @RequestMapping("/user/register")
  public String register(@ModelAttribute("user")User user){
    return "user/register";
  }
}

这样,/user/register.html请求将由register()方法处理,而/user/createUser.html请求将由createUser()方法处理。注意,它们都相对于Web应用的部署路径。

同一控制器的多个处理方法负责处理相同业务模块的不同操作,但凡设计合理的Web应用都会将这些操作请求安排在某一相同的URL之下。所以除非特别的原因,建议不要舍弃类定义处的@RequestMapping。

@RequestMapping不但支持标准的URL,还支持Ant风格和带占位符的URL。

通过@PathVariable可以将URL中的占位符参数绑定到控制器处理方法的入参中,如代码清单17-9所示。

package com.smart.web;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/user")
public class UserController{
  @RequestMapping("/{userId}")
  public ModelAndView showDetail(@PathVariable("userId") String userId){
    ModelAndView mav = new ModelAndView();
    mav.setViewName("user/showDetail");
    mav.addObject("user", userService.getUserById(userId));
    return mav;
  }
}

如果类定位处的URL使用占位符,也可以绑定到处理方法的入参中。

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController{
  @RequestMapping("/pets/{petId}")
  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model){
    ...
  }
}

2. 通过请求参数、请求方法或请求头进行映射

HTTP请求报文除URL外,还拥有其他众多的信息。以下是一个标准的HTTP请求报文,如图17-4所示。

图17-4 HTTP请求报文

代码清单17-11:UserController.java:使用其他信息映射请求

@Controller
@RequestMapping("/user")
public class UserController{

  @RequestMapping(path="/delete", method=RequestMethod.POST, param="userId")
  public String test1(@RequestParam("userId") String userId){
    //do sth
    return "user/test1";
  }
  
  @RequestMapping(path="/show", headers="content-type=text/*")
  public String test2(@RequestParam("userId") String userId){
    //do sth
    return "user/test2";
  }
}

@RequestMapping的value、method、params及headers是与的关系。

SpringMVC对控制器方法签名的限制是很宽松的,用户几乎可以按自己喜欢的方式进行方法签名。在必要时对方法及方法入参标注相应的注解即可,SpringMVC会优雅地完成剩下的工作:将HTTP请求的信息绑定到相应的方法入参中,并根据方法返回值类型做出相应的后续处理。一般情况下,处理方法的返回值类型为ModelAndView或String,前者包含模型和视图,而后者仅代表一个逻辑视图名。下面来看几个典型的方法签名,如代码清单17-12所示。

// ①请求参数按名称匹配的方式绑定到方法入参中,方法返回对应的字符串代表逻辑视图名
    @RequestMapping(value = "/handle1")
    public String handle1(@RequestParam("userName") String userName,
            @RequestParam("password") String password,
            @RequestParam("realName") String realName) {
        return "success";
    }

    // ②将Cooke值及报文头属性绑定到入参中、方法返回ModelAndView
    @RequestMapping(value = "/handle2")
    public ModelAndView handle2(@CookieValue("JSESSIONID") String sessionId,
            @RequestHeader("Accept-Language") String accpetLanguage) {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("success");
        mav.addObject("user", new User());
        return mav;
    }

    // ③请求参数按名称匹配的方式绑定到user的属性中、方法返回对应的字符串代表逻辑视图名
    @RequestMapping(value = "/handle3")
    public String handle3(User user) {
        return "success";
    }

    // ④直接将HTTP请求对象传递给处理方法、方法返回对应的字符串代表逻辑视图名
    @RequestMapping(value = "/handle4")
    public String handle4(HttpServletRequest request) {
        return "success";
    }

RFC3986定义了在URI中包含name-value的规范。随之在SpringMVC3.2中出现了@MatrixVariable注解,该注解的出现使得开发人员能够将请求中的矩阵变量(MatrixVariable)绑定到处理器的方法参数中。

在MatrixVariable中,多个变量可以使用“;”分号进行分隔。如果一个变量对应多个值,那么可以使用“,”逗号进行分隔。或者使用重复的变量名。

对请求处理方法签名做详细说明:

1. 使用@RequestParam绑定请求参数值

2. 使用@CookieValue绑定请求中的Cookie值

3. 使用@RequestHeader绑定请求报文头的属性值

4. 使用命令/表单对象绑定请求参数值

5. 使用Servlet API对象作为入参 

    @RequestMapping(value = "/handle21")
    public void handle21(HttpServletRequest request,
            HttpServletResponse response) {
        String userName = request.getParameter("userName");
        response.addCookie(new Cookie("userName", userName));
    }

在使用Servlet API的类作为入参时,SpringMVC会自动将Web层对应的Servlet对象传递给处理方法的入参。处理方法入参可以同时使用Servlet API类的入参和其他符合要求的入参,它们之间的位置顺序没有特殊要求。

6. 使用I/O对象作为入参

Servlet的ServletRequest拥有getInputStream()和getReader()方法, 可以通过它们读取请求的信息。相应的,Servlet的ServletResponse拥有getOutputStream()和getWriter()方法,可以通过它们输出响应信息。

SpringMVC允许控制器的处理方法使用java.io.InputStream/java.io.Reader及java.io.OutputStream/java.io.Writer作为方法的入参,SpringMVC将获取InputStream/Reader或OutputStream/Writer,然后传递给控制器的处理方法,如代码清单17-14所示。

    @RequestMapping(value = "/handle31")
    public void handle31(OutputStream os) throws IOException {
        Resource res = new ClassPathResource("/image.jpg");
        FileCopyUtils.copy(res.getInputStream(), os);
    }

7. 其他类型的参数

关于HttpMessageConverter<T>:

HttpMessageConverter<T>是Spring的一个重要接口,它负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息。

DispatcherServlet默认已经安装了RequestMappingHandlerAdapter作为HandlerAdapter的组件实现类,HttpMessageConverter即由RequestMappingHandlerAdapter使用,将请求信息转换为对象,或将对象转换为响应信息。

HttpMessageConverter<T>接口定义了以下几个方法:

  • Boolean canRead(Class<?> clazz, MediaType mediaType):指定转换器可以读取的对象类型,即转换器可将请求信息转换为clazz类型的对象;同时指定支持的MIME媒体类型,MIME媒体类型可在RFC2616中定义。
  • Boolean canWrite(Class<?> clazz, MideaType mediaType):指定转换器可以将clazz类型的对象写到响应流中,响应流支持的媒体类型在mediaType中定义。
  • List<MediaType> getSupportedMediaTypes():该转换器支持的媒体类型。
  • T read(Class<? extends T> clazz, HttpInputMessage inputMessage):将请求信息流转换为T类型的对象。
  • void write(T t, MediaType contentType,HttpOutputMessage outputMessage):将T类型的对象写到响应流中,同时指定响应的媒体类型为contentType。

1. HttpMessageConverter<T>的实现类

Spring为HttpMessageConverter<T>提供了众多的实现类,它们组成了一个功能强大、用途广泛的HttpMessageConverter<T>家族,具体说明如表17-2所示。

RequestMappingHandlerAdapter默认已经装配了如下HttpMessageConverter(StringHttpMessageConverter、ByteArrayHttpMessageConverter、SourceHttpMessageConverter和AllEncompassingFormHttpMessageConverter),如果你需要装配其他类型的HttpMessageConverter,则可以在Spring的Web容器上下文中自行定义一个RequestMappingHandlerAdapter,如代码清单17-15所示。

代码清单17-15:smart-servlet.xml

    <bean
            class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
            p:messageConverters-ref="messageConverters">
        <property name="webBindingInitializer">
            <bean class="com.smart.web.MyBindingInitializer"/>
        </property>
    </bean>


    <util:list id="messageConverters">
        <bean
                class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
        <bean
                class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter" />

        <bean
                class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
        <bean
                class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
                p:marshaller-ref="xmlMarshaller" p:unmarshaller-ref="xmlMarshaller">
        </bean>
        <bean
                class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    </util:list>

2. 使用HttpMessageConverter<T>

如何使用HttpMessageConverter<T>将请求信息转换并绑定到处理方法的入参中呢?SpringMVC提供了两种途径。

  • 使用@RequestBody/@ResponseBody对处理方法进行标注。
  • 使用HttpEntity<T>/ResponseEntity<T>作为处理方法的入参或返回值。

下面分别通过实例进行说明。首先来看使用@RequestBody/@ResponseBody的例子,如代码清单17-16所示。

代码清单17-16:UserController.java:使用@RequestBody/@ResponseBody

    @RequestMapping(value = "/handle41")
    public String handle41(@RequestBody String body) {
        System.out.println(body);
        return "success";
    }

    @ResponseBody
    @RequestMapping(value = "/handle42/{imageId}")
    public byte[] handle42(@PathVariable("imageId") String imageId)
            throws IOException {
        System.out.println("load image of " + imageId);
        Resource res = new ClassPathResource("/image.jpg");
        byte[] fileData = FileCopyUtils.copyToByteArray(res.getInputStream());
        return fileData;
    }

在代码清单17-15中,已经为RequestMappingHandlerAdapter注册了若干个HttpMessageConverter。handle41()方法的RequestBody入参标注了一个@RequestBody注解,SpringMVC将根据RequestBody的类型查找匹配的HttpMessageConverter。由于StringHttpMessageConverter的泛型类型对应String,所以StringHttpMessageConverter将被SpringMVC选中,用它将请求体信息进行转换并将结果绑定到requestBody入参上。

handle42()方法拥有一个@ResponseBody注解,由于方法返回值类型为byte[],所以SpringMVC根据类型匹配的查找规则将使用ByteArrayHttpMessageConverter对返回值进行处理,即将图片数据流输出到客户端。

下面编写一个测试用例,通过RestTemplate对handle41()及handle42()这两个方法进行测试,如代码清单17-17所示。

   @Test
    public void testhandle41() {
        RestTemplate restTemplate = new RestTemplate();
        MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
        form.add("userName", "tom");
        form.add("password", "123456");
        form.add("age", "45");
        restTemplate.postForLocation(
                "http://localhost:8080/chapter17/user/handle41.html", form);
    }
    @Test
    public void testhandle42() throws IOException {
        RestTemplate restTemplate = new RestTemplate();
        byte[] response = restTemplate.postForObject(
                "http://localhost:8080/chapter17/user/handle42/{itemId}.html", null, byte[].class, "1233");
        Resource outFile = new FileSystemResource("d:/image_copy.jpg");
        FileCopyUtils.copy(response, outFile.getFile());
    }

上面第二个参数为报文体参数数据,第三个参数指定方法的返回值类型,第四个参数为URL占位符参数的值。

RestTemplate是Spring的模板类,在客户端程序中可使用该类调用Web服务器端的服务,它支持REST风格的URL。此外,它像RequestMappingHandlerAdapter一样拥有一张HttpMessageConverter的注册表,RestTemplate默认已经注册了以下HttpMessageConverter:

  •  ByteArrayHttpMessageConverter。
  • StringHttpMessageConverter。
  • ResourceHttpMessageConverter。
  • SourceHttpMessageConverter。
  • AllEncompassingFormHttpMessageConverter。

所以,在默认情况下,RestTemplate就可以利用这些HttpMessageConverter对响应数据进行相应的转换处理。可通过RestTemplate的setMessageConverters(List<HttpMessageConverter<?>> messageConverters)方法手工注册HttpMessageConverter。

和@RequestBody/@ResponseBody类似,HttpEntity<?>不但可以访问请求和响应报文体的数据,还可以访问请求和响应报文头的数据。SpringMVC根据HttpEntity的泛型类型查找对应的HttpMessageConverter。

代码清单17-18:UserController.java:使用HttpEntity<?>

    @RequestMapping(value = "/handle43")
    public String handle43(HttpEntity<String> requestEntity) {
        long contentLen = requestEntity.getHeaders().getContentLength();
        System.out.println("user:" + requestEntity.getBody());
        return "success";
    }

    @RequestMapping(value = "/handle44/{imageId}")
    public ResponseEntity<byte[]> handle44(
            @PathVariable("imageId") String imageId) throws Throwable {
        Resource res = new ClassPathResource("/image.jpg");
        byte[] fileData = FileCopyUtils.copyToByteArray(res.getInputStream());
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(
                fileData, HttpStatus.OK);
        return responseEntity;
    }

在1处使用HttpEntity<String>指定入参的类型,SpringMVC分析出泛型类型为String,使用StringHttpMessageConverter将请求体内容绑定到httpEntity中,返回的String类型的值为逻辑视图名。

2处的处理方法返回值类型为ResponseEntity<byte[]>,SpringMVC分析出泛型类型为byte[],使用ByteArrayHttpMessageConverter输出图片数据流。

3. 处理XML和JSON

SpringMVC提供了几个处理XML和JSON格式的请求/响应消息的HttpMessageConverter。

  •  MarshallingHttpMessageConverter:处理XML格式的请求或响应消息。
  • Jaxb2RootElementHttpMessageConverter:同上,底层使用JAXB。
  • MappingJackson2HttpMessageConverter:处理JSON格式的请求或响应消息

因此,只要在Spring Web容器中为RequestMappingHandlerAdapter装配好相应的处理XML和JSON格式的请求/响应消息的HttpMessageConverter,并在交互中通过请求的Accept指定MIME类型,SpringMVC就可使服务器端的处理方法和客户端透明地通过XML或JSON格式的消息进行通信,开发者几乎无须关心通信层数据格式的问题,可以将精力集中到业务层的处理上。

首先为RequestMappingHandlerAdapter装配可处理XML和JSON格式的请求/响应消息的HttpMessageConverter,如代码清单17-19所示。

        <bean
                class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
                p:marshaller-ref="xmlMarshaller" p:unmarshaller-ref="xmlMarshaller">
        </bean>
        <bean
                class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    </util:list>

    <bean id="xmlMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
        <property name="streamDriver">
            <bean class="com.thoughtworks.xstream.io.xml.StaxDriver"/>
        </property>
        <property name="annotatedClasses">
            <list>
                <value>com.smart.domain.User</value>
            </list>
        </property>
    </bean>

使用STAX对XML消息进行处理,STAX占用内存少,响应速度也很快。

使用XStream的注解定义XML转换规则,使用XStream注解的类在此声明。

然后在控制器中编写相应的方法,如代码清单17-20所示。

代码清单17-20:UserController.java:支持XML和JSON格式的消息处理方法

    @RequestMapping(value = "/handle51")
    public ResponseEntity<User> handle51(HttpEntity<User> requestEntity) {
        User user = requestEntity.getBody();
        user.setUserId("1000");
        return new ResponseEntity<User>(user, HttpStatus.OK);
    }

对于服务器端的处理方法而言,除使用@RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T>进行方法签名外,不需要进行任何额外的处理,借由SpringMVC中装配的HttpMessageConverter,它便拥有了处理XML及JSON格式的消息的能力。

在接收到一个HTTP请求时,handle51()如何知道请求消息的格式?在处理完成后,又根据什么确定响应消息的格式?答案很简单:通过请求消息头的Content-Type及Accept属性确定。下面使用RestTemplate编写调用handle51()方法的客户端程序,如代码清单17-21所示。

代码清单17-21:UserControllerTest.java:使用XML格式的请求/响应消息

    @Test
    public void testhandle51WithXml() {

        RestTemplate restTemplate = buildRestTemplate();

        User user = new User();
        user.setUserName("tom");
        user.setPassword("1234");
        user.setRealName("汤姆");

        HttpHeaders entityHeaders = new HttpHeaders();
        entityHeaders.setContentType(MediaType.valueOf("application/xml;UTF-8"));
        entityHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
        HttpEntity<User> requestEntity = new HttpEntity<User>(user, entityHeaders);


        ResponseEntity<User> responseEntity = restTemplate.exchange(
                "http://localhost:8080/chapter17/user/handle51.html",
                HttpMethod.POST, requestEntity, User.class);

        User responseUser = responseEntity.getBody();
        Assert.assertNotNull(responseUser);
        Assert.assertEquals("1000", responseUser.getUserId());
        Assert.assertEquals("tom", responseUser.getUserName());
        Assert.assertEquals("汤姆", responseUser.getRealName());
    }

    private RestTemplate buildRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();

        //①创建MarshallingHttpMessageConverter
        XStreamMarshaller xmlMarshaller = new XStreamMarshaller();
        xmlMarshaller.setStreamDriver(new StaxDriver());
        xmlMarshaller.setAnnotatedClasses(new Class[]{User.class});

        MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
        xmlConverter.setMarshaller(xmlMarshaller);
        xmlConverter.setUnmarshaller(xmlMarshaller);
        restTemplate.getMessageConverters().add(xmlConverter);


        //②创建MappingJacksonHttpMessageConverter
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        restTemplate.getMessageConverters().add(jsonConverter);
        return restTemplate;
    }

使用RestController和AsyncRestTemplate

从Spring4.0开始,Spring以Servlet3.0为基础进行开发。如果使用SpringMVC测试框架,则需要制定Servlet3.0兼容的JAR包(因为其Mock的对象都是基于Servlet3.0的)

>>SpringMVC实现REST与Restlet的异同:

原文地址:https://www.cnblogs.com/2008nmj/p/14784377.html