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的异同: