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

  • 相关阅读:
    首次搭建微信小程序记录各种细节
    vue3.x 首次搭建
    通过nodejs 下载阿里云省、市、区的json文件,使用在echart上
    vscode
    使用 ts + and design + react-redux + react-router-dom + ahook 开发 管理后台
    Python 字典(dict) 操作基础
    高斯混合模型视频背景建模的EM算法与Matlab 实现
    Tensorflow 10分钟快速上手
    Ubuntu16.04LTS 搜狗输入法无法输入中文
    Machine Learning Class 1:Introduction,supervised & unsupervised learning
  • 原文地址:https://www.cnblogs.com/2008nmj/p/14784377.html
Copyright © 2020-2023  润新知